Jakub Gwóźdź
08/02/2024, 6:10 AMList<Either<E,A>>
(or List<EitherNel<E,A>>
) to an EitherNel<E,List<A>>
?
Right now I created myself something like:
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?Jakub Gwóźdź
08/02/2024, 8:40 AMmapOrAccumulate
🙂
(it’s just that it works on exceptions inside… 😕 )Gabriel Shanahan
08/09/2024, 4:14 PMsequence
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:
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.Jakub Gwóźdź
08/12/2024, 6:11 AM