Search code examples
haskellerror-handlingmonadseitherextract-error-message

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


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?


Solution

  • 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.