Formal Software Verification

4.7. Worked Examples🔗

Each example below is carried out in full and verbalised, as the guide does. They are disjoint from the exercises, and Lean checks every line when the notes are built.

4.7.1. Distributing a conjunction over a disjunction🔗

The statement a ∧ (b ∨ c) → (a ∧ b) ∨ (a ∧ c) uses only intro, apply, exact and bullets. The elimination rule of ∨ drives the proof, and juxtaposition instantiates it with the right conjunct of the hypothesis.

namespace Backward theorem and_or_distrib (a b c : Prop) : a (b c) (a b) (a c) := a:Propb:Propc:Propa (b c) a b a c a:Propb:Propc:Prophabc:a (b c)a b a c a:Propb:Propc:Prophabc:a (b c)b a b a ca:Propb:Propc:Prophabc:a (b c)c a b a c a:Propb:Propc:Prophabc:a (b c)b a b a c a:Propb:Propc:Prophabc:a (b c)hb:ba b a c a:Propb:Propc:Prophabc:a (b c)hb:ba b a:Propb:Propc:Prophabc:a (b c)hb:baa:Propb:Propc:Prophabc:a (b c)hb:bb a:Propb:Propc:Prophabc:a (b c)hb:ba All goals completed! 🐙 a:Propb:Propc:Prophabc:a (b c)hb:bb All goals completed! 🐙 a:Propb:Propc:Prophabc:a (b c)c a b a c a:Propb:Propc:Prophabc:a (b c)hc:ca b a c a:Propb:Propc:Prophabc:a (b c)hc:ca c a:Propb:Propc:Prophabc:a (b c)hc:caa:Propb:Propc:Prophabc:a (b c)hc:cc a:Propb:Propc:Prophabc:a (b c)hc:ca All goals completed! 🐙 a:Propb:Propc:Prophabc:a (b c)hc:cc All goals completed! 🐙 end Backward

In words. Assume a ∧ (b ∨ c). Its right conjunct is a disjunction, and it suffices to prove the conclusion from each disjunct. If b holds, it suffices to prove the left disjunct a ∧ b, whose parts are the left conjunct of the hypothesis and b itself. If c holds, the right disjunct a ∧ c follows the same way. Each bullet closes one branch, and the proof reads exactly like its pen-and-paper counterpart.

4.7.2. An unprovable goal and a backtrack🔗

The disjunction of the statement a ∧ b → a ∨ c admits two introduction rules, and only one leads to a proof. Or.inl and Or.inr can turn a provable goal into an unprovable one. The first attempt commits to the right disjunct, and the trace shows a conclusion c that no hypothesis proves, so only sorry closes the block.

declaration uses `sorry`example (a b c : Prop) : a b a c := a:Propb:Propc:Propa b a c a:Propb:Propc:Prophab:a ba c a:Propb:Propc:Prophab:a bc a b c:Prophab:a bca:Propb:Propc:Prophab:a bc All goals completed! 🐙
a b c:Prophab:a  bc

The remedy is to remember the choice point and backtrack. The second attempt commits to the left disjunct, and the left conjunct of the hypothesis closes it.

namespace Backward theorem and_imp_or (a b c : Prop) : a b a c := a:Propb:Propc:Propa b a c a:Propb:Propc:Prophab:a ba c a:Propb:Propc:Prophab:a ba All goals completed! 🐙 end Backward

4.7.3. rw versus simp🔗

Given f and the equation hf : ∀ x, f x = x + 1, either tactic proves the conclusion f (f 0) = 2, in different ways. rw [hf] rewrites the occurrences of the first matching subterm, here the outer application, and needs a second invocation for the inner one, after which the rfl it tries closes the goal. simp [hf] rewrites exhaustively and needs one invocation.

example (f : ) (hf : x, f x = x + 1) : f (f 0) = 2 := f: hf: (x : ), f x = x + 1f (f 0) = 2 All goals completed! 🐙 example (f : ) (hf : x, f x = x + 1) : f (f 0) = 2 := f: hf: (x : ), f x = x + 1f (f 0) = 2 All goals completed! 🐙

The residual goal makes "first matching subterm" concrete. One rw [hf] rewrites the outer application and leaves the inner one in place.

example (f : ) (hf : x, f x = x + 1) : f (f 0) = 2 := f: hf: (x : ), f x = x + 1f (f 0) = 2 f: hf: (x : ), f x = x + 1f 0 + 1 = 2 f: hf: (x : ), f x = x + 1f 0 + 1 = 2f: hf: (x : ), f x = x + 1f 0 + 1 = 2 All goals completed! 🐙
f:  hf: (x : ), f x = x + 1f 0 + 1 = 2

4.7.4. Discharging reverse_cons🔗

The final worked example of Lecture 3 stated reverse (x :: xs) = snoc (reverse xs) x and left it with sorry. Unfolding reverse turns the left side into appendPretty (reverse xs) [x], so the statement mixes appendPretty and snoc, and the missing piece is the theorem that relates them. This is the guide's hint in action: a hard case usually signals a missing auxiliary theorem.

namespace Backward theorem append_snoc {α : Type} (ys : List α) (x : α) : appendPretty ys [x] = snoc ys x := α:Typeys:List αx:αappendPretty ys [x] = snoc ys x induction ys with α:Typex:αappendPretty [] [x] = snoc [] x All goals completed! 🐙 α:Typex:αy:αys':List αih:appendPretty ys' [x] = snoc ys' xappendPretty (y :: ys') [x] = snoc (y :: ys') x All goals completed! 🐙 theorem reverse_cons {α : Type} (x : α) (xs : List α) : reverse (x :: xs) = snoc (reverse xs) x := α:Typex:αxs:List αreverse (x :: xs) = snoc (reverse xs) x All goals completed! 🐙 end Backward

The auxiliary theorem inducts on the list that the recursion of appendPretty consumes, and the main theorem is then one simp away, using the defining equations of reverse and the auxiliary theorem as rewrite rules.