Structured proofs, calculational proofs, and the PAT principle
Christiano Braga · Mestrado em Sistemas e Computação · IME
Based on the Hitchhiker's Guide to Logical Verification (LoVe), chapter 4.
A forward proof starts at the hypotheses and derives new facts until it reaches the goal. Its phrase is "from … we have …", the mirror of Lecture 4's "it suffices to prove".
Backward, from the goal
to prove c, by hbc it suffices to prove b; to prove b, by hab it suffices to prove a; and ha proves a.
Forward, from the hypotheses
from ha and hab, we have b; from b and hbc, we have c.
A natural deduction derivation admits both readings; this lecture writes the forward one, as a structured term.
Propositions as types, proofs as terms. A proposition is a type, and a proof of it is a term of that type. An implication a → b is the type of functions from proofs of a to proofs of b, so a proof of an implication is a function, and ∀ x, P x is a dependent function type.
fun a h => h : ∀ (a : Prop), a → a#check (fun (a : Prop) (h : a) => h)
fun h => h : True → True#check (assume h : True; h)
fun a h => h : ∀ (a : Prop), a → afun h => h : True → True
fix and assume, from LoVe's library, are the term parsers that expand to fun.
fix x : α discharges a ∀; assume h : P discharges a →; have h : P := pf names a forward fact; show P from pf restates the goal.
namespace Forward
theorem fst_of_two_props :
∀ a b : Prop, a → b → a :=
fix a b : Prop;
assume ha : a;
assume hb : b;
show a from ha
end Forward
The same term, three ways
namespace Forward
example : ∀ a b : Prop, a → b → a :=
fix a b : Prop;
assume ha : a; assume hb : b; ha
example : ∀ a b : Prop, a → b → a :=
fun a b ha hb => ha
end Forward
The composition of implications, forwards as two have steps and backwards as the Lecture 4 script.
namespace Forward
theorem prop_comp (a b c : Prop)
(hab : a → b) (hbc : b → c) :
a → c :=
assume ha : a;
have hb : b := hab ha;
show c from hbc hb
end Forward
namespace Forward
example (a b c : Prop)
(hab : a → b) (hbc : b → c) :
a → c := a:Propb:Propc:Prophab:a → bhbc:b → c⊢ a → c
a:Propb:Propc:Prophab:a → bhbc:b → cha:a⊢ c
a:Propb:Propc:Prophab:a → bhbc:b → cha:a⊢ b
a:Propb:Propc:Prophab:a → bhbc:b → cha:a⊢ a
All goals completed! 🐙
end Forward
From ha and hab we have b; from b and hbc we have c.
Elimination rules take a hypothesis apart; introduction rules and the anonymous constructor build the goal.
namespace Forward
theorem And_swap (a b : Prop) :
a ∧ b → b ∧ a :=
assume h : a ∧ b;
have ha : a := And.left h;
have hb : b := And.right h;
show b ∧ a from
And.intro hb ha
end Forward
namespace Forward
theorem Or_swap (a b : Prop) :
a ∨ b → b ∨ a :=
assume h : a ∨ b;
Or.elim h
(fun ha => Or.inr ha)
(fun hb => Or.inl hb)
end Forward
Exists.intro t pf supplies a witness; Exists.elim h f names one; Iff.mp and Iff.mpr apply an equivalence each way.
namespace Forward
example (P : ℕ → Prop) (h : P 3) :
∃ n, P n :=
Exists.intro 3 h
example (α : Type) (P : α → Prop)
(Q : Prop) (h : ∃ x, P x)
(f : ∀ x, P x → Q) : Q :=
Exists.elim h f
end Forward
namespace Forward
example (a b : Prop)
(h : a ↔ b) (ha : a) : b :=
Iff.mp h ha
example (a b : Prop)
(h : a ↔ b) (hb : b) : a :=
Iff.mpr h hb
end Forward
The payoff. A quantifier whose bound variable is pinned by an equation collapses to a single instance.
namespace Forward
theorem Forall_one_point (α : Type)
(t : α) (P : α → Prop) :
(∀ x, x = t → P x) ↔ P t :=
Iff.intro
(assume h : ∀ x, x = t → P x;
h t rfl)
(assume hpt : P t;
fix x : α;
assume hxt : x = t;
hxt ▸ hpt)
end Forward
namespace Forward
theorem Exists_one_point (α : Type)
(t : α) (P : α → Prop) :
(∃ x, x = t ∧ P x) ↔ P t :=
Iff.intro
(assume h : ∃ x, x = t ∧ P x;
Exists.elim h
(fun x hx => hx.1 ▸ hx.2))
(assume hpt : P t;
Exists.intro t
(And.intro rfl hpt))
end Forward
calc
A calculational proof lays a chain of equalities out for the reader, each step justified by a rewrite or a lemma, calc composing Eq.trans.
namespace Forward
theorem two_mul_example (m n : ℕ) :
2 * m + n = m + n + m := m:ℕn:ℕ⊢ 2 * m + n = m + n + m
calc 2 * m + n = (m + m) + n := m:ℕn:ℕ⊢ 2 * m + n = m + m + n All goals completed! 🐙
_ = m + n + m := by m:ℕn:ℕ⊢ m + m + n = m + n + m ac_rfl All goals completed! 🐙
end Forward
Each step is the kind of equation rw consumes, and ac_rfl closes a rearrangement.
calc versus simp and Eq.trans
One identity, three proofs. calc documents the chain, Eq.trans shows the transitivity, ac_rfl hides it all.
namespace Forward
theorem two_mul_trans (m n : ℕ) :
2 * m + n = m + n + m := by m:ℕn:ℕ⊢ 2 * m + n = m + n + m
have h1 :
2 * m + n = (m + m) + n := by
rw [Nat.two_mul m:ℕn:ℕ⊢ m + m + n = m + m + n] m:ℕn:ℕh1:2 * m + n = m + m + n⊢ 2 * m + n = m + n + m
have h2 :
(m + m) + n = m + n + m := by
ac_rfl m:ℕn:ℕh1:2 * m + n = m + m + nh2:m + m + n = m + n + m⊢ 2 * m + n = m + n + m
exact Eq.trans h1 h2 All goals completed! 🐙
end Forward
namespace Forward
theorem two_mul_ac (m n : ℕ) :
2 * m + n = m + n + m := by m:ℕn:ℕ⊢ 2 * m + n = m + n + m
rw [Nat.two_mul m:ℕn:ℕ⊢ m + m + n = m + n + m] m:ℕn:ℕ⊢ m + m + n = m + n + m
ac_rfl All goals completed! 🐙
end Forward
The choice is about the reader, not the checker.
have h : P := pf adds a proved fact; let x := t adds an abbreviation; specialize and obtain are forward steps too. Real proofs mix the two directions.
namespace Forward
example (P : ℕ → Prop)
(h : ∀ n, P n) : P 7 := by P:ℕ → Proph:∀ (n : ℕ), P n⊢ P 7
specialize h 7 P:ℕ → Proph:P 7⊢ P 7
exact h All goals completed! 🐙
end Forward
namespace Forward
example (α : Type) (P : α → Prop)
(Q : Prop) (hex : ∃ x, P x)
(h : ∀ x, P x → Q) : Q := by α:TypeP:α → PropQ:Prophex:∃ x, P xh:∀ (x : α), P x → Q⊢ Q
obtain ⟨a, ha⟩ := hex α:TypeP:α → PropQ:Proph:∀ (x : α), P x → Qa:αha:P a⊢ Q
exact h a ha All goals completed! 🐙
end Forward
A forward have inside a backward proof, each direction marked.
namespace Forward
theorem prop_comp_tactical
(a b c : Prop)
(hab : a → b) (hbc : b → c) :
a → c := by a:Propb:Propc:Prophab:a → bhbc:b → c⊢ a → c
intro ha a:Propb:Propc:Prophab:a → bhbc:b → cha:a⊢ c
have hb : b := hab ha a:Propb:Propc:Prophab:a → bhbc:b → cha:ahb:b⊢ c
exact hbc hb All goals completed! 🐙
end Forward
intro ha and exact work backwards from the goal.
have hb : b := hab ha works forwards from the hypotheses.
The mixed proof is often the shortest, taking each fact from wherever it is easiest to reach.
Under PAT, a recursive function returning a proof is a proof by induction, and the recursive call is the induction hypothesis.
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
induction
The recursive proof and the induction tactic are the same proof; the recursive call is the branch's ih.
namespace Forward
theorem reverse_append {α : Type} :
∀ (xs ys : List α),
reverse (appendPretty xs ys)
= appendPretty (reverse ys)
(reverse xs)
| [], ys => α:Typeys:List α⊢ reverse (appendPretty [] ys) = appendPretty (reverse ys) (reverse []) by α:Typeys:List α⊢ reverse (appendPretty [] ys) = appendPretty (reverse ys) (reverse [])
simp [reverse, appendPretty,
append_nil] All goals completed! 🐙
| x :: xs, ys => α:Typex:αxs:List αys:List α⊢ reverse (appendPretty (x :: xs) ys) = appendPretty (reverse ys) (reverse (x :: xs)) by α:Typex:αxs:List αys:List α⊢ reverse (appendPretty (x :: xs) ys) = appendPretty (reverse ys) (reverse (x :: xs))
simp [reverse, appendPretty,
reverse_append xs ys,
append_assoc] All goals completed! 🐙
end Forward
namespace Forward
theorem reverse_append_tac
{α : Type} (xs ys : List α) :
reverse (appendPretty xs ys)
= appendPretty (reverse ys)
(reverse xs) := by α:Typexs:List αys:List α⊢ reverse (appendPretty xs ys) = appendPretty (reverse ys) (reverse xs)
induction xs with
| nil => nil α:Typeys:List α⊢ reverse (appendPretty [] ys) = appendPretty (reverse ys) (reverse [])
simp [reverse, appendPretty,
append_nil] All goals completed! 🐙
| cons x xs' ih => cons α: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'))
simp [reverse, appendPretty,
ih, append_assoc] All goals completed! 🐙
end Forward
Weeks 6 and 7 give the general method.
Forall_one_pointA quantifier proof natural forwards and awkward backwards.
namespace Forward
theorem Forall_one_point_worked (α : Type)
(t : α) (P : α → Prop) :
(∀ x, x = t → P x) ↔ P t :=
Iff.intro
(assume h : ∀ x, x = t → P x; h t rfl)
(assume hpt : P t;
fix x : α; assume hxt : x = t; hxt ▸ hpt)
end Forward
Forwards, instantiate h at t and close t = t by rfl. Backwards, fix x, assume x = t, and rewrite with ▸.
a ∧ (b ∨ c) → (a ∧ b) ∨ (a ∧ c), the first worked example of Lecture 4, now as a structured term.
namespace Forward
theorem and_or_distrib
(a b c : Prop) :
a ∧ (b ∨ c) →
(a ∧ b) ∨ (a ∧ c) :=
assume habc : a ∧ (b ∨ c);
have ha : a := And.left habc;
Or.elim (And.right habc)
(fun hb =>
Or.inl (And.intro ha hb))
(fun hc =>
Or.inr (And.intro ha hc))
end Forward
From habc we have a, named ha.
Its right conjunct b ∨ c gives two cases.
In each case we build the matching disjunct with the anonymous constructor.
The backward script planned from the goal; the forward term builds from the hypotheses.
A forward proof reads "from … we have …", the mirror of backward's "it suffices to prove".
A structured proof is a term shaped like its proposition, with fix, assume, have, show.
Under PAT a proposition is a type and a proof is a term, so assume is fun.
Forward reasoning uses the rule names by juxtaposition and the anonymous constructor.
calc lays a chain of equalities out for the reader; ac_rfl and simp hide it.
Real proofs mix directions, a forward have inside a backward apply.
Structural recursion is a proof by induction, formalised in weeks 6 and 7.
Exercises: see the lecture notes.