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 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 <> or omitted effect type).
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 |