Use this file to discover all available pages before exploring further.
Lean 4 structures effectful computation through a hierarchy of type classes: Functor, Applicative, and Monad. These classes abstract over computational contexts — sequencing, failure, state, environment, and IO — so that the same code pattern works across many different effect types. Understanding this hierarchy lets you write generic effectful code and layer multiple effects cleanly.
Applicative adds pure (inject a value) and <*> (apply a wrapped function to a wrapped argument). Effects from both sides are sequenced.
-- class Applicative (f : ...) extends Functor f where-- pure : α → f α-- seq : f (α → β) → (Unit → f α) → f β -- i.e., <*>#eval pure 42 : Option Nat -- some 42#eval pure 42 : List Nat -- [42]-- <*> applies a function in a context to a value in a context#eval (some (· + 10)) <*> (some 5) -- some 15#eval (none : Option (Nat → Nat)) <*> some 5 -- none
The <* and *> operators sequence effects while discarding one side’s value:
-- x <* y runs x then y, returns x's value-- x *> y runs x then y, returns y's value#eval (some 1) *> (some 2) -- some 2#eval (none : Option Nat) *> (some 2) -- none
Monad adds bind (the >>= operator), which allows the result of one computation to determine the next computation. This is the key power of monads.
-- class Monad (m : ...) extends Applicative m where-- bind : m α → (α → m β) → m β -- i.e., >>=-- Chaining Optionsdef safeSqrt (n : Float) : Option Float := if n < 0 then none else some (Float.sqrt n)#eval (some 16.0) >>= safeSqrt -- some 4.0#eval (some (-1.0)) >>= safeSqrt -- none-- >> sequences and discards left value#eval (some 1) >> (some 2) -- some 2-- do notation is sugar for >>= chainsdef chainExample : Option Nat := do let x ← some 3 let y ← some 4 return x * y -- some 12
Option α represents a computation that may fail without a reason. A none at any point short-circuits the rest of the chain.
def safeDiv (n d : Nat) : Option Nat := if d == 0 then none else some (n / d)def compute (a b c : Nat) : Option Nat := do let x ← safeDiv a b let y ← safeDiv x c return y + 1#eval compute 100 5 2 -- some 11#eval compute 100 0 2 -- none (b = 0)
IO α is the monad for programs with real-world side effects. It is defined as:
-- From Init/System/IO.lean:-- abbrev IO : Type → Type := EIO IO.Error-- def EIO (ε : Type) (α : Type) : Type := EST ε IO.RealWorld α-- def BaseIO (α : Type) := ST IO.RealWorld α
The IO.RealWorld token is a compile-time fiction that threads through every IO action, enforcing sequential evaluation. In practice, you write do blocks and never see IO.RealWorld directly.
def main : IO Unit := do IO.println "Hello from IO!" let line ← IO.getLine IO.println s!"You said: {line.trim}"
ExceptT ε m α adds typed exceptions of type ε on top of monad m.
abbrev AppE α := ExceptT String IO αdef mustBePositive (n : Int) : AppE Nat := do if n ≤ 0 then throw s!"Expected positive, got {n}" return n.toNatdef runAppE : IO Unit := do let result ← (mustBePositive 5).run match result with | Except.ok n => IO.println s!"Got: {n}" | Except.error e => IO.println s!"Error: {e}"
-- All equivalent ways to sequence Option computations:example : Option Nat := some 3 >>= fun x => some (x * 2)example : Option Nat := do let x ← some 3 return x * 2example : Option Nat := (· * 2) <$> some 3
MonadLift m n provides a way to run computations from monad m inside monad n. Lean inserts lifts automatically in do blocks.
-- class MonadLift (m : ...) (n : ...) where-- monadLift : m α → n α-- liftM is the explicit form-- liftM : [MonadLiftT m n] → m α → n α-- Automatic: IO actions are lifted into ReaderT Config IOdef example : ReaderT String IO Unit := do IO.println "This IO action is lifted automatically" let env ← read IO.println s!"Environment: {env}"
The transitive closure MonadLiftT means lifts compose: if m lifts to n and n lifts to k, then m lifts to k automatically.
-- StateT: run with initial state, returns (value, finalState)#eval (do modify (· + 1); get : StateM Nat Nat).run 0-- (1, 1)-- StateT.run': run with initial state, discard final state#eval (do modify (· + 10); get : StateM Nat Nat).run' 5-- 15-- ExceptT: run, unwrap to Except#eval (do throw "oops"; return 42 : ExceptT String Id Nat).run-- Except.error "oops"-- ReaderT: run with an environment#eval (do let r ← read; return r + 1 : ReaderT Nat Id Nat).run 10-- 11
Lawful Monads
The LawfulMonad type class requires instances to satisfy the three monad laws:
Left identity: pure a >>= f = f a
Right identity: m >>= pure = m
Associativity: (m >>= f) >>= g = m >>= fun x => f x >>= g
Lean provides LawfulMonad instances for Option, Except, StateT, ReaderT, ExceptT, and IO. You can use these laws in proofs with simp [bind_pure_comp, pure_bind, bind_assoc].