Hi. Is there a library function that changes `List...
# arrow
j
Hi. Is there a library function that changes
List<Either<E,A>>
(or
List<EitherNel<E,A>>
) to an
EitherNel<E,List<A>>
? Right now I created myself something like:
Copy code
private fun <E, A> List<EitherNel<E, A>>.traverse(): EitherNel<E, List<A>> =
    mapNotNull(EitherNel<E, A>::leftOrNull).flatten().toNonEmptyListOrNull()?.left()
        ?: mapNotNull(EitherNel<E, A>::getOrNull).right()
But it feels like I’m reinventing wheel here, surely such operation is helpful for wider audience and there’s already a solution in Arrow?
Ok nvm, just found the
mapOrAccumulate
🙂 (it’s just that it works on exceptions inside… 😕 )
g
The operation you're describing is called
sequence
in general, and transform
F[R[_]]
into
R[F[_]]
. In the case of
Either
, you need to provide a semigroup for
L
(technobabble for "when implementing this operation, you need to specify how instances of
L
should be combined). For example, imagine you have
listOf(Left(<someL>), Left(<someOtherL>))
. Using
sequence
(the operation you're looking for) you want to transform this to a single instance of
Either
. It's clear it's gonna be an instance of
Left
, but what should be inside it? That's why you need the semigroup. Alternatively, you can just say "I always want to pick the first error" (i.e. fail-fast semantics, also called "monadic"), in which case you don't need the semigroup, and can use the
bind
DSL. For the "error accumulating version" (also called "applicative"), it's beneficial (but not necessary) to have the semigroup as a context receiver, and to define another established function,
mapN
. Here's some code:
Copy code
interface Semigroup<E> {
    fun combine(one: E, two: E): E
}

context(Semigroup<L>)
fun <L, R1, R2, R> mapN(one: Either<L, R1>, two: Either<L, R2>, transform: (R1, R2) -> R): Either<L, R> = when (one) {
    is Either.Right -> when (two) {
        is Either.Right -> transform(one.value, two.value).right()
        is Either.Left -> two
    }
    is Either.Left -> when (two) {
        is Either.Right -> one
        is Either.Left -> combine(one.value, two.value).left()
    }
}

context(Semigroup<L>)
fun <L, R> List<Either<L, R>>.sequenceA(): Either<L, List<R>> = fold(emptyList<R>().right() as (Either<L, List<R>>) ) { result, it ->
    mapN(result, it) { list, value -> list + value }
}

fun <L, R> List<Either<L, R>>.sequence(): Either<L, List<R>> = either {
    map { it.bind() }
}
As far as I can tell, the version of
mapOrAccumulate
that accepts an
(Error, Error) -> Error
function as its first parameter is the applicative variant, while the version without the parameter is the monadic one.
❤️ 2
arrow 1
j
Thank you very much for this knowledge!