What is a Proof?
A proof is evidence that a statement is true. In Lean, proofs are programs and statements are types. This correspondence is the foundation of everything you'll learn about theorem proving.
Why Do We Need Proofs in Programming?
Traditional programming relies on tests to catch bugs, but tests can only check a finite number of cases. A function might work for every input you test, yet fail on some input you never considered. Proofs go further—they provideguarantees for all possible inputs.
The Problem: Trust
Consider this function:
1def divide (x y : Nat) : Nat := x / yWhat happens when y = 0? In most languages, this crashes at runtime. You can add a comment saying "y must not be zero," but comments lie—the compiler doesn't check them.
What if the type system could guarantee that y is never zero?
1def safeDivide (x : Nat) (y : Nat) (h : y ≠ 0) : Nat := x / y23-- To call this, you must PROVE y ≠ 04#eval safeDivide 10 2 (by decide) -- Works: 55-- #eval safeDivide 10 0 ??? -- Can't call! No proof possibleThe parameter h : y ≠ 0 requires the caller to provideproof that y is not zero. No proof, no function call. The impossible is prevented at compile time.
Proofs as Machine-Checked Reasoning
When you write a proof in Lean, the computer verifies every step. Unlike a human reviewer who might miss mistakes, Lean's kernel is extremely rigorous. If your proof compiles, it's correct—guaranteed.
1-- This proof is VERIFIED by Lean's kernel2theorem example_proof : ∀ n : Nat, n + 0 = n := by3 intro n4 rfl -- Lean checks that this step is valid56-- If you try to cheat, Lean catches it:7-- theorem bad_proof : 1 = 2 := by rfl -- ERROR: rfl cannot prove thisThis machine-checked guarantee is incredibly valuable. Mathematical papers sometimes contain subtle errors that go unnoticed for years. With Lean, such errors are caught immediately.
Statements are Types
In Lean, mathematical statements are expressed as types. This is a profound insight that connects logic and computation:
1-- "2 + 2 = 4" is a TYPE2#check (2 + 2 = 4) -- Prop34-- "For all natural numbers n, n + 0 = n" is a TYPE5#check (∀ n : Nat, n + 0 = n) -- Prop67-- "There exists a natural number greater than 5" is a TYPE8#check (∃ n : Nat, n > 5) -- Prop910-- Even complex statements are types11#check (∀ a b c : Nat, (a + b) + c = a + (b + c)) -- AssociativityThese types live in a special universe called Prop—the universe of propositions (statements that are either true or false).
Proofs are Terms
If a statement is a type, then a proof is a term of that type. Just as 5 : Nat means "5 is a natural number," writing proof : 2 + 2 = 4 means "proof is evidence that 2 + 2 = 4."
1-- The statement "2 + 2 = 4" is a type2-- A proof of it is a term of that type34example : 2 + 2 = 4 := rfl56-- rfl is a proof term7-- Its type is "2 + 2 = 4"8-- Having such a term proves the statement910-- You can name proofs and reuse them11theorem two_plus_two : 2 + 2 = 4 := rfl1213-- Now two_plus_two is a proof we can use elsewhere14#check two_plus_two -- two_plus_two : 2 + 2 = 4The Type-Proof Correspondence
This correspondence goes deep. Consider how types and proofs relate:
| Type/Proposition | Term/Proof |
|---|---|
Nat | 5, 0, 42 |
String | "hello", "world" |
2 + 2 = 4 | rfl |
1 = 2 | No term exists! (unprovable) |
When a type has no terms (like 1 = 2), it means the corresponding statement is false—there's no proof because the statement isn't true.
Why Do We Need Tactics?
Proof terms can be written directly, but they get complicated fast:
1-- Direct proof term: works but unreadable2example : ∀ n : Nat, n + 0 = n := 3 fun n => Nat.add_zero n45-- What if the proof requires many steps?6-- The proof term becomes a nested mess of function applications7-- Imagine hundreds of lines of nested lambdas and applications...Tactics are commands that build proof terms for you. They let you describe the proof strategy in a step-by-step, human-readable way:
1-- Tactic proof: clear and structured2example : ∀ n : Nat, n + 0 = n := by3 intro n -- Let n be any Nat4 rfl -- This is true by definition56-- Each tactic transforms the proof state7-- Lean constructs the underlying proof term automaticallyThe by keyword enters "tactic mode." Each tactic transforms the proof state until the goal is solved. Lean then converts your tactic script into an actual proof term.
The Interactive Workflow
Proving in Lean is interactive. You write tactics one at a time, and Lean shows you the current proof state after each step:
- State your goal — Write a theorem or lemma with its statement
- Enter tactic mode — Use
by - Read the proof state — The Infoview shows what you need to prove
- Apply a tactic — Transform the goal
- Repeat — Until all goals are solved
- Done — When "No goals" appears, your proof is complete
1-- Watch the proof state change with each tactic2theorem add_example (a b c : Nat) : a + b + c = c + b + a := by3 -- Proof state: ⊢ a + b + c = c + b + a4 omega5 -- Proof state: No goals (proof complete!)Common First Tactics
Here are tactics you'll use constantly:
| Tactic | What it does |
|---|---|
rfl | Proves equality by computation (x = x) |
intro | Introduces variables or assumptions |
exact | Provides the exact proof term |
simp | Simplifies using known lemmas |
omega | Solves linear arithmetic |
decide | Decides by computation |
Deep Dive: The Curry-Howard Correspondence
The idea that "proofs are programs" is called the Curry-Howard correspondence (or isomorphism). It was discovered independently by logicians Haskell Curry and William Howard in the 20th century.
Key correspondences:
- Propositions ↔ Types
- Proofs ↔ Programs (terms)
- Implication (A → B) ↔ Function type
- Conjunction (A ∧ B) ↔ Product type (Pair)
- Disjunction (A ∨ B) ↔ Sum type (Either)
- Universal (∀) ↔ Dependent function (Π)
- Existential (∃) ↔ Dependent pair (Σ)
This is why Lean can be both a programming language and a theorem prover— they're fundamentally the same thing! When you prove a theorem, you're writing a program. When you write a dependently typed program, you're constructing a proof.
A Complete Example
Let's trace through a complete proof to see these concepts in action:
1-- Statement: addition is commutative2theorem add_comm_example : ∀ a b : Nat, a + b = b + a := by3 intro a b -- Let a and b be any Nats4 -- Goal: a + b = b + a5 induction a with -- Prove by induction on a6 | zero => 7 -- Base case: 0 + b = b + 08 simp -- simp knows 0 + b = b and b + 0 = b9 | succ n ih =>10 -- Inductive case: (n + 1) + b = b + (n + 1)11 -- ih: n + b = b + n (induction hypothesis)12 simp [Nat.succ_add, Nat.add_succ] -- Use known lemmas13 exact ih -- Apply induction hypothesis1415-- Now we can use this theorem16#check add_comm_example 3 5 -- : 3 + 5 = 5 + 3Don't worry if this looks complex—we'll build up to it step by step. The important thing is that Lean verified this proof is correct. Every step was machine-checked.
What You'll Learn
In this section on tactics, you'll learn:
- Core tactics: rfl, rw, simp, exact, apply, intro, have
- Automation: omega, decide, aesop—tactics that solve goals automatically
- Case analysis: cases, induction, match—for breaking down problems
- Advanced techniques: calc, conv, combinators—for complex proofs
- Mathlib tactics: ring, linarith, nlinarith—powerful domain-specific tools
Prove a simple equality using rfl.
1example : 3 + 4 = 7 := by2 rfl -- Lean computes 3 + 4 = 7 and verifies itUse omega to prove an inequality.
1example (n : Nat) : n ≤ n + 1 := by2 omega -- omega handles linear arithmetic