【问题标题】:How is it possible to collect all error messages in the Either Monad?如何在 Either Monad 中收集所有错误消息?
【发布时间】:2020-08-10 18:49:27
【问题描述】:

我尝试使用ApplicativesEither Monad 验证Record 的构造。它工作正常。但我看不到所有错误消息。只有第一个可见,因为Either MonadRight 路径忽略了它们。

这是我的代码:

import Data.Either (either)
import Text.Printf (printf)

data Record = Record
  { fieldA :: String
  , fieldB :: String
  , fieldC :: String
  } deriving (Show, Eq)

type Err = String
    
setField :: String -> String -> Either Err String
setField field value
  | length value > 0 = Right value
  | otherwise = Left $ printf "value for field %s is to short" field

setFieldA :: String -> Either Err String
setFieldA = setField "fieldA"

setFieldB :: String -> Either Err String
setFieldB = setField "fieldB"

setFieldC :: String -> Either Err String
setFieldC = setField "fieldC"
  
makeRecord :: Either Err Record
makeRecord = Record
  <$> setField "fieldA" "valueA"
  <*> setField "fieldB" "valueB"
  <*> setField "fieldC" "valueC"

makeRecord' :: Either Err Record
makeRecord' = Record
  <$> setFieldA "valueA"
  <*> setFieldB "valueB"
  <*> setFieldC "valueC"

recordFromEither :: Either Err Record -> Maybe Record
recordFromEither r =
  case r of
    Right v -> Just $ v
    Left _ -> Nothing

main :: IO ()
main = putStrLn $ output
  where
    output = case makeRecord of
      Right v -> show v
      Left err -> show err

main' :: IO ()
main' = putStrLn $ either id show makeRecord'

我的问题是如何保存和显示所有错误消息。也许与 State Monad 一起?

【问题讨论】:

    标签: haskell error-handling monads either extract-error-message


    【解决方案1】:

    这是因为 Either Applicative 实例的工作方式。您可以做的是将Either 包装在newtype 中:

    newtype Validation e r = Validation (Either e r) deriving (Eq, Show, Functor)
    

    然后给它另一个Applicative实例:

    instance Monoid m => Applicative (Validation m) where
      pure = Validation . pure
      Validation (Left x) <*> Validation (Left y) = Validation (Left (mappend x y))
      Validation f <*> Validation r = Validation (f <*> r)
    

    您现在可以使用&lt;$&gt;&lt;*&gt; 组成Validation [Err] Record 结果。有关详细信息,请参阅我在 Applicative validation 上的文章。

    【讨论】:

    • 因为Applicative 实例实际上从不使用Monoidmempty,所以可以使用Semigroup 实例。这允许我们使用Data.List.NonEmpty 作为累加器,这反映在Left 中总是至少有一个错误的类型。
    • 只是为了理解。我们利用Either 的现有Monoid 实例通过mappend 组合Left 值?我知道Applicative 工作需要Functor 实例,通过派生它们,我们得到了一个。我对DeriveFunctor Language Pragma 没有太多经验。一个有效的Functor 实例会是什么样子? PS:谢谢分享。我会在Applicative Functors阅读整个系列。
    • @Zetrik 不,Applicative 实例中的Monoid m 是一个约束。它可以是任何Monoid 实例。对于验证,这通常是[](列表)实例。正如@danidiaz 所写,您可以将类型约束放宽到Semigroup 并使用NonEmpty 作为实例。 IIRC,当我写这篇文章时,Semigroup 不是 Monoid 的子类,就像现在一样。
    • @Zetrik FunctorValidation 实例将等效于 Either 实例。您唯一需要做的就是解开Validation 中的Either,在Either 值上调用fmap,然后将结果包装在一个新的Validation 值中。这是完全自动化的,这就是DeriveFunctor 语言扩展的作用。不过,如果您不熟悉这门语言,那么手工练习是一个很好的练习。
    • @MarkSeemann 啊,现在我明白了,以Semigroup 作为约束,我必须用&lt;&gt; 替换mappend 对吗?
    【解决方案2】:

    要累积错误,您需要为 Either 使用不同的 Applicative 实例。 Either 的这个变体有时称为Validation。 Hackage 上至少有两个库为该实例提供了 Either 的变体:

    -- Standard definition
    (<*>) :: Either e (a -> b) -> Either e a -> Either e b
    Left e <*> _ = Left e
    Right _ <*> Left e = Left e
    Right f <*> Right x = Right (f x)
    
    -- "Validation" variant
    (<*>) :: Monoid e => Either e (a -> b) -> Either e a -> Either e b
    Left e <*> Left e' = Left (e <> e')
    Left e <*> Right _ = Left e
    Right _ <*> Left e = Left e
    Right f <*> Right x = Right (f x)
    

    关于这个话题,一个共同的争论点是“验证”变体是否与EitherMonad 操作兼容(或者它是否应该首先兼容):

    (u <*> v)   =   (u >>= \f -> v >>= \x -> pure (f x))
    

    我在上面提到了两个库,因为对这个主题有不同的看法(我认为归结为没有就相等的传统定义达成一致,这本身就是 Haskell 没有正式语义的症状)。

    • validation 库表明不存在兼容的 monad 实例,因此不要定义一个。
    • monad-validate 库认为,上述定律支持特定的等效概念,这在错误报告的上下文中可以说是可以做到的,但最坏的情况是您报告的错误可能比您预期的要少。 (图书馆的文档也包含很多相关的说明。)

    【讨论】:

    • 感谢您的解释。我将探索这两个存储库并尝试理解这两种方法。 ApplicativesMonad Transformers。你知道有什么实用资源可以更深入地了解Monad Transformers吗?
    • @Zetrik 1. 我最喜欢的 monad 转换器介绍是 All About Monads。 2. 如果你想现在进步,以后再学习,你不需要了解 monad 转换器就可以使用 monad 的 non-transformer 版本。 (如果!)
    • @DanielWagner 感谢分享。目前,我正在努力提高 Haskell 的工作效率。但实际例子很难找到。
    • @Zetrik 最简单的转换器概念是一个 bind 函数的实现,它接受另一个 bind 函数作为参数,即基本 monad。现在你实现你的转换器bind,它尊重你通过的每个基本单子的单子定律。这同样适用于return。简单来说,每一个变形金刚都是手写的bind作文。
    • @scriptum 是的。我知道FunctorsApplicatives 撰写,Monadsdon't。 Monad Transformers 是一种赋予Monads 组合能力的方法。唯一缺少的就是练习。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-07-03
    • 2018-02-18
    • 2011-09-23
    • 2021-09-04
    • 1970-01-01
    • 2020-10-25
    • 1970-01-01
    相关资源
    最近更新 更多