I have a list of entities where even one failed validation would yield an error. However, I'd still like to iterate the whole list and collect all the errors for further logging.
Traverse/sequence with a default Either's Applicative would yield Either<E, A[]>
(only the first encountered error) instead of needed Either<E[], A[]>
.
Is there a default tool in the library to achieve this, or could it be achieved by writing a custom Applicative?
There’s no fp-ts function that does this directly, but you could use getApplicativeValidation
from the Either
module:
import * as E from 'fp-ts/lib/Either'
import * as RA from 'fp-ts/lib/ReadonlyArray'
import {pipe} from 'fp-ts/lib/function'
const collectErrors = <E, A>(
xs: readonly E.Either<E, A>[]
): E.Either<readonly E[], readonly A[]> =>
pipe(
xs,
RA.traverse(E.getApplicativeValidation(RA.getSemigroup<E>()))(
E.mapLeft(RA.of)
)
)
// Left(['a', 'b'])
collectErrors([E.left('a'), E.right(1), E.left('b'))])
// Right([1, 2])
collectErrors([E.right(1), E.right(2)])
// Right([])
collectErrors([])
How this works is that getApplicativeValidation
takes a Semigroup
instance and returns an Applicative
instance that can be used with traverse
. The applicative instance will combine errors using the semigroup instance.
RA.traverse(
// The applicative instance for the either that will collect the errors
// into an array
E.getApplicativeValidation(
// The semigroup instance for readonly E[], which concatenates the arrays
RA.getSemigroup<E>()
)
)(
// Turn each Either<E, A> into Either<readonly E[], A> by putting each error
// inside an array so they can be concatenated
E.mapLeft(RA.of)
)