3

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?

Zetrik
  • 65
  • 6

2 Answers2

8

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.

Mark Seemann
  • 225,310
  • 48
  • 427
  • 736
  • 6
    Because the `Applicative` instance never actually uses the `mempty` of the `Monoid`, one can get by with a `Semigroup` instance. This allows us to use `Data.List.NonEmpty` as the accumulator, reflecting in the types that there will always be at least one error inside the `Left`. – danidiaz Aug 10 '20 at 19:27
  • Just for understanding. We utilize the preexisting `Monoid` instance of `Either` to combine the `Left` values via `mappend`? I know that a `Functor` Instance is needed for the `Applicative` to work and by deriving them we got one. I haven't had much experience with the `DeriveFunctor Language Pragma`. How would a valid `Functor` instance look like? PS: Thanks for sharing. I'll read the entire series on `Applicative Functors`. – Zetrik Aug 11 '20 at 12:00
  • @Zetrik No, the `Monoid m` in the `Applicative` instance is a constraint. It can be any `Monoid` instance. For validation, this will typically be the `[]` (list) instance. As @danidiaz writes, you can relax the type constraint to `Semigroup` and use `NonEmpty` as the instance instead. IIRC, when I wrote the article, `Semigroup` wasn't a sub class of `Monoid`, like it is now. – Mark Seemann Aug 11 '20 at 15:21
  • @Zetrik The `Functor` instance for `Validation` would be equivalent to the `Either` instance. The only thing you'd need to do is to unwrap the `Either` inside the `Validation`, call `fmap` on the `Either` value, and wrap the result in a new `Validation` value. This is completely automatable, so that's what the `DeriveFunctor` language extension does. It's a good exercise to do by hand, though, if you're new to the language. – Mark Seemann Aug 11 '20 at 15:25
  • @MarkSeemann Ah now I got it and with `Semigroup` as the constraint I must replace `mappend` with `<>` right? – Zetrik Aug 11 '20 at 15:33
  • @MarkSeemann I'm relatively new to the language. I started learning a few years ago, as an exercise for React / Redux. Now I like the concepts of functional programming so much that I want to delve deeper into the paradigm and have chosen `Haskell` for that. So, an explicit `Functor` instance as an exercise sounds good. I will do that, thanks. – Zetrik Aug 11 '20 at 16:08
  • @Zetrik Yes, if you relax the constraint to `Semigroup` you must use `<>` instead of `mappend`. – Mark Seemann Aug 11 '20 at 17:13
6

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:

-- 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.)
Li-yao Xia
  • 31,896
  • 2
  • 33
  • 56
  • Thank you for the explanation. I will explore both repositories and try to understand both approaches. `Applicatives` vs `Monad Transformers`. Do you know any practical resources to understand `Monad Transformers` more deeply? – Zetrik Aug 11 '20 at 12:01
  • @Zetrik 1. My favorite introduction to monad transformers is [All About Monads](https://wiki.haskell.org/All_About_Monads). 2. You do not need to understand monad transformers to use the non-transformer version of the monad, if you want to make progress now and learn later. (Big if!) – Daniel Wagner Aug 11 '20 at 14:23
  • @DanielWagner Thanks for sharing. Currently I'm trying to become more productive with Haskell in general. But practical examples are difficult to find. – Zetrik Aug 11 '20 at 15:09
  • @Zetrik The most simple notion of a transformer is an implementation of a `bind` function that takes another `bind` function as an argument, namely the base monad . Now you implement your transformer `bind` in a way that it respects the monad laws for every base monad you pass. The same applies to `return`. Simply put, each transformer is a hand-written `bind` composition. –  Aug 11 '20 at 16:05
  • @scriptum Yeah. I got that `Functors` and `Applicatives` compose, `Monads`don't. `Monad Transformers` are a way to give `Monads` the ability to be composed. The only thing missing is practice. – Zetrik Aug 11 '20 at 16:19