我有一些我需要验证的嵌套记录,我想知道什么是惯用的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]
答案 0 :(得分:4)
你已经拥有的基本上很好,它只需要一些清理:
where
子句定义上的类型签名通常被省略。)(++)
依次变得丑陋 - 使用concat
(或许unwords
)而不是concat . map f
是concatMap 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>