As indicated by others, this is totally “a thing”. The ResultME library linked by @rupert makes a great practical choice of making the accumulating error type a NonEmpty List which is what you are going to want 99% of the time.

You asked about concepts so I’d like to try to expand on that a little.

## Generality

First, is whether there is generalization that we can make. Yes. We can combine errors for any type that supports combining/appending. That is a Semigroup which we can represent with a simple function. Please excuse the formatting but below you will find a function that will build a record of functions for combining the error of any result for which we can supply an append function for the error type

```
forErrorType :
(err -> err -> err)
->
{ andMap : Result err a -> Result err (a -> b) -> Result err b
, map2 : (a -> b -> c) -> Result err a -> Result err b -> Result err c
, map3 : (a -> b -> c -> d) -> Result err a -> Result err b -> Result err c -> Result err d
, map4 : (a -> b -> c -> d -> e) -> Result err a -> Result err b -> Result err c -> Result err d -> Result err e
, traverse : (a -> Result err b) -> List a -> Result err (List b)
, sequence : List (Result err a) -> Result err (List a)
}
forErrorType append=
let
andMap aResult aToBResult =
case ( aResult, aToBResult ) of
( Err a, Err aToB ) -> Err <| append a aToB
( Err a, _ ) -> Err a
( _, Err aToB ) -> Err aToB
( Ok a, Ok aToB ) -> Ok <| aToB a
map2 f a b = Ok f |> andMap a |> andMap b
sequence = List.foldl (map2 (::)) <| Ok []
in
{ andMap = andMap
, map2 = map2
, map3 = \f a b c -> Ok f |> andMap a |> andMap b |> andMap c
, map4 = \f a b c d -> Ok f |> andMap a |> andMap b |> andMap c |> andMap d
, traverse = \f -> List.map f >> sequence
, sequence = sequence
}
```

Once again, 99% of the time you just want a NonEmpty List to accumulate your errors so I am not saying you would want to do this. I am only saying that you can and that it is general.

You might use this, for example, if your error type were a bitwise `Int`

where each error is represented as a specific bit. In that case you could write

```
bitwiseResult = forErrorType Bitwise.or
-- and now we can call `bitwiseResult.map2` `map3`, etc. etc. to get combining behavior
```

## Categories

Second, you might ask how this “thing” that you have discovered relates to Categories such as Functor and Monad.

If you don’t care then please just disregard!

The long and short is that as soon as you start accumulating errors in your Result your Result ceases to be a law abiding Monad. You can obviously still call `andThen`

on the `Result`

but the Result’s behavior will not follow the laws. Essentially the laws indicate that you should be able to write `andMap`

in terms of `andThen`

. However, when `andMap`

accumulates errors that ceases to be true. `andThen`

for `Result`

short-circuits (stops) on the first error while `andMap`

combines both errors. So they differ.

This really only matters, in my opinion (which is probably ill-informed and wrong ), in languages with abstraction over Functor and Monad. In those languages you need types to be substitutable and to follow laws in order to avoid surprising behavior. Since Elm supports neither type classes nor scrap-your-type-classes style abstraction (higher kinds + Rank2 type records) nor OCaml-like modules, this is a moot point. In Elm you are pretty much always aware of the **concrete** semantics of your type so you don’t have to really worry about it as much. There still might be cases for surprising behavior but I suspect they would be very edge case.

### Haskell Examples

However, if interested you can see that for the purposes of obeying laws the Haskell page Data.Validation only implements Applicative for Validation and not Monad. That means they offer `andMap`

, `map2`

, `map3`

, etc. but don’t offer `andThen`

.

By contrast, there is another package ValidateT which has **fantastic** documentation on the subject. The author *does* make the Validation a monad by arguing for weaker Monad laws (which is kind of an Obi Wan Kenobi “from a certain point of view” style argument).

### PureScript

Then there is the PureScript Validation library which does something really beautiful: They allow you to have your and eat it too!!!

- They only make the Validation type an instance of abstract Functor and Applicative and
**not** Monad because they don’t want to violate the laws.
- However, they offer an Elm-like
`andThen`

which is the Monad bind function.

That means that the PureScript validation type is perfectly safe to be used correctly in abstract settings AND you can also use the very helpful (and often necessary, in my experience) `andThen`

function when working with the validation in concrete settings.

So that type is perfectly law abiding in the abstract and useful in the concrete. I think this is probably the best you can do and it gave me goosebumps the first time I saw it.