Haskell中的验证

Ski*_*gys 10 haskell

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

简化:

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

data ItemA {
  itemAItemsC :: [ItemC]
} deriving (Show)
Run Code Online (Sandbox Code Playgroud)

要求是:

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

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

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]
Run Code Online (Sandbox Code Playgroud)

ehi*_*ird 5

您已经拥有的基本上没问题,只需要进行一些清理:

  • 子验证应该是顶级定义,因为它们相当复杂。(顺便说一句,where子句定义上的类型签名通常被省略。)
  • 缺乏一致的命名约定
  • (++)顺序中的很多s 可能会变得丑陋 - 使用concat(或者可能unwords)代替
  • 轻微的格式化怪癖(有一些多余的括号,concat . map fisconcatMap 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
Run Code Online (Sandbox Code Playgroud)

我认为这很好。如果你不喜欢列表符号,你可以使用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]
Run Code Online (Sandbox Code Playgroud)


npo*_*cop 5

阅读Haskell文章中报告错误的 8 种方法。对于您的特定情况,由于您需要收集所有错误而不仅仅是第一个错误,Writer@ehird 建议的 monad方法似乎最适合,但了解其他常见方法也很好。