Algebraic effects and handlers
AdvancedAlgebraic effects are the most exciting development in effect systems
since monads. They give you the separation that free monads do (programs
describe what they want; handlers decide what happens) without monad
plumbing, and they compose linearly — adding an effect doesn't change
the type of unrelated code. They're native in Koka, Eff, Unison,
and OCaml 5; Scala has them via cats-effect-style libraries and the
Effekt language; WebAssembly is shipping them.
The shape
You declare an effect by listing its operations:
effect ask
fun ask() : int
Code using the effect calls operations like any function:
fun double-asked() : ask int
ask() * 2
Note the type — ask int means "produces an int, may perform ask." Then
a handler discharges the effect by saying what ask should do:
fun main()
with handler
fun ask() resume(42)
double-asked() // = 84
The handler resumes the computation with 42. That's the magic word.
The continuation
When a program performs ask(), the runtime captures the continuation
— "the rest of the program after this call" — and hands it to the handler
as resume. The handler can:
- Call
resume(x)once → ordinary effect, like returningx. - Call
resume(x)multiple times → backtracking, search, generators. - Not call
resumeat all → exceptions, early exit.
This single primitive (delimited continuations as effect handlers) subsumes exceptions, generators, async/await, state, nondeterminism, mock IO, and dependency injection — everything in the monad-transformer zoo.
Compared to monad transformers
| Monad transformers | Algebraic effects |
|---|---|
| Stack order matters | Effects are unordered (a set of capabilities) |
Each effect needs lift plumbing | Calls look like ordinary functions |
| Adding an effect changes types of every caller | Adding an effect adds one row to the effect row |
mtl-style requires typeclass per effect | Effects are first-class |
| n × m instance problem | Linear: one operation, one handler |
Compared to free monads
Free monads have the same "describe then interpret" split, but the program is a data structure you walk. Algebraic effects skip the intermediate tree — the runtime captures continuations directly. Theoretically equivalent; in practice faster and lower-syntax.
A pseudo-implementation in Clojure
Clojure doesn't have native algebraic effects, but you can fake the shape with dynamic vars as handlers:
That's the static-resume-once case. True algebraic effects also support
multi-shot and zero-shot resumption, which dynamic vars can't do — you'd
need full delimited continuations (Clojure has them via cloroutine and
similar libraries, with limitations).
Multi-shot example: nondeterminism
A handler that resumes twice implements nondeterministic choice:
effect choose
fun choose<a>(xs: list<a>) : a
fun pair() : choose (int, int)
(choose([1,2]), choose([10,20]))
fun all-results()
with handler
fun choose(xs)
flatmap(xs, fn(x) resume(x)) // resume once per option
pair()
// = [(1,10), (1,20), (2,10), (2,20)]
The body pair() is written as if there were one answer; the handler
expands it into the list of all combinations. This is the same trick the
list monad plays, but the user code is straight-line.
Why this is the future
- Linear composition. Effect rows add; they don't nest. Library code stays maximally polymorphic.
- Effects are decoupled from data structures. The same
Consoleeffect can be discharged to real IO, to a mock, to a queue, to a recording — all without changing the program. - Async/await falls out for free. Suspension is just "the handler holds the continuation."
- Mainstream adoption is here. OCaml 5's runtime, .NET's "experimental effect handlers," WebAssembly's stack-switching proposal.
Check yourself
? quiz
What's the key primitive that algebraic-effect handlers expose, that lets them subsume both exceptions and generators?
Exercise
Write down (in any pseudo-syntax) a state effect with operations get : () → s and set : s → (), and a handler that threads the state value
through the computation. Show that calling (get, set 1, get) evaluates to
something like (s₀, (), 1).