1. Effect Handlers
Effect handlers provide a type-safe, modular way to reason about effects in your code. They let you work with state and exceptions in a purely functional (immutable) way — the state changes are observable, but only accessible within a certain scope.
However, effect handlers are much more powerful than the most common examples. In a language with effect handlers, you can define the scheduling and logic behind async/await, cooperative coroutines, generators, and iterators — as ordinary library code, not special language features.
1.1. A Simple Example
The following Koka program defines an err effect and handles it:
effect err
ctl err(message: string): a
fun main()
with final ctl err(x) println("Error: " ++ x)
err("something went wrong")
The with handler installs a handler for err; final means the handler
does not resume after handling the effect.
1.2. Effects as Types
Koka tracks effects in the type system. A function that throws err has type:
fun risky() : <err> int
A function that handles all effects is typed as total (or just pure).
This means the compiler can statically guarantee that all effects are handled.
1.3. Why This Matters
Effect handlers unify many control-flow abstractions under one mechanism:
| Pattern | As an effect handler |
|---|---|
| Exceptions | ctl throw: non-resumable |
| State | fun get, fun set: resumable |
| Async/Await | Resumable continuation scheduling |
| Generators | ctl yield: resume after each value |
| Backtracking | Multiple resumptions |