LeanEff
LeanEff is a small Lean 4 extensible-effects library inspired by “Freer Monads, More Extensible Effects,” and shaped for practical Lean programming.
The intended user-facing style is concrete effect rows:
import LeanEff
open LeanEff
def program : Eff [Reader Nat, Writer String] Nat := do
let n ← ask
tell s!"n = {n}"
pure (n + 1)
#eval program
|> runReader 41
|> runWriter
|> run
Effect rows are treated as set-like lists for handler lookup. A handler removes its effect wherever it appears and preserves the remaining row order; the order in which handlers are called controls effect interaction.
Included Effects
Reader ρ:ask,runReaderWriter ω:tell,runWriterState σ:get,put,modify,runState,evalState,execStateExceptE ε:throw,tryCatch,runExceptRandom:randNat,randBool,runRandom,evalRandomLiftIO:liftIO,runLiftIOClock τ:now,runClockAtConsole:printLine,readLine,runConsoleIODisplay frame:drawFrame,runDisplayIOInput ι:pollInputSleep:sleepMs,runSleepIO
Debug Snapshots
recordSnapshot records request/response effect interactions into a
Snapshot. A snapshot can later replay the same responses with
replaySnapshot, run a deterministic check with checkSnapshot, or be compared
against another run with compareSnapshots. Use Snapshot.toJsonString and
Snapshot.fromJsonString to persist traces as JSON.
def recorded :=
program
|> recordSnapshot
|> runReader 41
|> runWriter (ω := String)
|> runWriter (ω := SnapshotEvent)
|> run
def replayed :=
program
|> replaySnapshot recorded.2
def checked :=
program
|> checkSnapshot recorded.2
def divergence :=
compareSnapshots recorded.2 anotherSnapshot
Effects opt into snapshots through SnapshotCodec, which encodes effect
requests and responses as structured Lean.Json values. LeanEff includes
codecs for the built-in resumable effects.
Snapshots are request/response traces, so an effect operation that never resumes
does not produce an event through this middleware. During a check, recorded
responses drive input-like effects such as Random and Input, while
output-like effect requests such as Display.draw frames must match the
recorded snapshot. SnapshotCodec.checkMode controls whether a request replays
its recorded response or asserts the generated request.
Examples
Examples.AsciiTetris: an animated terminal Tetris clone with nonblocking controls, ANSI colors, and a next-piece preview. It usesReaderfor configuration,Statefor the board,Writerfor game events,ExceptEfor quit/game-over exits,Randomfor piece generation,Display Stringfor drawing,Input Commandfor controls, andSleepfor frame pacing.
Run it with:
lake exe ascii_tetris
Record a JSON snapshot for deterministic debugging with:
lake exe ascii_tetris --record trace.json
Animate a JSON snapshot by replaying the recorded keys with:
lake exe ascii_tetris --replay trace.json
Strictly verify a JSON snapshot without animation with:
lake exe ascii_tetris --check trace.json
Run it in a real terminal because it temporarily switches terminal input mode.
Examples.VideoRental: a small "Palladin's Video Rental" shop. The business logic usesClockfor the current rental day,RentalRepofor customers, movies, and rentals, andWriter Stringas a notification outbox. The demo interpreter backs the repository with an initialized in-memory SQLite database throughleanprover/leansqlite, then runs an interactive menu until quit.
Run it with:
lake exe video_rental_shop
Examples.AgentSnake: a concurrent multi-agent snake arena. The arena uses anAgentHosteffect to spawn AI agents, publish world snapshots, and gather agent moves. Each AI snake is an independentAgentEffprogram that observes snapshots and chooses moves.Randomdrives agent jitter and food placement, snakes render as colored*cells, and the same effects can run with either a threaded host interpreter or a single-threaded cooperative interpreter.
Run it with:
lake exe agent_snake_arena
Use the single-threaded cooperative interpreter with:
lake exe agent_snake_arena --cooperative
Record a JSON snapshot of the arena with:
lake exe agent_snake_arena --record trace.json
Replay the recorded snake animation with:
lake exe agent_snake_arena --replay trace.json
Strictly verify the snapshot without animation with:
lake exe agent_snake_arena --check trace.json
V1 Notes
- Duplicate effect labels are unsupported. Wrap labels in distinct types if two effects have the same payload type.
- The core uses a Lean-friendly mutual
Eff/Arrscontinuation queue with aViewLoperation, so continuations are consumed from the front instead of repeatedly walking a left spine. - Recursive handlers are executable
partial defs; v1 handlers therefore require inhabited result types.