Is there a more idiomatic way to nest Eithers? Thi...
# arrow
j
Is there a more idiomatic way to nest Eithers? This is what I have.
Copy code
sourceA().handleErrorWith {
    sourceB().handleErrorWith {
        sourceC()
    }
}
j
A more idiomatic way would be:
Copy code
sourceA()
    .flatMap { sourceB() }
    .flatMap { sourceC() }
If you only want to invoke sourceB and sourceC in cases errors occurred, then I guess your approach is fine.
j
Yeah I only want them as fallbacks. I wanted something like raceN but returning a left still cancels the others.
j
I believe the handleErrorWith approach is pretty idiomatic for the use case you are describing.
If you want to race and have a fallback, you could combine raceN and handleErrorWith. 🤔
j
I’m not sure what you mean. I image something with a signature like raceN but is
firstRight(a(), b(), c()).fold(onA, onB, onC)
Which wouldn’t cancel on a Left but would cancel on a Right and would return that Right.
j
I don’t think such a function exists. You can always write it yourself. 😃
s
@Jeff I propose just using something like
swap
on
Either
which swaps the side so you could do.
Copy code
race({ eitherA().swap() }, { eitherB().swap() }).swap()
It's a bit tedious but for sure the easiest way to achieve your tasks without to much overhead since 3x swap is minimal compared to dispatching 3 times. (2 for launching tasks, and one again to return to original ctx).
handleErrorWith
is the correct way of chaining
Left
you can consider it
flatMapLeft
and thus you could also write a
eitherLeft { }
block for it if you'd want to.
j
How is the swap going to help in this case? 😯 An error on the right might still win the race, right?
s
Oh, I misunderstood the requirement.
j
Race could be used in combination with something like
keepRetryingSourceAUntilItYieldsARight(maxRetries)
And same for sourceB and sourceC. The raceN would probably return the first one that yields a right.
But not sure if this is a good fit for the system that’s being build.
s
It'd be possible to write such a combinator from composition with other primitives but you need to do some state management I believe for the case where all result in
Left
. This is a snippet I whipped together and should be thoroughly tested but basically, it just keeps track of how many task have finished and the first
Left
it encounters. If all task run out without a
Right
winning the race than the first encountered
Left
will be returned rather than the last which marked the race as finsihed with all
Left
. The first
Right
wins the race. This is achieved by making
Left
suspend and thus not allowing them to complete the race
Copy code
suspend fun <E, A> firstRight(fa: suspend () -> Either<E, A>, fb: suspend () -> Either<E, A>): Either<E, A> {
  var firstLeft: Atomic<Either<E, A>?> = Atomic(null)
  var finished: Atomic = Atomic(0)

  suspend fun Either<E, A>.suspendOnLeft(): Either<E, A> =
    when(this) {
       is Right -> this
       is Left -> when {
         finished.get() == 2 -> firstLeft.get()!!
         else -> {
           firstLeft.update { current -> if(current == null) this else current }
           finished.update(Int:inc)
           suspendCoroutine<A> { }
       }
    }

  return race({ fa().suspendOnLeft() }, { fb().suspendOnLeft() }) }
}
Copy code
if(firstLeft.get() != null) finished.update(Int::inc) else firstLeft.update { this }
This line needs to be rewritten to use
modify
on
Atomic
instead since this is not atomically sound
I updated the snippet
I think such an operator might be eligible for being added in Arrow Fx since working with
suspend
and
Either
is an important focus of Arrow
🤞 1
👍 1