Search code examples
kotlinfunctional-programmingarrow-kt

Kotlin arrow-kt, functional way to map a collection of either to an either of a collection


I've been using kotlin arrow quite a bit recently, and I've ran into a specific use case that has me stuck.

Let's say I have a collection of some object that I want to convert to another datatype using a convert function. Let's also say that this convert function has an ability to fail-- but instead of throwing an exception, it will just return an Either, where Either.Left() is a failure and Either.Right() is the mapped object. What is the best way to handle this use case? Some sample code below:

val list: Collection<Object> // some collection
val eithers: List<Either<ConvertError, NewObject>> = list.map { convert(it) } // through some logic, convert each object in the collection 

val desired: Either<ConvertError, Collection<NewObject>> = eithers.map { ??? } 


fun convert(o: Object) : Either<ConvertError, NewObject> { ... }

Essentially, I'd like to call a mapping function on a collection of data, and if any of the mappings respond with a failure, I'd like to have an Either.Left() containing the error. And then otherwise, I'd like the Either.Right() to contain all of the mapped objects.

Any ideas for a clean way to do this? Ideally, I'd like to make a chain of function calls, but have the ability to percolate an error up through the function calls.


Solution

  • You can use Arrow's computation blocks to unwrap Either inside map like so:

    import arrow.core.Either
    import arrow.core.computations.either
    
    val list: ListObject> // some collection
    val eithers: List<Either<ConvertError, NewObject>> = list.map { convert(it) } // through some logic, convert each object in the collection 
    
    val desired: Either<ConvertError, Collection<NewObject>> = either.eager {
      eithers.map { convert(it).bind() } 
    }
    
    fun convert(o: Object) : Either<ConvertError, NewObject> { ... }
    

    Here bind() will either unwrap Either into NewObject in the case Either is Right, or it will exit the either.eager block in case it finds Left with ConvertError. Here we're using the eager { } variant since we're assigning it to a val immediately. The main suspend fun either { } block supports suspend functions inside but is itself also a suspend function.

    This is an alternative to the traverse operator. The traverse operation will be simplified in Arrow 0.12.0 to the following:

    import arrow.core.traverseEither
    
    eithers.traverseEither(::convert) 
    

    The traverse operator is also available in Arrow Fx Coroutines with support for traversing in parallel, and some powerful derivatives of this operation.

    import arrow.fx.coroutines.parTraverseEither
    
    eithers.parTraverseEither(Dispatcheres.IO, ::convert)