Source: Theorem Proving in Lean 4
Basics
Commands:
#check e: Shows the type ofe(elaborates without evaluating)#eval e: Evaluateseand prints the results (compiles and runs)#print x: Shows the definitionvariable: For more compact function declarations. E.g.variable (x : Nat)andfun (y : Nat) => x + ymeans the same asfun (x : Nat) (y : Nat) => x + y. Variables are arguments to all definitions that reference them in the file.sectioncan be used to limit the scope of variables.
Keywords:
def: Declares a new constant symbol into the working environmentlet: Local definition. E.glet a := t1; t2is definitionally equal to the result of replacing every occurrence ofaint2byt1. Used e.g. for local variables in functions.fun: Creates a function from an expression, e.g.fun (x : Nat) => x + 5
Definitions:
- “definitionally equal”: Two terms that reduce to the same value
- “dependent types”: Types that depend on parameters (including parameters of type
Type).
Propositions and proofs
Propositions as types
Propis the type of propositions.- A proposition
P : Propis a type itself, which can be inhabited (= there exists a term of typeP) or uninhabited (= there is no term of typeP). - If there exists a term
t : P, thenPis true, andtis a proof ofP.
Curry-Howard isomorphism:
Propis just syntactic sugar forSort 0(= the bottom of the type hierarchy).- A proof of
Pis just a term of typeP. - A function
P → Q(withP Q : Prop) is isomorphic to the logical implication “if P then Q”. - Conjunctions
P ∧ Qare isomorphic to product typesP × Q. - Disjunctions
P ∨ Qare isomorphic to sum typesP ⊕ Q. - Negations
¬Pare definitionally equal toP -> False.
The theorem command is like the def command, since a function that takes proofs of propositions as arguments and returns a proof of another proposition is a theorem. The only difference is that proofs are marked as irreducible (not unfolded). This is fine because Prop enjoys “proof irrelevance”: any two proofs of a proposition are definitionally equal, so the specific proof does not matter, only the fact that it is provable.
Examples:
variable {p : Prop}
variable {q : Prop}
theorem t1 (hp : p) (hq : q) : p := hp
theorem modus_ponens (hp : p) (pq : p → q) : q := pq hp(The h in hp and hq stands for “hypothesis”.)
Similarly, have is similar to let. The difference is that have x : p := hp does not remember the value of x (the proof), only the type (the proposition).
sorry is similar to Rust’s todo!(). It is a placeholder for a proof that has not been completed yet. axiom is similar to theorem, but it does not expect a term (or, equivalently, as if the body was sorry). It simply postulates the existence of a term of the given type.
-- Same statement as `Classical.em`
axiom excluded_middle (p : Prop) : p ∨ ¬p
example (a: Nat): a = 1 ∨ a ≠ 1 :=
excluded_middle (a = 1)Propositional logic
And: Similar to the product type, or, equivalently, a struct storing two propositions (And.leftandAnd.right).Or: Similar to the sum type, or, equivalently, an enum (in Lean: inductive type) with two cases (Or.inlandOr.inr). It has two constructors (to pass either one of the propositions), but only one eliminator (to handle both cases).Not: Defined asdef Not (a : Prop) : Prop := a → False. It is a function type, so it has one constructor (the lambda) and one eliminator (application).False: An uninhabited type (no constructors), so it has no intro rules, but one elimination rule (False.elim : False → Cfor anyC).True: An inhabited type (one constructor, no fields), so it has one intro rule (True.intro) but no elimination rules (it carries no information).Iff: A structure with two fields,Iff.mpandIff.mpr, which are the two directions of the equivalence.
Introduction and elimination rules:
- An introduction rule says how to build a proof of a connective.
- An elimination rule says how to use a proof you already have.
- For an inductive type: constructors = intro rules, and the eliminator (recursor) must cover every constructor.
| Connective | intro | elim |
|---|---|---|
∧ | And.intro | .left/.right (or And.rec) |
∨ | Or.inl/Or.inr | Or.elim |
→ | fun (lambda) | application |
∃ | Exists.intro | Exists.elim |
True | True.intro | — (carries no info) |
False | — (none) | False.elim |
∧: one intro (one constructor), but project either side.∨: two intros, but its eliminator forces you to handle both cases.True: one intro, no fields → no useful elimination.False: zero constructors → no intro;False.elim : False → Cgives anything.
Shorthands:
- If there is a single constructor and the type can be inferred,
⟨ ... ⟩can be used as syntactic sugar for the intro rule. - For an expression
eof typeFoo,e.baris a shorthand forFoo.bar e.
Proof examples:
theorem and_commutative (p q: Prop) (pq: p ∧ q) : q ∧ p :=
⟨ pq.right, pq.left ⟩
theorem or_commutative (p q: Prop) (h: p ∨ q) : q ∨ p :=
h.elim
(fun (hp: p) => Or.inr hp)
(fun (hq: q) => Or.inl hq)
theorem or_swap (p q: Prop): p ∨ q ↔ q ∨ p :=
Iff.intro
(fun h => or_commutative p q h)
(fun h => or_commutative q p h)
example (p: Prop) (h: p ∨ False): p :=
h.elim
(fun hp => hp)
(fun hfalse => hfalse.elim )Classical logic
In classical logic, the law of excluded middle states that for any proposition p, either p is true or ¬p is true. It can be instantiated via Classical.em.
From this, we can derive the double negation elimination:
theorem double_negation (p: Prop) (h: ¬¬p) : p :=
-- `p ∨ ¬p` follows from the law of excluded middle.
-- In each of the two cases, we can derive `p`.
(Classical.em p).elim
-- If `p` is true, then we are done.
(fun hp => hp)
-- If `¬p` is true, we both `hnp: ¬p` and `h: ¬¬p`, which is a contradiction
-- from which we can derive `p` (or anything else, for that matter).
-- `¬q` is defined as `q → False`, so `h` is a function that takes a proof of `¬p`
-- and produces a proof of `False`. Applying the elimination rule for `False` allows
-- us to derive any proposition, including `p`.
-- An equivalent way to write this would be `absurd hnp h`.
(fun hnp => (h hnp).elim )
-- The law of excluded middle also follows from double negation:
example (p: Prop): p ∨ ¬p :=
double_negation (p ∨ ¬p)
(fun (h: ¬(p ∨ ¬p)) => h (Or.inr (fun hp => h (Or.inl hp))))The classical axioms give us access to additional proof patterns:
- Proof by cases:
Classical.byCasesallows us to deriveqif we can showp → qand¬p → q. - Proof by contradiction:
Classical.byContradictionallows us to derivepif we can show¬p → False.
Appendix
TODO:
- Explain
show,suffices