在数据验证中使用“任何一个”

时间:2019-05-29 15:06:59

标签: haskell

我有一个专门用于创建“学生”(名字,姓氏,年龄)并验证输入数据的程序。我的问题是:当我插入学生的名字时,例如姓名没有2个字母或年龄小于18岁-程序仅显示一个错误。如何使用“任何一个函数”创建例如包含所有错误的字符串?

BufferedImage

3 个答案:

答案 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

然后还有另一个功能来处理列表,并将其打印给用户,或将其记录下来,或进行任何您想使用的操作。