Reputation: 43
Arrow.kt docs point out that instead of:
fun get(): Either<Err, Res>
we can use
context(Raise<Err>)
fun get(): Res
And we can even have multiple context receivers.
Imagine we have two error types
class Err1
class Err2
And a function that has two context receivers of those errors
context(Raise<Err1>, Raise<Err2>)
private fun func(a: Int): Int {
return when(a) {
0 -> raise(Err1())
1 -> raise(Err2())
else -> a
}
}
How this function can be elegantly called to handle many or all errors without deep nesting?
Only ugly nesting way comes to my mind:
recover<Err1, Unit>({
recover<Err2, Unit>({
val res = func(1)
//happy pass
}) { err2 -> /* error 1 */ }
}){ err1 -> /* error 2 */ }
I can remove nesting by creating separate functions and recovering only once per function but that arguably would be even worse.
P.S.
Arrow.kt with context receivers awfully reminds me how checked exceptions work in java. Except that your error is not required to inherit Throwable
and the fact that all exceptions can be caught much cleaner.
That's a bit funny that we came a full circle from Java checked exceptions to Kotlin's no checked exceptions to Arrow.kt way to 'emulate' checked exceptions with context receivers :)
Upvotes: 1
Views: 931
Reputation: 1
As Agusto mentioned, try using "sealed interfaces".
So:
sealed interface Err {
class Err1 : Err
class Err2 : Err
}
context(Raise<Err>)
private fun func(a: Int): Int {
return when (a) {
0 -> raise(Err.Err1())
1 -> raise(Err.Err2())
else -> a
}
}
And:
recover<Err, Unit>({
val res = func(1)
//happy pass
}) { err ->
when (err) {
is Err.Err1 -> /* error 1 */
is Err.Err2 -> /* error 2 */
}
}
Personally, for "func", I prefer:
context(Raise<Err>)
private fun func(a: Int): Int {
ensure(a != 0) { Err.Err1() }
ensure(a != 1) { Err.Err2() }
return a
}
Upvotes: 0