ExamplesBy LevelBy TopicLearning Paths
189 Expert

Introduction to Algebraic Effects

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "Introduction to Algebraic Effects" functional Rust example. Difficulty level: Expert. Key concepts covered: Functional Programming. Algebraic effects are a modern alternative to monads for structuring side effects. Key difference from OCaml: 1. **Native vs. simulated**: OCaml 5 has native effect handlers with efficient stack manipulation; Rust requires explicit continuation

Tutorial

The Problem

Algebraic effects are a modern alternative to monads for structuring side effects. Unlike free monads (which build heap-allocated trees), algebraic effects are handled at the runtime level — the effect is "thrown" and a handler "catches" it, potentially resuming the computation. OCaml 5 introduced native effect handlers. Rust simulates them using closures and callbacks. Effects are used in async runtimes (every await is an effect), error handling, non-determinism, and resource management.

🎯 Learning Outcomes

  • • Understand algebraic effects as a mechanism for suspending and resuming computations
  • • Learn how effects differ from free monads: effects are operational, free monads are denotational
  • • See a simple simulation using Rust's closures and callback-based resumption
  • • Understand why algebraic effects require native language support for full efficiency
  • Code Example

    #![allow(clippy::all)]
    // Stub — awaiting conversion from OCaml source.

    Key Differences

  • Native vs. simulated: OCaml 5 has native effect handlers with efficient stack manipulation; Rust requires explicit continuation-passing simulation.
  • Stack vs. heap: OCaml's effect continuations are captured stack frames — efficient; Rust's simulation uses heap-allocated closures.
  • Multi-shot continuations: OCaml's continue k can call k multiple times (non-determinism); Rust's FnOnce callbacks can only be called once.
  • Composability: OCaml effects compose naturally via nested handlers; Rust simulations require manual composition.
  • OCaml Approach

    OCaml 5's native effect handlers:

    effect Read : string
    let with_reader env program =
      match_with program ()
        { retc = (fun v -> v)
        ; exnc = raise
        ; effc = fun (type a) (eff : a eff) ->
            match eff with
            | Read -> Some (fun (k : (a, _) continuation) ->
                continue k env)
            | _ -> None }
    

    The handler intercepts Read effects, provides a value, and resumes the computation via continue k env. This is purely functional with no heap-allocated trees — the runtime manages the stack frames.

    Full Source

    #![allow(clippy::all)]
    // Stub — awaiting conversion from OCaml source.

    Deep Comparison

    OCaml vs Rust: Effect Intro

    Overview

    See the example.rs and example.ml files for detailed implementations.

    Key Differences

    AspectOCamlRust
    Type systemHindley-MilnerOwnership + traits
    MemoryGCZero-cost abstractions
    MutabilityExplicit refmut keyword
    Error handlingOption/ResultResult<T, E>

    See README.md for detailed comparison.

    Exercises

  • Implement a with_logger handler that intercepts Log(message) effects and stores them in a Vec<String>.
  • Simulate non-determinism: an Amb(choices: Vec<A>) effect that a handler interprets by trying all choices.
  • Compare the performance of a free monad interpreter vs. the effect simulation for a program with 10,000 operations.
  • Open Source Repos