I have a few nested records that I need to validate, and I wonder what is an idiomatic Haskell way to do it.
To simplify:
data Record = Record {
recordItemsA :: [ItemA],
recordItemB :: ItemB
} deriving (Show)
data ItemA {
itemAItemsC :: [ItemC]
} deriving (Show)
Requirements are:
ItemsA
against ItemB
String
s are sufficient to represent errorsI currently have code that feels awkward:
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]
Form Validation is a necessary process before the data entered in the form is submitted to the database. This is done to avoid unnecessary errors. In PHP Form validation, the script checks for data in respective fields based on the rules set by the developer, and returns an error if it does not meet the requirements.
Validation in PHP is the process where we check if the input information in the various fields in any form such as text, checkbox or radio button, etc, submitted by the end-user in the form is correct or not using HTML code.
The server-side validation validates data in the web server using PHP. To validate data in PHP, you can use the filter_var() and filter_input() functions.
Read the 8 ways to report errors in Haskell article. For your particular case, as you need to collect all errors and not only the first one, the approach with Writer
monad suggested by @ehird seems to fit best, but it's good to know other common approaches.
What you have already is basically fine, it just needs some clean-up:
where
clause definitions are usually omitted.)(++)
s in sequence can get ugly — use concat
(or perhaps unwords
) insteadconcat . map f
is concatMap f
, etc.)The product of all this:
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
I think that's pretty good. If you don't like the list notation, you can use the 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]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With