Kristian Nedrevold
01/10/2023, 11:24 AMraulraja
01/10/2023, 11:57 AMLogger
and has some suspend
capabilities to log local or remote.
error
can't be called unless you are in a suspend context so in a way it's already protected for async and IO similar as to how you may encode this on a lang like monads such as Haskell. IO a
here is represented as suspend() -> A
with the same guarantees.raulraja
01/10/2023, 11:58 AMLogger
to a dependency of your function.
context(Logger)
suspend fun foo(): Unit =
error("bar")
raulraja
01/10/2023, 11:59 AMdnowak
01/10/2023, 12:08 PMinterface Logger
or `context(Logger)`` . Assuming that logging may be needed anywhere in the code than I will end up with one of:
• all logic will be implemented as extension functions of Logger
• all functions will require context(Logger)
dnowak
01/10/2023, 12:10 PMKristian Nedrevold
01/10/2023, 12:26 PMdnowak
01/10/2023, 12:36 PMCLOVIS
01/10/2023, 12:42 PMlaunch
.Kristian Nedrevold
01/10/2023, 1:13 PMdnowak
01/10/2023, 1:15 PMKristian Nedrevold
01/10/2023, 1:18 PMKristian Nedrevold
01/10/2023, 1:25 PMcarbaj0
01/10/2023, 1:31 PMinterface Logger {
fun error(msg: String)
}
data class CoroutineLogger(
val logger: Logger
) : AbstractCoroutineContextElement(CoroutineLogger) {
companion object Key : CoroutineContext.Key<CoroutineLogger>
}
fun CoroutineScope.log(msg: String) {
coroutineContext[CoroutineLogger]?.logger?.error(msg)
}
suspend fun main() {
val logger = object : Logger {
override fun error(msg: String) {
println(msg)
}
}
val ctx = CoroutineScope(CoroutineLogger(logger))
ctx.launch {
log("Implicit logger")
}
delay(100)
}
raulraja
01/10/2023, 3:44 PMI am using context receivers for it. So wrapping any side-effect in suspend is the same as using IO in other FP languages?Yes, it's effectively the same to have a
IO<A>
as suspend () -> A
both are functions that wrap a potentially async, blocking, cancelling... computation from being evaluated until you run your coroutines at the edge.
In fact suspend () -> A
is more general than IO a
and it includes also IO a
. It's more general because suspend
models continuations that can fail with Result<A>
which includes Throwable
on the left, but suspend can also be seen as polymorphic and can be evaluated into any target F[A]
. We see this in Arrow where Effect
is folded into Either or whatever terminal value you want your suspended program.
Unlike in those languages where IO is modeled as a data type in Kotlin suspend
is modeled as a function type which you can use as a simple function modifiers. Suspend covers same laws and evaluation / cancellation semantics of IO.
Continuations can model a lot more than just IO, they can model all monads https://www.schoolofhaskell.com/school/to-infinity-and-beyond/pick-of-the-week/the-mother-of-all-monads
If you squint they provide non blocking F[A] => A
for all F
. If you are aware of scala or haskell that operation is Comonad extract.
Continuations embedded in Kotlin as suspend are dual and can model any monad bind operation F[A] => A => F[B]
just as F[A] => A
Continuations control flow and embedding in kotlin removes the need for map
and flatMap
in all boxed monads or types that emit a single value such as Option, Either and many others.raulraja
01/10/2023, 3:47 PMcontext(Logger)
class Repository
vs
class Repository {
val logger = Logger.getInstance
}
raulraja
01/10/2023, 3:47 PMraulraja
01/10/2023, 3:49 PMKristian Nedrevold
01/10/2023, 7:39 PM