Effect Handlers (OCaml 5)
Effect handlers generalize exceptions: a handler can resume the computation that performed the effect.
Effect handlers generalize exceptions: a handler can resume the computation that performed the effect. They power lightweight concurrency (Eio), generators, async/await, dependency injection, and more.
35.1 Declaring and performing effects
open Effect
open Effect.Deep
type _ Effect.t += Ask : string -> int Effect.t (* effect returning int *)
type _ Effect.t += Log : string -> unit Effect.t
let program () =
perform (Log "starting");
let x = perform (Ask "x") in
let y = perform (Ask "y") in
perform (Log (Printf.sprintf "sum = %d" (x + y)));
x + y35.2 Handling effects (OCaml 5.3+ syntax)
let run () =
match program () with
| v -> Printf.printf "Result: %d\n" v
| effect (Ask name), k -> continue k (String.length name * 10)
| effect (Log msg), k -> print_endline ("[log] " ^ msg); continue k ()35.3 Handling effects (OCaml 5.0–5.2 record syntax)
Also try_with (no return-value handler) and Effect.Shallow for shallow handlers.
let run () =
match_with program ()
{ retc = (fun v -> Printf.printf "Result: %d\n" v);
exnc = raise;
effc = (fun (type a) (eff : a Effect.t) ->
match eff with
| Ask name -> Some (fun (k : (a, _) continuation) -> continue k (String.length name * 10))
| Log msg -> Some (fun k -> print_endline ("[log] " ^ msg); continue k ())
| _ -> None) }35.4 Continuations
continue k v — resume with value v.
discontinue k exn — resume by raising exn at the perform site.
Continuations are one-shot: resuming twice raises Continuation_already_resumed.
Unhandled effects raise Effect.Unhandled.
35.5 Example: generators from iterators
let to_seq (type a) (iter : (a -> unit) -> unit) : a Seq.t =
let module M = struct type _ Effect.t += Yield : a -> unit Effect.t end in
let rec step () =
match iter (fun x -> perform (M.Yield x)) with
| () -> Seq.Nil
| effect (M.Yield x), k -> Seq.Cons (x, fun () -> continue k ())
in
fun () -> step ()
let s = to_seq (fun f -> List.iter f [1; 2; 3])
(* Note: a one-shot Seq — consume it once. *)35.6 Example: cooperative scheduler
type _ Effect.t += Yield : unit Effect.t | Fork : (unit -> unit) -> unit Effect.t
let run main =
let q = Queue.create () in
let next () = if not (Queue.is_empty q) then (Queue.pop q) () in
let rec spawn f =
match f () with
| () -> next ()
| effect Yield, k -> Queue.push (fun () -> continue k ()) q; next ()
| effect (Fork g), k -> Queue.push (fun () -> continue k ()) q; spawn g
in
spawn main
let () = run (fun () ->
perform (Fork (fun () -> for i = 1 to 3 do Printf.printf "A%d " i; perform Yield done));
for i = 1 to 3 do Printf.printf "B%d " i; perform Yield done)
(* interleaved output *)35.7 Effects vs monads
Effects provide direct-style code (no let* everywhere, no function coloring). The type system does not (yet) track which effects a function performs — that is a current limitation; unhandled effects are runtime errors.