Haskell中的验证

时间:2012-01-04 03:16:17

标签: haskell

我有一些我需要验证的嵌套记录,我想知道什么是惯用的Haskell方法。

简化:

data Record = Record {
  recordItemsA :: [ItemA],
  recordItemB :: ItemB
} deriving (Show)

data ItemA {
  itemAItemsC :: [ItemC]
} deriving (Show)

要求是:

  • 收集并返回所有验证错误
  • 某些验证可能跨项目,例如ItemsA针对ItemB
  • String足以表示错误

我目前的代码感觉很尴尬:

type ErrorMsg = String

validate :: Record -> [ErrorMsg]
validate record =
  recordValidations ++ itemAValidations ++ itemBValidations
  where
    recordValidations :: [ErrorMsg]
    recordValidations = ensure (...) $
      "Invalid combination: " ++ (show $ recordItemsA record) ++ " and " ++ (show $ recordItemsB record)
    itemAValidations :: [ErrorMsg]
    itemAValidations = concat $ map validateItemA $ recordItemsA record
    validateItemA :: ItemA -> [ErrorMsg]
    validateItemA itemA = ensure (...) $
      "Invalid itemA: " ++ (show itemA)
    itemBValidations :: [ErrorMsg]
    itemBValidations = validateItemB $ recordItemB record
    validateItemB :: ItemB -> [ErroMsg]
    validateItemB itemB = ensure (...) $
      "Invalid itemB: " ++ (show itemB)

ensure :: Bool -> ErrorMsg -> [ErrorMsg]
ensure b msg = if b then [] else [msg]

4 个答案:

答案 0 :(得分:4)

你已经拥有的基本上很好,它只需要一些清理:

  • 子验证应该是顶级定义,因为它们相当复杂。 (顺便说一句,where子句定义上的类型签名通常被省略。)
  • 缺乏一致的命名惯例
  • 许多(++)依次变得丑陋 - 使用concat(或许unwords)而不是
  • 次要格式怪癖(有一些多余的括号,concat . map fconcatMap f等。)

所有这一切的产物:

validateRecord :: Record -> [ErrorMsg]
validateRecord record = concat
  [ ensure (...) . concat $
      [ "Invalid combination: ", show (recordItemsA record)
      , " and ", show (recordItemB record)
      ]
  , concatMap validateItemA $ recordItemsA record
  , validateItemB $ recordItemB record
  ]

validateItemA :: ItemA -> [ErrorMsg]
validateItemA itemA = ensure (...) $ "Invalid itemA: " ++ show itemA

validateItemB :: ItemB -> [ErrorMsg]
validateItemB itemB = ensure (...) $ "Invalid itemB: " ++ show itemB

我觉得这很不错。如果您不喜欢列表表示法,可以使用Writer [ErrorMsg] monad:

validateRecord :: Record -> Writer [ErrorMsg] ()
validateRecord record = do
  ensure (...) . concat $
    [ "Invalid combination: ", show (recordItemsA record)
    , " and ", show (recordItemB record)
    ]
  mapM_ validateItemA $ recordItemsA record
  validateItemB $ recordItemB record

validateItemA :: ItemA -> Writer [ErrorMsg] ()
validateItemA itemA = ensure (...) $ "Invalid itemA: " ++ show itemA

validateItemB :: ItemB -> Writer [ErrorMsg] ()
validateItemB itemB = ensure (...) $ "Invalid itemB: " ++ show itemB

ensure :: Bool -> ErrorMsg -> Writer [ErrorMsg] ()
ensure b msg = unless b $ tell [msg]

答案 1 :(得分:4)

阅读the 8 ways to report errors in Haskell文章。对于您的特定情况,因为您需要收集所有错误而不仅仅是第一个错误,@ ehird建议的Writer monad方法似乎最合适,但了解其他常用方法很好。

答案 2 :(得分:1)

在@ ehird的回答基础上,你可以引入一个Validate类型类:

class Validate a where
  validate :: a -> [ErrorMsg]

instance Validate a => Validate [a] where
  validate = concatMap validate

instance Validate Record where
  validate record = concat
    [ ensure (...) . concat $
      [ "Invalid combination: ", show (recordItemsA record)
      , " and ", show (recordItemB record)
      ]
    , validate $ recordItemsA record
    , validate $ recordItemB record
    ]

instance Validate ItemA where
  validate itemA = ensure (...) $ "Invalid itemA: " ++ show itemA

instance Validate ItemB where
  validate itemB = ensure (...) $ "Invalid itemB: " ++ show itemB

答案 3 :(得分:0)

您可能会考虑尝试的一件事是,不是在事后验证您的数据,而是使用优秀的fclabels包中的镜头作为您的数据接口(而不是模式匹配/类型构造函数),以确保您的数据总是正确的。

查看支持失败here的变体,并通过将对数据类型进行一些验证的setter和getter传递给lens函数来构建镜头。

如果您需要更复杂的错误报告或其他内容,请查看Maybe lens {{1}}变体的implementation,并根据抽象界面定义镜头。< / p>