Document coroutines codegen: simple lambda example

This commit is contained in:
Ilmir Usmanov
2020-08-13 20:24:25 +02:00
committed by Ilmir Usmanov
parent 25edda02f4
commit 5fd08b3ee5
@@ -31,3 +31,151 @@ may vary from version to version, from back-end to back-end, and one should use
Lastly, most of the examples presented in the document actually suspend, so one is sure every piece is in place since coroutines is a broad
and complex topic, and it is easy to forget one piece, which will lead to a runtime error or even worse, semantically wrong code execution.
## Suspend Lambda
Let us begin by introducing suspend lambdas.
Suspend lambda is an example of a coroutine, and the compiler turns ordinary, sequential code inside the lambda into suspendable.
The example shows a simple suspend lambda:
```kotlin
suspend fun dummy() {}
suspend fun main() {
val lambda: suspend () -> Unit = {
dummy()
println(1)
dummy()
println(2)
}
lambda()
}
```
which, upon running, will print `1` and `2`, as expected.
One can call a suspend function only from other suspend function or suspend lambda, but it can call ordinary, non-suspendable functions. For example, both `dummy` and `println` are used only inside the lambda. Because one is not allowed to call suspendable functions from ordinary, we can imagine two worlds: suspendable and ordinary. Alternatively, one can consider them as being of two different colors, and we color the program by using the "suspend" modifier.
The lambda, creatively named `lambda`, contains two suspend calls (`dummy`) and one from the `main` function to the lambda itself,
but there is no suspension. Let us add it:
```kotlin
import kotlin.coroutines.*
var c: Continuation<Unit>? = null
suspend fun suspendMe() = suspendCoroutine<Unit> { continuation ->
println("Suspended")
c = continuation
}
suspend fun main() {
val lambda: suspend () -> Unit = {
suspendMe()
println(1)
suspendMe()
println(2)
}
lambda()
}
```
Now, when we run the code, it prints `Suspended` and nothing else; it does not even finish the execution of the program, since `lambda` is, in fact, suspended, and it suspends `suspend fun main` as well.
To fix the issue with the suspension of `main`, we need to cross a boundary between suspendable and ordinary worlds and make
`main` ordinary, so, when it starts a coroutine, and the coroutine suspends, `main` does not. Since one cannot call a suspendable
function from an ordinary one, there are special functions, so-called coroutine builders, whose sole purpose is to create a coroutine, run it, and when it suspends, return execution to the caller.
Other than that, they act like other ordinary functions.
Let's name ours, I don't know, `builder`:
```kotlin
fun builder(c: suspend () -> Unit) {
c.startCoroutine(object: Continuation<Unit> {
override val context = EmptyCoroutineContext
override fun resumeWith(result: Result<Unit>) {
result.getOrThrow()
}
})
}
```
A separate section explains the exact mechanism of starting a coroutine (in a broad sense) and how one can write their builders. For now, consider `builder` as a boilerplate to cross the worlds.
Now, when we change `main` to use the builder and not suspend itself
```kotlin
fun main() {
val lambda: suspend () -> Unit = {
suspendMe()
println(1)
suspendMe()
println(2)
}
builder {
lambda()
}
}
```
and then run the example, it will print expected `Suspended`, but this time it will exit the program.
Additionally, when we change `main` to resume the lambda
```kotlin
import kotlin.coroutines.*
var c: Continuation<Unit>? = null
suspend fun suspendMe() = suspendCoroutine<Unit> { continuation ->
println("Suspended")
c = continuation
}
fun builder(c: suspend () -> Unit) {
c.startCoroutine(object: Continuation<Unit> {
override val context = EmptyCoroutineContext
override fun resumeWith(result: Result<Unit>) {
result.getOrThrow()
}
})
}
fun main() {
val lambda: suspend () -> Unit = {
suspendMe()
println(1)
suspendMe()
println(2)
}
builder {
lambda()
}
c?.resume(Unit)
}
```
it will print
```text
Suspended
1
Suspended
```
`lambda` is resumed and then suspended once more. If we add a couple more `c?.resume(Unit)`
```kotlin
fun main() {
val lambda: suspend () -> Unit = {
suspendMe()
println(1)
suspendMe()
println(2)
}
builder {
lambda()
}
c?.resume(Unit)
c?.resume(Unit)
c?.resume(Unit)
}
```
we will get
```text
Suspended
1
Suspended
2
Exception in thread "main" java.lang.IllegalStateException: Already resumed
```
The last line is what we get when we try to resume a finished continuation.
In this little example happens a lot. The rest of the section explains it bit by bit, starting with a state-machine.