如何收集 Either Monad 中的所有错误消息?

How is it possible to collect all error messages in the Either Monad?

我试图用 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 一起?

这是因为 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)

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

要累积错误,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 库认为上述定律符合特定的等价概念,这在错误报告的上下文中可以说是可以的,其中应该发生的最坏情况是您报告的错误可能比您预期的要少。 (图书馆的文档也包含很多相关的论述。)