Jörg Winter
03/16/2021, 2:15 PMsimon.vergauwen
03/17/2021, 8:13 AMsuspend fun example(): Either<String, Int> =
either {
val a = Right(1).bind()
a
}
Then the answer is no. Since this is 100% agnostic of any coroutine builder. This is true for all suspend
code you find in Arrow Core.
If you're using Arrow Fx that 100% interopts with KotlinX Coroutines out-of-the-box since 0.12.0
there will be no need to pay attention to which coroutine builders you're using anymore. There'll also be no need more anymore to use interopt-libraries from Arrow 🙂simon.vergauwen
03/17/2021, 8:13 AMJörg Winter
03/17/2021, 8:17 PM...And recovering from errors is safe inI am more thinking about wrapping some database- or http-request with Either, or just using the monad computations. So you're saying that async exception handling is another benefit... ok that's fine, but other than that and monadic computation-blocks, there is no 'runtime stuff' ' that is hadled by arrow in the background, right ? And also you wrote:even though the code can be async. If you're doing this manually in Java for example you need manually wire exception handling accross async boundaries, here that is done automatically for us through thesuspend
Continuation
...basically every suspend function results inI don't get what is meant by "takes that into account". The exceptions are still handled, if handled at all, by the calling code, right ?orA
, that is actually always true in Kotlin since we don't have checked exceptions. HoweverThrowable
always takes that into account.suspend
simon.vergauwen
03/18/2021, 7:32 AMok that's fine, but other than that and monadic computation-blocks, there is no 'runtime stuff' ' that is hadled by arrow in the background, right ?I'm not 100% sure what you mean by 'runtime stuff'. Computation blocks have their own implementation for running on a
Continuation
. So it has its own 'runtime', or implementation. This is the 'runtime' used for `either { }`, `nullable { }`, etc.
These implementations have been worked out in such a way that they automatically integrate with other "runtimes". For example either { }
works together with KotlinX Coroutines runtime without having a dependency on it. Here is a test that verifies that short-circuiting in `either { }` cancels structured concurrency correctly that is running inside the `either { }` block.
I don't get what is meant by "takes that into account". The exceptions are still handled, if handled at all, by the calling code, right ?
suspend
explicitly models that it will result in Throwable
or A
, similar to IO
.
The exceptions are still handled, if handled at all, by the calling code, right ?If I understand you correctly, this is a misconception 😄 Let's take
suspend () -> Int
and all we got in the standard library. The only way we can run it is:
val f: suspend () -> Int = { 5 }
f.startCoroutine(Continuation(EmptyCoroutineContext) { res: Result<Int> ->
res.fold({ i: Int -> println(i) }, { e: Throwable -> println(e) })
})
So a suspend
function can "only" be run by handling both Int
and Throwable
.
However, KotlinX Coroutines hides this from the user and re-throws unhandled exceptions instead.
So depending on which runner you use to run a suspend
function and an unhandled exception are rethrown.simon.vergauwen
03/18/2021, 7:41 AMI am more thinking about wrapping some database- or http-request with Either, or just using the monad computations.However, that doesn't mean using
Either
in combination with suspend
doesn't offer a better way to offer your error domain! The power of Arrow and suspend
in Kotlin is that it can so efficiently mix effects, and you can combine suspend
with either { }
without any costs.
In contrast to IO
with Either
(or rather EitherT
) which is very user-unfriendly, and performance very costly on the JVM due to all the nesting and wrapping.
So I would still advise you to do something like (warning pseudo-code :p):
object MyErrorDomain // Some ADT that models some errors
object User // Some data class
suspend fun DbImpl.fetchUser(id: Int): Either<MyDbError, User> =
either {
val queryRes = !Either.catch { query(...) }
val data = !validated(queryRes)
!data.toUser()
}