What’s with either requiring suspend? What about e...
# arrow
r
What’s with either requiring suspend? What about either.eager? Does either just require suspend so that you can use suspend functions inside of it?
s
😅 This is one of the simplifications made towards 2.0.0 (and 1.2.0-RC), and available on latest
alpha
.
either { }
required
suspend
because it co-operates with Kotlin Coroutines. When it short-circuits on
Either.Left.bind
it needs to cancel the Kotlin Coroutines it's potentially running inside of. To avoid requiring
suspend
when not running inside a Kotlin Coroutine you had to use
either.eager
. This is now solved by working more the
inline
behavior of Kotlin, which automatically works with
suspend
and inlines the same behavior as before into a Kotlin Coroutine when used inside of one.
Besides of having to distinct between
either { }
and
either.eager { }
the new DSL is source compatible with the current one. So only requires changing package.
r
Neat! How does that work with multiple error types, similar to Java’s union types on checked exceptions? I mean, Either<L,R> has only one Left and Either<Either<Error1, Error2>, Result> looks a bit weird. How would you compose a function that can “throw” two different errors A,B inside another function that itself can also “throw” error C and thus would have to return something like Either< A|B|C, Result> ?
s
There are a couple different strategies, depending on how experimental you are or how much of the code you control. Today with Kotlin 1.8.20 and Arrow 1.1.5: If you're in control of `Error1`and
Error2
you can create a common
sealed interface
and since
either
and
Either
are covariant in
E
you can do.
Copy code
sealed class CommonError
object Error1 : CommonError
object Error2: CommonError

val x: Either<Error1, Int> = 1.right()
val y: Either<Error2, Int> = 1.right()

val sum = either.eager {
  x.bind() + y.bind()
}
(Drop eager for
alpha
version of Arrow) If you don't control the errors: • You can apply the same pattern but wrapping the original errors • Resort to
Either<Either<Error1, Error2>, Result>
With context receivers it's quite neat, but sadly those are still only experimental for JVM. In that case you can do something neat.
Copy code
object Error1 : CommonError
object Error2: CommonError

val x: Either<Error1, Int> = 1.right() // fun Raise<Error1>.x(): Int = 1
val y: Either<Error2, Int> = 1.right() // fun Raise<Error1>.y(): Int = 1

context(Raise<Error1>, Raise<Error2>)
fun sum(): Int =
  x.bind() + y.bind() // x() + y()

context(Raise<Error2>)
fun resolveError1(): Int =
  recover({ sum() }) { err: Error1 -> 0 }
If we ever get union types in Kotlin, https://youtrack.jetbrains.com/issue/KT-13108/Denotable-union-and-intersection-types. You could do:
Copy code
val sum: Either<Error1 | Error2, Int> = either {
  x.bind() + y.bind()
}

context(Raise<Error1 | Error2>)
fun sum(): Int = x() + y()
r
Hm, what if one function threw Error1|Error2, but another one Error2|Error3? Could I do
Copy code
sealed interface Error1_2
sealed interface Error2_3
object Error1 : Error1_2
object Error2 : Error1_2, Error2_3
object Error3 : Error2_3

fun a(): Either<Error1_2, String>
fun b(): Either<Error2_3, String>
or what would be a good solution in that case?
s
https://github.com/nomisRev/ktor-arrow-example/blob/main/src/main/kotlin/io/github/nomisrev/DomainError.kt Here I have a practical example where I have a parent
DomainError
, and some smaller
sealed interface
that inherit from the parent. The errors of a layer form a
sealed interface
and all the layers inherit from the top-most parent. That allows all errors to be combined conveniently as a travel through the layers.
r
That looks okay! Why is the last one a UserError though? Shouldn’t it be an ArticleError or be higher up with the other UserErrors?
s
Ah, I should probably clean that up. I need to remove
Unexpected
all together from this hierachy.
It should either be
DomainError
or be removed all together. It's something I've used here to not throw any exceptions and then map to
500
but it should probably just stay an exception instead. More details in this discussion: https://kotlinlang.slack.com/archives/C5UPMM0A0/p1678805582480059?thread_ts=1678801512.268339&amp;cid=C5UPMM0A0
r
Thank you a lot for all the insights, @simon.vergauwen, I’ll see if I can use Either as a checked exception replacement.
s
My pleasure ☺️ If you have any more questions, or doubts be sure to ask them here 👍