Hi, I’m trying to solve folling case. I have a `L...
# arrow
d
Hi, I’m trying to solve folling case. I have a
List<IO<Either<E,A>>>
and I want to stop on first success. How to do this with Arrow?
s
You can use
traverse
on
List
with
EitherT.applicative(IO.applicative())
. And it should return you the shape
EitherT<ForIO, E, List<A>>
by calling
value()
you should be able to extract.
IO<Either<E, List,A>>
Trying with
ListK
is probably easiest. I can share a snippet later.
d
Traverse seems to stop on first error… This is my simple test:
Copy code
import arrow.Kind
import arrow.core.Either
import arrow.core.ForListK
import arrow.core.extensions.list.traverse.traverse
import arrow.core.left
import arrow.core.right
import arrow.fx.ForIO
import <http://arrow.fx.IO|arrow.fx.IO>
import <http://arrow.fx.extensions.io|arrow.fx.extensions.io>.monad.monad
import arrow.fx.fix
import arrow.mtl.EitherT
import arrow.mtl.extensions.eithert.applicative.applicative
import arrow.mtl.value

data class Error(val message: String)
data class Result(val result: String)

fun evaluate(value: Int): IO<Either<Error, Result>> = IO {
    if ((value % 5) == 2) {
        Result("Success: $value").right()
    } else {
        Error("Error: $value").left()
    }
}

fun main() {
    val list: List<IO<Either<Error, Result>>> = List(10) { index -> evaluate(index) }
    val result: IO<Either<Error, Kind<ForListK, Result>>> = list.traverse(EitherT.applicative<Error, ForIO>(IO.monad())) {
        EitherT(it)
    }.value().fix()
    val runResult: Either<Error, Kind<ForListK, Result>> = result.unsafeRunSync()
    println(runResult)
}
What I want to achive is to ignore all errors until I get a first success and the success should be the result.
s
Oh you want to stop on the first success case. My bad.
Okay, let me think about that. So you want to implement a traverse that uses
handleErrorWith
as composition instead, right? So the first success returns, and they execute in order.
Copy code
val ios: List<IO<Either<E, A>>> = TODO()

val eitherT: List<EitherT<ForIO, E, A>> = ios.map { EitherT }

val ioEA: IO<Either<E, A>> = eitherT.fold(EitherT.raiseError(RuntimeException("Initial error")) { acc, a ->
  acc.handleErrorwith { a }
}.value()
The first success will short-circuit all remaining
handleErrorWith
calls. So you can provide a
list
of
IO<Either<E, A>>
and it’ll return the first successful
Either<E, A>
.
So it’s just folding over the original list, and composing all IOs together using
handleErrorWith
🙂
😍 1
d
@simon.vergauwen I tried to implement your solution in Arrow 0.10.5. Unfortunately, I cant get it compile 😞.
s
I wrote it from memory. Is there a snippet you can share? Otherwise I’ll whip up an example with imports later 😉
d
Copy code
import arrow.core.Either
import arrow.core.left
import arrow.core.right
import arrow.fx.ForIO
import <http://arrow.fx.IO|arrow.fx.IO>
import <http://arrow.fx.extensions.io|arrow.fx.extensions.io>.monad.monad
import arrow.mtl.EitherT
import arrow.mtl.extensions.eithert.applicativeError.raiseError

data class Error(val message: String)
data class Result(val result: String)

fun evaluate(value: Int): IO<Either<Error, Result>> = IO {
    if ((value % 5) == 2) {
        Result("Success: $value").right()
    } else {
        Error("Error: $value").left()
    }
}

val ios: List<IO<Either<Error, Result>>> = List(10) { index -> evaluate(index) }
val eitherT: List<EitherT<Error, ForIO, Result>> = ios.map { io -> EitherT(io) }
val ioEA: IO<Either<Error, Result>> = eitherT.fold(EitherT.raiseError(IO.monad(), Error("Initial"))) { acc, a ->
    acc.handleErrorwith { a }
}.value().fix()

fun main() {
    val result = ioEA.unsafeRunSync()
    println(result)
}
s
What about this? It seems
EitherT
is still missing some useful operator like
handleErrorWith
. So I used the one from the underlying
IO
directly.
d
@simon.vergauwen Which version of Arrow have you used?
s
All
This is on master. I don’t have a 0.10.5 project checked out right now
d
The only thing I changed to make it compile under 0.10.5 is that I extracted initial error to:
Copy code
val initial = EitherT
        .applicativeError<Error, ForIO>(IO.monad())
        .raiseError<Error>(Error("Initial error")) as arrow.mtl.EitherT<Error, ForIO, Result>
And the result I get is:
Left(Error(message=Initial))
s
Yes, there that API functions seems to be missing. You could easily create your own overload, something like this. You can also do
fun <A, F, B> EitherT.Companion.raiseError(a: A): EitherT<A, ForIO, B> = EitherT(IO.just(a.left())
You get initial?
d
Yes
s
Hey @dnowak not sure if you solved it yet but you want an operator for
EitherT
that short-circuits on
Throwable
and
E
. For example
handleBothWith
.
Copy code
fun <A, B> EitherT<A, IOPartialOf<Nothing>, B>.handleBothWith(
  f: (Either<Throwable, A>) -> EitherT<A, IOPartialOf<Nothing>, B>
): EitherT<A, IOPartialOf<Nothing>, B> =
  EitherT(value().fix().attempt().flatMap {
    when (it) {
      is Either.Left -> f(it)
      is Either.Right -> when (val bb = it.b) {
        is Either.Left -> f(Either.Right(bb.a))
        is Either.Right -> EitherT(IO.just(bb))
      }
    }.value()
  })
This way you can recover from
Throwable
and
Error
while folding and the first success
B
will finish the chain.