Pattern Matching
Pattern matching is the primary control flow mechanism in Lean. It's how you deconstruct data and handle different cases elegantly.
The Match Expression
Use match to inspect values and branch based on their structure:
1def describe (n : Nat) : String :=2 match n with3 | 0 => "zero"4 | 1 => "one"5 | 2 => "two"6 | _ => "many" -- underscore matches anything78#eval describe 0 -- "zero"9#eval describe 5 -- "many"Each branch has a pattern (left of =>) and a result (right of =>). Lean checks patterns from top to bottom.
Function Definition Syntax
For simple functions, you can pattern match directly in the definition:
1-- Using match2def factorial1 (n : Nat) : Nat :=3 match n with4 | 0 => 15 | n + 1 => (n + 1) * factorial1 n67-- Direct pattern matching (cleaner)8def factorial2 : Nat → Nat9 | 0 => 110 | n + 1 => (n + 1) * factorial2 n1112#eval factorial2 5 -- 120match) is often cleaner for simple functions. Use match when you need to match in the middle of a larger expression.Matching on Inductive Types
Pattern matching shines when working with inductive types:
1inductive Shape where2 | circle (radius : Float)3 | rectangle (width : Float) (height : Float)4 | triangle (base : Float) (height : Float)56def area : Shape → Float7 | Shape.circle r => 3.14159 * r * r8 | Shape.rectangle w h => w * h9 | Shape.triangle b h => 0.5 * b * h1011def perimeter : Shape → Float12 | Shape.circle r => 2.0 * 3.14159 * r13 | Shape.rectangle w h => 2.0 * (w + h)14 | Shape.triangle _ _ => 0.0 -- would need side lengths1516#eval area (Shape.rectangle 3.0 4.0) -- 12.0Exhaustiveness Checking
Lean refuses to compile if you miss a case:
1inductive Color where2 | red | green | blue34-- This won't compile!5-- def toHex : Color → String6-- | Color.red => "#FF0000"7-- | Color.green => "#00FF00"8-- -- Missing blue case!910-- Complete version:11def toHex : Color → String12 | Color.red => "#FF0000"13 | Color.green => "#00FF00"14 | Color.blue => "#0000FF"Destructuring Structures
Pattern matching works on structures too:
1structure Point where2 x : Float3 y : Float45-- Destructure in the pattern6def distanceFromOrigin : Point → Float7 | ⟨x, y⟩ => Float.sqrt (x * x + y * y)89-- Equivalent using field access10def distanceFromOrigin' (p : Point) : Float :=11 Float.sqrt (p.x * p.x + p.y * p.y)1213#eval distanceFromOrigin ⟨3.0, 4.0⟩ -- 5.0Nested Patterns
Patterns can be nested to match complex structures:
1-- Match on nested structures2def firstElement : List (Option Nat) → Option Nat3 | [] => none4 | none :: _ => none5 | some n :: _ => some n67#eval firstElement [some 1, none] -- some 18#eval firstElement [none, some 2] -- none910-- Match on pairs11def addPair : Nat × Nat → Nat12 | (a, b) => a + b1314#eval addPair (3, 4) -- 7Guards (Conditional Patterns)
Add conditions to patterns with if:
1def classifyNumber (n : Int) : String :=2 match n with3 | 0 => "zero"4 | n => if n > 0 then "positive" else "negative"56-- Or using pattern guards7def grade (score : Nat) : String :=8 match score with9 | s => if s >= 90 then "A"10 else if s >= 80 then "B"11 else if s >= 70 then "C"12 else if s >= 60 then "D"13 else "F"1415#eval grade 85 -- "B"Or-Patterns
Match multiple patterns with the same result:
1def isWeekend : Weekday → Bool2 | Weekday.saturday | Weekday.sunday => true3 | _ => false45-- Without or-patterns (more verbose)6def isWeekend' : Weekday → Bool7 | Weekday.saturday => true8 | Weekday.sunday => true9 | _ => falseDeep Dive: Pattern Matching Compiles to Efficient Code
Lean's pattern matching compiles to efficient decision trees. The compiler analyzes patterns and generates optimal branching code—often better than hand-written if-else chains.
This means you can write clear, declarative patterns without worrying about performance.
Matching on Lists
Lists are commonly pattern-matched to process elements:
1def sum : List Nat → Nat2 | [] => 03 | x :: xs => x + sum xs45def head? : List α → Option α6 | [] => none7 | x :: _ => some x89def tail? : List α → Option (List α)10 | [] => none11 | _ :: xs => some xs1213def take : Nat → List α → List α14 | 0, _ => []15 | _, [] => []16 | n + 1, x :: xs => x :: take n xs1718#eval sum [1, 2, 3, 4, 5] -- 1519#eval take 2 [1, 2, 3, 4, 5] -- [1, 2]