Effects System Guide

This guide explains Turmeric's algebraic effects system and when to use it.

Overview

Turmeric implements algebraic effect handlers inspired by OCaml 5, enabling ergonomic asynchronous programming, custom control flow, and composable abstractions. Effects allow you to perform operations that handlers can intercept and re-route dynamically.

Core Concepts

Effects, Performs, and Handlers

Three core primitives:

;; Declare an effect
type _ Effect.t += Read : string Effect.t

;; Perform the effect
(def name (perform Read))

;; Handle the effect
(try-with
  (fn []
    (let [name (perform Read)]
      (println "Hello " name)))
  (fn [e k]
    (match e
      Read -> (continue k "World"))))

One-Shot Continuations

Continuations in Turmeric are one-shot: calling continue k v consumes k, preventing reuse. This matches Turmeric's ownership model but means no backtracking in v1. (See Logic Programming Guide for cloneable continuations in v2.)

Properties

Common Use Cases

Direct-Style Async

Effects enable ergonomic async/await (see Async/Await Guide):

(async
  (await (read-file "data.txt"))
  (println "done"))

Custom Control Flow

Implement generators, early returns, or custom exception handling:

;; Generator: yield values one at a time
type _ Effect.t += Yield : a Effect.t

(try-with
  (fn []
    (perform (Yield 1))
    (perform (Yield 2)))
  (fn [e k]
    (match e
      (Yield v) -> (println v) (continue k))))

Dependency Injection

Mock I/O operations in tests:

type _ Effect.t += ReadFile : string -> bytes Effect.t

;; Production
(try-with code
  (fn [e k]
    (match e
      (ReadFile path) -> 
        (continue k (read-file-real path)))))

;; Tests
(try-with code
  (fn [e k]
    (match e
      (ReadFile path) -> 
        (continue k "mock data"))))

Transactional Retry

Automatic conflict resolution (see STM Tutorial):

type _ Effect.t += Retry : never Effect.t

(try-with
  (fn []
    (when (< (read-tvar x) 10)
      (perform Retry)))
  (fn [e k]
    ;; Re-run transaction on conflict
    (match e
      Retry -> (continue k))))

Effect Rows (Typed Effects)

A v2 extension puts effects in the type system via effect rows — sets of effects a function may perform:

val read_file : path -> string @ (ReadFile)
val pure_fn : int -> int @ ()
val map : ('a -> 'b @ 'e) -> 'a list -> 'b list @ 'e

Benefits: - Polymorphismmap propagates caller's effects through the mapped function. - Compile-time checking — Verify effect-free code stays pure; catch unhandled effects early. - Optimization — Pure code (@ ()) can be aggressively refactored.

Status: Planned for v3 of effects design; requires elaborator support and type-system changes.

Integration with Ownership and Defer

Effects interact with Turmeric's defer mechanism (§5, §4 of turmeric-plan.md):

See Also