← Back to lessons

53 Effect Handlers - Resumption

Hard

So far, examples we have shown on the previous slide have resumed immediately after handling the effect. However, we do have the option for resuming outside the handler scope. Take into account that handlers of this type are slower, and you should write handlers without resumption argument unless needed.

Effect handlers implementation relies on stack switching and dynamic binding.

Resumption<A, B> type saves the state of computation paused by perform, and it is passed to resume to jump back to the line with perform (with switching stacks) First parameter represents the type of an object passed to resume (resume with), while the second parameter represent the return type of resumption. For better understanding you can see the code.

Regarding deferred resumption, in case you just write resume in the handle block the compiler automatically fills the code, but if you want to resume the effect from outside you need to store the Resumption object, like shown in the code. It also can only be assigned to an immutable variable, therefore you can use Box to move the object out from the scope.

Eff() is performed at the line 43. The handler increments cnt, saves resumption state, and returns “handle block. cnt = 1” string that’s stored in val.

Will jump to and execute lines 43 and 44 and will then jump to handler and return the string “handle block. cnt = 2”.

The confusion may arise here when we call the handler for the second time. As it does not contain resume statement you may think it should continue executing lines 56 and 58 like before, but that’s not true. Let’s explain what happens in detail:

First, when we reach try-handle clause a new stack is created and execution is switched to this stack. When Eff() is performed for the first time and handler finishes, what happens is that we return to the main stack and that’s why lines 55, 56 and 59 get executed. Then, as we resume the effect, execution will switch stacks again. After Eff is performed at the line 45 and handler finishes, the stack gets switched back to the main one, which was stopped at the line 59. So we proceed from here.

For more application of Effect Handlers like dependency injection, custom concurrency, and memoization, visit this github link

You’re also encouraged to watch this conference video about effect handlers in Cangjie, that also contains deep refactoring example at the end.

resumption.cj
import stdx.effect.*

class Luka <: Command<String> {}
class Alin <: Command<Unit> {}
class Eff <: Command<Unit> {}

main() {
    // Deferred Resumptions
    let res: Box<?Resumption<String, Unit>> = Box(None)
    try {
        println(0)
        println(perform Luka())
        println(3)
    } handle (_: Luka, next: Resumption<String, Unit>) {
        //                               |      - return type of resumption
        //                               - expected type to resume with

        // res: Resumption<A, B>
        // (resume next with <A>) : B
        println(1)
        res.value = Some(next)
    }

    println(2)
    resume res.value.getOrThrow() with "resumed from outside"
    println(4)

    // Resumption with return type
    let res2: Box<?Resumption<Unit, String>> = Box(None)
    try {
        perform Alin()
        "try block"
    } handle (_: Alin, next: Resumption<Unit, String>) {
        res2.value = Some(next)
        "handle block"
    }

    println(resume res2.value.getOrThrow())

    let res3: Box<?Resumption<Unit, String>> = Box(None)
    let cnt: Box<Int64> = Box(0)
    let val = try {
        perform Eff() // Line 43
        println("returned back to first perform")
        perform Eff() // Line 45
        println("returned back to the secoond perform")
        "try block. cnt = ${cnt.value}"
    } handle (_: Eff, next: Resumption<Unit, String>) {
        cnt.value++
        res3.value = Some(next)
        "handle block. cnt = ${cnt.value}"
    }

    
    println(val) // Line 55
    println(cnt) // Line 56


    println(resume res3.value.getOrThrow()) // Line 59

    
    println(val)
    println(cnt)
}

// Output:
// 0
// 1
// 2
// resumed from outside
// 3
// 4
// try block
// handle block. cnt = 1
// 1
// returned back to first perform
// handle block. cnt = 2
// handle block. cnt = 1
// 2