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.
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 divide (x : Nat) (y : Nat) (h : y ≠ 0) : Nat := x / y23-- To call this, you must PROVE y ≠ 04#eval divide 10 2 (by decide) -- Works: 55-- #eval divide 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.
Statements are Types
In Lean, mathematical statements are expressed as types:
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) -- PropThese 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:
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 statementJust as 5 : Nat means "5 is a natural number," writing proof : 2 + 2 = 4 means "proof is evidence that 2 + 2 = 4."
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 applicationsTactics 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 definitionThe by keyword enters "tactic mode." Each tactic transforms the proof state until the goal is solved.
The Workflow
Proving in Lean is interactive:
- State your goal — Write a theorem or lemma with its statement
- Enter tactic mode — Use
by - Read the proof state — See what you need to prove
- Apply tactics — Transform the goal step by step
- Close the proof — When no goals remain, you're done
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.
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.
A Complete Example
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 induction a with -- Prove by induction on a5 | zero => 6 simp -- Base case: 0 + b = b + 07 | succ n ih =>8 simp [Nat.succ_add, Nat.add_succ] -- Use known lemmas9 exact ih -- Apply induction hypothesis1011-- Now we can use this theorem12#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.