1.7. Natural Deduction in Lean
In Lean, we state a proposition and prove it in one declaration. The example keyword introduces an anonymous statement, and theorem introduces a named one. Hypotheses appear before the colon as named assumptions, and the proposition to prove, the goal, appears after it.
Lean encodes natural deduction directly. A proof of a proposition is a term whose type is that proposition, an open assumption is a variable of that type, and each deduction rule becomes a way to build or take apart such a term. The simplest proof uses an assumption directly, the assumption rule of natural deduction.
example (P : Prop) (h : P) : P := h
Here h names the assumption that P holds, and the proof is h itself. Lecture 3 develops this correspondence between propositions and types.W. A. Howard, The Formulae-as-Types Notion of Construction, in To H. B. Curry: Essays on Combinatory Logic, Lambda Calculus and Formalism, Academic Press, 1980.
Table 1.7.1 maps each rule of the section on natural deduction to the Lean term that realizes it. An introduction rule builds a term, and an elimination rule takes one apart.
Rule | Lean term | Example |
|---|---|---|
assumption | a hypothesis name |
|
→I |
|
|
→E | application |
|
∧I |
|
|
∧E₁, ∧E₂ |
|
|
∨I₁, ∨I₂ |
|
|
∨E |
|
|
¬I |
|
|
¬E |
application into |
|
⊥E |
|
|
Table 1.7.1. The natural deduction rules and the Lean terms that realize them.
Because ¬P abbreviates P → False, the negation rules reuse the terms for implication. To see the correspondence on a full derivation, take P ∧ Q → Q ∧ P. It discharges the assumption P ∧ Q, projects each conjunct, and reassembles them in the opposite order.
[P ∧ Q] [P ∧ Q]
───────── ∧E₂ ───────── ∧E₁
Q P
─────────────────────────── ∧I
Q ∧ P
───────────────────────────── →I
P ∧ Q → Q ∧ P
The Lean term follows the derivation step for step. The abstraction fun h => … is the →I that discharges P ∧ Q, the projections h.right and h.left are the two ∧E steps, and the pair ⟨_, _⟩ is the ∧I.
example (P Q : Prop) : P ∧ Q → Q ∧ P :=
fun h => ⟨h.right, h.left⟩
1.7.1. Examples
The proofs below encode the ten derivations of the previous section as proof terms. Each term mirrors its derivation, with an introduction rule building a term and an elimination rule taking one apart.
Example 1. Implication is reflexive.
example (P : Prop) : P → P :=
fun h => h
Example 2. A conjunction entails each conjunct.
example (P Q : Prop) : P ∧ Q → P :=
fun h => h.left
Example 3. A disjunct entails the disjunction.
example (P Q : Prop) : P → P ∨ Q :=
fun h => Or.inl h
Example 4. Anything follows from absurdity.
example (P : Prop) : False → P :=
fun h => False.elim h
Example 5. Modus ponens, packaged as a single implication.
example (P Q : Prop) : (P → Q) ∧ P → Q :=
fun h => h.left h.right
Example 6. Disjunction commutes.
example (P Q : Prop) : P ∨ Q → Q ∨ P :=
fun h => h.elim
(fun hP => Or.inr hP)
(fun hQ => Or.inl hQ)
Example 7. Double negation introduction.
example (P : Prop) : P → ¬¬P :=
fun hP hnP => hnP hP
This example deserves the full unfolding, because its proof has two functions where the statement seems to have one implication. Since ¬A is A → False, the double negation unfolds twice, from the outside in. First ¬¬P is ¬P → False, then it is (P → False) → False, and the whole statement is P → ((P → False) → False). The parentheses around the inner negation are needed. The arrow associates to the right, so P → P → P → False is the proposition P → (P → (P → False)), which is a different one, and a false one.
The term therefore has one function per arrow, and fun hP hnP => hnP hP abbreviates fun hP => fun hnP => hnP hP. The first function is the →I that discharges P and returns a proof of ¬¬P. That proof is itself a function, and the second function is the ¬I that discharges ¬P. Its parameter has type ¬P, not P, which is the easy place to slip. The context then holds hP of type P and hnP of type P → False, and False remains to be proved. A negative hypothesis is a function into False, so applying it to what it denies is the ¬E, and hnP hP closes the proof. The other order does not typecheck, because hP is not a function.
example (P : Prop) : P → ¬¬P :=
fun (hP : P) =>
fun (hnP : ¬P) => hnP hP
Example 8. Contraposition.
example (P Q : Prop) : (P → Q) → (¬Q → ¬P) :=
fun hPQ hnQ hP => hnQ (hPQ hP)
Example 9. Double negation elimination, which needs classical reasoning.
example (P : Prop) : ¬¬P → P :=
fun h => Classical.byContradiction (fun hnP => h hnP)
Example 10. Currying turns a conjunctive hypothesis into nested implications.
example (P Q R : Prop) : (P ∧ Q → R) → (P → (Q → R)) :=
fun h hP hQ => h ⟨hP, hQ⟩