Formal Software Verification

5.6. Proofs by Pattern Matching and Recursion🔗

Under the PAT principle, a recursive function that returns a proof is a proof by induction, and the recursive call is the induction hypothesis. A definition by pattern matching on a list has one equation for the empty list and one for a cons, and the equation for x :: xs may call the function on the smaller list xs, which is the appeal to the induction hypothesis. The section shows two list identities proved this way and states plainly that the general theory of structural induction over arbitrary inductive types is the subject of weeks 6 and 7. Here recursion appears only as a forward proof device.

Two auxiliary identities come first. Appending the empty list on the right changes nothing, and appending is associative. Each is proved by recursion on the first list, and the recursive call carries the induction hypothesis; congrArg (List.cons x) rebuilds the cons around it.

namespace Forward theorem append_nil {α : Type} : (xs : List α), appendPretty xs [] = xs | [] => rfl | x :: xs => congrArg (List.cons x) (append_nil xs) theorem append_assoc {α : Type} : (xs ys zs : List α), appendPretty (appendPretty xs ys) zs = appendPretty xs (appendPretty ys zs) | [], _, _ => rfl | x :: xs, ys, zs => congrArg (List.cons x) (append_assoc xs ys zs) end Forward

Reversal distributes over append, in reversed order. The proof recurses on the first list, using the two auxiliaries and the recursive call, which simp consumes as rewrite rules.

namespace Forward theorem reverse_append {α : Type} : (xs ys : List α), reverse (appendPretty xs ys) = appendPretty (reverse ys) (reverse xs) α:Typeys:List αreverse (appendPretty [] ys) = appendPretty (reverse ys) (reverse []) α:Typeys:List αreverse (appendPretty [] ys) = appendPretty (reverse ys) (reverse []) All goals completed! 🐙 α:Typex:αxs:List αys:List αreverse (appendPretty (x :: xs) ys) = appendPretty (reverse ys) (reverse (x :: xs)) α:Typex:αxs:List αys:List αreverse (appendPretty (x :: xs) ys) = appendPretty (reverse ys) (reverse (x :: xs)) All goals completed! 🐙 end Forward

The same statement proved by the induction tactic of Lecture 4 is the same proof in a different dress. The base case is the nil branch, and the step case is the cons branch, whose induction hypothesis ih is exactly the recursive call above.

namespace Forward theorem reverse_append_tactical {α : Type} (xs ys : List α) : reverse (appendPretty xs ys) = appendPretty (reverse ys) (reverse xs) := α:Typexs:List αys:List αreverse (appendPretty xs ys) = appendPretty (reverse ys) (reverse xs) induction xs with α:Typeys:List αreverse (appendPretty [] ys) = appendPretty (reverse ys) (reverse []) All goals completed! 🐙 α:Typeys:List αx:αxs':List αih:reverse (appendPretty xs' ys) = appendPretty (reverse ys) (reverse xs')reverse (appendPretty (x :: xs') ys) = appendPretty (reverse ys) (reverse (x :: xs')) All goals completed! 🐙 end Forward

5.6.1. Examples🔗

The examples below prove list and number identities by recursion, set the recursive proof beside the induction tactic, name the recursive call as the induction hypothesis, and mark the discipline that weeks 6 and 7 formalise.

Example 1. A proof by recursion on ℕ, its base case 0 and its step case n + 1, recasting the induction of Lecture 4 as pattern matching. The identity is add 0 n = n for the add of Lecture 3.

namespace Forward theorem add_zero_rec : (n : ), add 0 n = n | 0 => rfl n:add 0 (n + 1) = n + 1 n:add 0 (n + 1) = n + 1 All goals completed! 🐙 end Forward

Example 2. The base case of associativity alone. The empty first list makes both sides reduce to the same term, so rfl closes it.

namespace Forward example {α : Type} (ys zs : List α) : appendPretty (appendPretty [] ys) zs = appendPretty [] (appendPretty ys zs) := rfl end Forward

Example 3. The step case makes the induction hypothesis explicit. The recursive call ih proves the associativity of the smaller lists, and congrArg (List.cons x) rebuilds the cons around it.

namespace Forward example {α : Type} (x : α) (xs ys zs : List α) (ih : appendPretty (appendPretty xs ys) zs = appendPretty xs (appendPretty ys zs)) : appendPretty (appendPretty (x :: xs) ys) zs = appendPretty (x :: xs) (appendPretty ys zs) := congrArg (List.cons x) ih end Forward

Example 4. The base case of reverse_append, where the reversal of the empty list and the right identity of append together close the goal.

namespace Forward example {α : Type} (ys : List α) : reverse (appendPretty [] ys) = appendPretty (reverse ys) (reverse ([] : List α)) := α:Typeys:List αreverse (appendPretty [] ys) = appendPretty (reverse ys) (reverse []) All goals completed! 🐙 end Forward

Example 5. The step case of reverse_append, using the induction hypothesis ih and the associativity of append.

namespace Forward example {α : Type} (x : α) (xs ys : List α) (ih : reverse (appendPretty xs ys) = appendPretty (reverse ys) (reverse xs)) : reverse (appendPretty (x :: xs) ys) = appendPretty (reverse ys) (reverse (x :: xs)) := α:Typex:αxs:List αys:List αih:reverse (appendPretty xs ys) = appendPretty (reverse ys) (reverse xs)reverse (appendPretty (x :: xs) ys) = appendPretty (reverse ys) (reverse (x :: xs)) All goals completed! 🐙 end Forward

Example 6. The right identity of append by the induction tactic. It is the same proof as the recursive append_nil above, in a different dress.

namespace Forward example {α : Type} (xs : List α) : appendPretty xs [] = xs := α:Typexs:List αappendPretty xs [] = xs induction xs with α:TypeappendPretty [] [] = [] All goals completed! 🐙 α:Typex:αxs':List αih:appendPretty xs' [] = xs'appendPretty (x :: xs') [] = x :: xs' All goals completed! 🐙 end Forward

Example 7. The finished recursive proof rests on propext, which simp uses, and not on sorryAx, so the recursion is genuine.

namespace Forward 'Forward.reverse_append' depends on axioms: [propext]#print axioms reverse_append end Forward
'Forward.reverse_append' depends on axioms: [propext]

Example 8. Not every recursive definition is accepted. The definition below calls itself on the same list, so no argument grows smaller and the recursion never stops, and Lean rejects it with a termination error. The discipline that weeks 6 and 7 formalise is exactly what rules such definitions out.

def loopForever {α : Type} : List α → List α
  | []      => []
  | x :: xs => loopForever (x :: xs)

Example 9. One identity, two proofs. The associativity of append by recursion and by the induction tactic prove the same proposition, and both rest only on structural recursion.

namespace Forward example {α : Type} (xs ys zs : List α) : appendPretty (appendPretty xs ys) zs = appendPretty xs (appendPretty ys zs) := α:Typexs:List αys:List αzs:List αappendPretty (appendPretty xs ys) zs = appendPretty xs (appendPretty ys zs) induction xs with α:Typeys:List αzs:List αappendPretty (appendPretty [] ys) zs = appendPretty [] (appendPretty ys zs) All goals completed! 🐙 α:Typeys:List αzs:List αx:αxs':List αih:appendPretty (appendPretty xs' ys) zs = appendPretty xs' (appendPretty ys zs)appendPretty (appendPretty (x :: xs') ys) zs = appendPretty (x :: xs') (appendPretty ys zs) All goals completed! 🐙 end Forward

Example 10. The general form. A structural recursion on an inductive type has one branch per constructor, and each recursive call, taken on a smaller value, is the induction hypothesis for that branch. Weeks 6 and 7 make this precise for arbitrary inductive types; the reverse_reverse of the worked examples is one more instance.

namespace Forward example {α : Type} (xs : List α) : reverse (appendPretty xs []) = reverse xs := α:Typexs:List αreverse (appendPretty xs []) = reverse xs All goals completed! 🐙 end Forward