Do Notation
Do notation provides clean syntax for sequencing operations that have effects or might fail. It's essential for practical Lean programming.
The Problem: Nested Binds
Chaining operations that return Option or Exceptgets messy fast:
lean
1def lookup (key : String) (env : List (String × Nat)) : Option Nat :=2 env.lookup key34-- Without do notation: deeply nested5def compute (env : List (String × Nat)) : Option Nat :=6 match lookup "x" env with7 | none => none8 | some x => match lookup "y" env with9 | none => none10 | some y => match lookup "z" env with11 | none => none12 | some z => some (x + y + z)Do Notation Cleans This Up
lean
1-- With do notation: flat and readable2def compute' (env : List (String × Nat)) : Option Nat := do3 let x ← lookup "x" env4 let y ← lookup "y" env5 let z ← lookup "z" env6 return x + y + z78def env := [("x", 1), ("y", 2), ("z", 3)]9#eval compute' env -- some 6Key Takeaway
The
← extracts a value from Option. If any extraction fails (none), the whole block returnsnone.How Do Notation Works
do desugars to bind operations:
lean
1-- This do block:2def example1 : Option Nat := do3 let x ← some 54 let y ← some 35 return x + y67-- Is equivalent to:8def example2 : Option Nat :=9 (some 5).bind fun x =>10 (some 3).bind fun y =>11 pure (x + y)1213-- Or using >>= operator:14def example3 : Option Nat :=15 some 5 >>= fun x =>16 some 3 >>= fun y =>17 pure (x + y)Key Constructs
let ← : Extract Value
lean
1def safeDivide (x y : Nat) : Option Nat :=2 if y == 0 then none else some (x / y)34def calculation : Option Nat := do5 let a ← safeDivide 10 2 -- a = 56 let b ← safeDivide a 0 -- Fails! Returns none7 let c ← safeDivide b 1 -- Never reached8 return creturn : Wrap Value
lean
1-- return wraps a pure value2def justFive : Option Nat := do3 return 5 -- same as: some 545-- Can be anywhere in the block6def example : Option Nat := do7 let x ← some 38 if x > 10 then9 return 0 -- Early return10 return x * 2let = : Pure Binding
lean
1def example : Option Nat := do2 let x ← some 5 -- ← extracts from Option3 let y = x * 2 -- = is pure computation4 let z = y + 1 -- no Option involved5 return z -- some 11ℹ
Use
← for extracting from monads (Option,IO, etc.). Use = for regular assignments.Do Works with Many Types
Option
lean
1def optionExample : Option String := do2 let x ← some "hello"3 let y ← some "world"4 return s!"{x} {y}"Except
lean
1def divide (x y : Int) : Except String Int :=2 if y == 0 then .error "division by zero" else .ok (x / y)34def exceptExample : Except String Int := do5 let a ← divide 10 26 let b ← divide a 0 -- Fails with error message7 return bList (Non-determinism)
lean
1-- For List, do represents all combinations2def pairs : List (Nat × Nat) := do3 let x ← [1, 2]4 let y ← [10, 20]5 return (x, y)67#eval pairs -- [(1, 10), (1, 20), (2, 10), (2, 20)]IO (Side Effects)
lean
1def greet : IO Unit := do2 IO.println "What's your name?"3 let name ← IO.getLine4 IO.println s!"Hello, {name}!"Control Flow in Do Blocks
if-then-else
lean
1def classify (n : Nat) : IO Unit := do2 if n == 0 then3 IO.println "zero"4 else if n < 10 then5 IO.println "small"6 else7 IO.println "large"for Loops
lean
1def printNumbers : IO Unit := do2 for i in [1, 2, 3, 4, 5] do3 IO.println s!"Number: {i}"45def sumList (xs : List Nat) : IO Nat := do6 let mut sum := 07 for x in xs do8 sum := sum + x9 return sumwhile Loops
lean
1def countdown : IO Unit := do2 let mut n := 53 while n > 0 do4 IO.println s!"{n}..."5 n := n - 16 IO.println "Liftoff!"Deep Dive: What is a Monad?
do notation works with any type that has aMonad instance. A monad provides:
lean
1class Monad (m : Type → Type) where2 pure : α → m α -- Wrap a value3 bind : m α → (α → m β) → m β -- Chain operationsOption, Except,List, and IO are all monads.
Combining Different Monads
You can't mix monads directly in one do block:
lean
1-- This doesn't work:2-- def bad : IO Nat := do3-- let x ← some 5 -- Option, not IO!4-- return x56-- Convert Option to IO by handling the failure:7def good : IO Nat := do8 let opt : Option Nat := some 59 match opt with10 | none => return 011 | some x => return x