我有一个专门用于创建“学生”(名字,姓氏,年龄)并验证输入数据的程序。我的问题是:当我插入学生的名字时,例如姓名没有2个字母或年龄小于18岁-程序仅显示一个错误。如何使用“任何一个函数”创建例如包含所有错误的字符串?
BufferedImage
答案 0 :(得分:9)
两个Monad和Applicative实例不能累积错误:根据法律,它们必须停在第一个Left值处。因此,如果要使用Either来累积错误,则必须手动操作,而不是通过Applicative或Monad实例。
您正在寻找的是Validation。这样,您可以编写:
causes :: Applicative f => Bool -> a -> Validation (f a) ()
True `causes` err = Failure $ pure err
False `causes` err = Success ()
validateA :: String -> Validation [String] Age
validateA a = (Success . Age $ age)
<* (age <= 18) `causes` "Student has to be at least 18"
<* (age > 100) `causes` "Student has more than 100 years. Probably it is an error."
where age = read a
,同样适用于您的其他验证器。 mkStudent
仍然像您所写的那样:应用组合器是组合验证值的正确方法。
答案 1 :(得分:0)
一种基本方法是定义一个自定义帮助器功能,该功能可以收集列表中的错误:
collectError :: Error e a -> [e]
collectError (Left e) = [e]
collectError _ = [] -- no errors
然后,我们可以按如下方式利用帮助程序:
mkStudent :: String -> String -> String -> Either [String] Student
mkStudent fn ln a = case (validate fn, validate ln, validate a) of
(Right xfn, Right xln, Right xa) -> Right (Student xfn xln xa)
(efn , eln , ea ) ->
Left (collectError efn ++ collectError eln ++ collectError ea)
这可能不是最优雅的方法,但是很简单。
如果程序中经常使用此模式,我很想制作一个自定义Applicative
来记录所有错误。像
data Result e a = Error e | OK a
deriving Functor
instance Semigroup e => Applicative (Result e) where
pure = OK
(OK f) <*> (OK x) = OK $ f x
(Error e1) <*> (Error e2) = Error (e1 <> e2)
(Error e1) <*> _ = Error e1
_ <*> (Error e2) = Error e2
(此名称应该已经存在于库中了。)
然后
mkStudent :: String -> String -> String -> Result [String] Student
mkStudent fn ln a =
Student <$> validate fn
<*> validate ln
<*> validate a
只要验证函数在失败时返回Error ["message"]
,并且返回类型为Result [String] T
,就应该可以正常工作。
答案 2 :(得分:0)
一个简单的解决方法是将左侧的Either
更改为自定义数据类型。我的意思是,您可以拥有Either String Age
,然后定义
Either ErrorType Age
data ErrorType = LowAge | WrongName
在我看来,这也将是更好的编程实践,因为您将把错误抛出及其处理程序分开。特别是在Haskell和函数式编程中,我不会携带字符串表示形式来处理错误。然后,您可以例如将错误合并到一个列表中,即:
mkStudent :: String -> String -> String -> Either [ErrorType] Student
然后还有另一个功能来处理列表,并将其打印给用户,或将其记录下来,或进行任何您想使用的操作。