Lecture 5 · Formal Software Verification

Forward Proofs

Structured proofs, calculational proofs, and the PAT principle

Based on the Hitchhiker's Guide to Logical Verification (LoVe), chapter 4.

§5.1 Forward and backward, once more

  • 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.

§5.1 The PAT principle

  • 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  a
fun h => h : True  True
  • fix and assume, from LoVe's library, are the term parsers that expand to fun.

§5.2 Structured constructs

  • 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

§5.2 Structured versus tactic

  • 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 ca c a:Propb:Propc:Prophab:a bhbc:b cha:ac a:Propb:Propc:Prophab:a bhbc:b cha:ab a:Propb:Propc:Prophab:a bhbc:b cha:aa All goals completed! 🐙 end Forward
  • From ha and hab we have b; from b and hbc we have c.

§5.3 Forward about conjunction and disjunction

  • 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

§5.3 Forward about existential and biconditional

  • 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

§5.3 One-point rules

  • 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

§5.4 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 := m:n:m + m + n = m + n + m All goals completed! 🐙 end Forward
  • Each step is the kind of equation rw consumes, and ac_rfl closes a rearrangement.

§5.4 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 := m:n:2 * m + n = m + n + m m:n:h1:2 * m + n = m + m + n2 * m + n = m + n + m m:n:h1:2 * m + n = m + m + nh2:m + m + n = m + n + m2 * m + n = m + n + m All goals completed! 🐙 end Forward
namespace Forward theorem two_mul_ac (m n : ) : 2 * m + n = m + n + m := m:n:2 * m + n = m + n + m m:n:m + m + n = m + n + m All goals completed! 🐙 end Forward
  • The choice is about the reader, not the checker.

§5.5 Forward steps in tactic mode

  • 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 := P: Proph: (n : ), P nP 7 P: Proph:P 7P 7 All goals completed! 🐙 end Forward
namespace Forward example (α : Type) (P : α Prop) (Q : Prop) (hex : x, P x) (h : x, P x Q) : Q := α:TypeP:α PropQ:Prophex: x, P xh: (x : α), P x QQ α:TypeP:α PropQ:Proph: (x : α), P x Qa:αha:P aQ All goals completed! 🐙 end Forward

§5.5 A mixed proof

  • 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 := a:Propb:Propc:Prophab:a bhbc:b ca c a:Propb:Propc:Prophab:a bhbc:b cha:ac a:Propb:Propc:Prophab:a bhbc:b cha:ahb:bc 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.

§5.6 Proofs by recursion

  • 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

§5.6 Recursion versus 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) α: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
namespace Forward theorem reverse_append_tac {α : 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
  • Weeks 6 and 7 give the general method.

§5.7 Worked example: Forall_one_point

  • A 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 .

§5.7 Worked example: the distributive law

  • 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.

Summary

  • 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.