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

核能气质少年 提交于 2021-02-04 19:11:07

问题


I tried to validate the construction of a Record with Applicatives and the Either Monad. It works fine. But I can't see all Error Messages. Only the first is visible because the Right Path of the Either Monad ignores them.

Here is my code:

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'

My question is how can I keep and display all error messages. Maybe with the State Monad?


回答1:


That's because of the way the Either Applicative instance works. What you can do is to wrap Either in a newtype:

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

Then give it another Applicative instance:

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)

You can now use <$> and <*> to compose a Validation [Err] Record result. See my article on Applicative validation for more details.




回答2:


To accumulate errors, you need a different Applicative instance for Either. This variant of Either is sometimes called Validation. At least two libraries on Hackage provide a variant of Either with that instance:

  • https://hackage.haskell.org/package/validation
  • https://hackage.haskell.org/package/monad-validate
-- 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)

On this topic, a common point of contention is whether the "validation" variant is compatible with the Monad operations of Either (or whether it should be compatible in the first place):

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

I mentioned two libraries above because there are differing opinions on the topic (which I think boil down to there being no agreed upon conventional definition of equality, which itself is a symptom of Haskell having no formal semantics).

  • The validation library says that no compatible monad instance exists, so refrains from defining one.
  • The monad-validate library considers that the law above holds up to a particular notion of equivalence, which is arguably okay to do in the context of error reporting, where the worst that should happen is that you might report fewer error than you'd expect. (The library's documentation also contains a lot of relevant exposition.)


来源:https://stackoverflow.com/questions/63346279/how-is-it-possible-to-collect-all-error-messages-in-the-either-monad

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!