Module 3 · Lesson 4

Termination Proofs

Prove that recursive functions always terminate. Lean requires termination proofs to ensure functions are total.

Why Termination Matters

lean
1-- This wouldn't be safe:
2-- def bad (n : Nat) : Nat := bad n -- Infinite loop!
3
4-- Lean rejects functions that might not terminate
5-- Every recursive call must make "progress"
In a total language like Lean, every function must terminate. This ensures that proof terms always reduce to values.

Automatic Termination

lean
1-- Lean often proves termination automatically
2def factorial : Nat Nat
3 | 0 => 1
4 | n + 1 => (n + 1) * factorial n
5-- ✓ Lean sees: n < n + 1
6
7def length : List α Nat
8 | [] => 0
9 | _ :: xs => 1 + length xs
10-- ✓ Lean sees: xs is structurally smaller
11
12def fib : Nat Nat
13 | 0 => 0
14 | 1 => 1
15 | n + 2 => fib (n + 1) + fib n
16-- ✓ Both recursive calls decrease

Structural Recursion

lean
1-- Lean's primary termination checker uses structural recursion
2-- Arguments must decrease according to a well-founded order
3
4def sum : List Nat Nat
5 | [] => 0
6 | x :: xs => x + sum xs -- xs is a subterm of x :: xs
7
8def treeSize : Tree α Nat
9 | .leaf => 0
10 | .node left _ right => 1 + treeSize left + treeSize right
11 -- left and right are subterms of node
Key Takeaway
Structural recursion means calling the function on subterms of the input. Lean automatically recognizes this pattern.

termination_by Clause

lean
1-- When Lean can't infer termination, specify what decreases
2def gcd (a b : Nat) : Nat :=
3 if b = 0 then a
4 else gcd b (a % b)
5termination_by b -- b decreases each call
6
7-- For multiple arguments, use a tuple or expression
8def ack : Nat Nat Nat
9 | 0, n => n + 1
10 | m + 1, 0 => ack m 1
11 | m + 1, n + 1 => ack m (ack (m + 1) n)
12termination_by m n => (m, n) -- Lexicographic order

decreasing_by Tactic

lean
1-- When termination isn't obvious, provide a proof
2def div (n m : Nat) (h : m > 0) : Nat :=
3 if n < m then 0
4 else 1 + div (n - m) m h
5termination_by n
6decreasing_by
7 simp_wf -- Simplify well-founded goal
8 omega -- Prove n - m < n when m > 0 and n ≥ m
Deep Dive: Well-Founded Recursion

Termination is based on well-founded relations. A relation is well-founded if there are no infinite descending chains.

lean
1-- Natural numbers with < are well-founded
2-- No infinite chain: n > n-1 > n-2 > ... > 1 > 0 (stops!)
3
4-- Lean uses this to ensure termination:
5-- Each recursive call must use "smaller" arguments
6-- where "smaller" means related by a well-founded order

Common Patterns

Fuel Pattern

lean
1-- Add a "fuel" parameter that decreases
2def search (fuel : Nat) (f : Nat Bool) (n : Nat) : Option Nat :=
3 match fuel with
4 | 0 => none -- Out of fuel
5 | fuel + 1 =>
6 if f n then some n
7 else search fuel f (n + 1)
8termination_by fuel

Mutual Recursion

lean
1mutual
2 def isEven : Nat Bool
3 | 0 => true
4 | n + 1 => isOdd n
5
6 def isOdd : Nat Bool
7 | 0 => false
8 | n + 1 => isEven n
9end
10-- Termination: n decreases in both directions

Nested Recursion

lean
1def f91 (n : Nat) : Nat :=
2 if n > 100 then n - 10
3 else f91 (f91 (n + 11))
4-- Requires sophisticated termination proof!
5-- This is McCarthy's famous f91 function
Exercise: Decreasing Measure

Provide a termination measure for a simple recursive function.

lean
1def countdown : Nat Nat
2 | 0 => 0
3 | n + 1 => countdown n
4termination_by n

partial Escape Hatch

lean
1-- When you can't prove termination, use partial
2partial def infiniteLoop : Nat Nat
3 | n => infiniteLoop (n + 1) -- No termination proof needed
4
5-- partial functions can't be used in proofs!
6-- They may not terminate, breaking soundness
partialfunctions cannot appear in proof terms. They're only for executable code where termination isn't needed for soundness.

Examples

Binary Search

lean
1def binarySearch (arr : Array Int) (target : Int) (lo hi : Nat) : Option Nat :=
2 if lo hi then none
3 else
4 let mid := (lo + hi) / 2
5 if arr[mid]! < target then binarySearch arr target (mid + 1) hi
6 else if arr[mid]! > target then binarySearch arr target lo mid
7 else some mid
8termination_by hi - lo
9decreasing_by all_goals simp_wf; omega

Quick Sort

lean
1def quickSort : List Nat List Nat
2 | [] => []
3 | pivot :: rest =>
4 let smaller := rest.filter (· < pivot)
5 let larger := rest.filter (· pivot)
6 quickSort smaller ++ [pivot] ++ quickSort larger
7termination_by xs => xs.length
8-- smaller and larger have length < rest.length + 1