⚡ Algebraic Effects and Handlers
First-class computational effects with composable handlers
Overview
Algebraic effects are one of the most exciting developments in programming language theory. They separate the declaration of an effect (what can happen) from its interpretation (what it means), using handlers that intercept effects and can resume the suspended computation.
This module simulates algebraic effects on any OCaml version using a free monad / CPS encoding. OCaml 5 adds native effect handlers, but the concepts here apply universally.
Concepts Demonstrated
- Algebraic effects — operations declared as effect types, handled externally
- Delimited continuations — CPS-based capture and resumption
- Free monads — encoding effects as an AST of operations
- Handler composition — stack multiple independent effect handlers
- Effect polymorphism — write code generic over effect interpretation
Effect Patterns
| Effect | Operations | Handler Provides |
|---|---|---|
| State | Get, Put | Thread mutable state through pure code |
| Exception | Raise | Error handling without unwinding |
| Nondeterminism | Choose, Fail | Backtracking search, collect all results |
| Logging | Log | Collect messages, redirect output |
| Async | Await, Fork | Cooperative concurrency via scheduling |
How It Works
(* Effects are declared as operations *)
type _ effect =
| Get : int effect (* read state *)
| Put : int -> unit effect (* write state *)
(* Code "performs" effects — doesn't know the implementation *)
let increment () =
let x = perform Get in
perform (Put (x + 1));
x + 1
(* A handler gives meaning to effects *)
let run_state init computation =
handle computation () with
| Get, k -> fun s -> continue k s s
| Put s, k -> fun _ -> continue k () s
| return v -> fun _ -> v
|> fun f -> f init
(* Same code, different handler = different semantics *)
(* Handler for logging all state changes: *)
let run_logged computation =
handle computation () with
| Put s, k -> printf "State → %d\n" s; continue k ()
| effect -> forward effect (* pass other effects through *)
Why Algebraic Effects?
- Separation of concerns: Code declares what it needs; handlers decide how
- Composability: Stack handlers independently (state + exceptions + logging)
- Testability: Swap handlers to mock I/O, state, randomness in tests
- No monad transformers: Avoids the "monad transformer stack" complexity
- Resumable exceptions: Handlers can resume after handling an effect
Related Modules
- Monads — the classic approach to structuring effects
- Software Transactional Memory — concurrency via transactions
- Actors — message-passing concurrency