Lecture 3: Proofs with Structure
Date: February 06, 2026
- 1. Introduction
- 2. Conjunction (∧)
- 3. Disjunction (∨)
- 4. Negation and Contradiction
- 5. Existential Quantifiers (∃)
- 6. Universal Quantifiers (∀)
- 7. Bi-implication (↔)
- 8. Uniqueness (
∃!) - 9. Example: Deterministic Automata
- 10. DFA’s computation is fully determined by its input
- 11. Classical Logic, Constructive Logic, and
Decidable - 12. Exercises
1. Introduction
In the previous lectures, we learned the basic syntax of Lean and how to write simple definitions and theorems. Now we begin learning how to build structured mathematical proofs using Lean’s tactic framework.
This lecture focuses on logical connectives and quantifiers:
| Concept | Symbol | Lean Tactics |
|---|---|---|
| Conjunction | ∧ | constructor, cases |
| Disjunction | ∨ | left, right, cases |
| Negation | ¬P | intro, by_contra |
| Implication | P → Q | intro, apply |
| Existential | ∃ x, P x | exists, cases, refine |
| Universal | ∀ x, P x | intro, specialize |
| Iff | P ↔ Q | constructor, apply, have |
| Uniqueness | ∃! x, P x | pair of existence + uniqueness, refine |
These are the building blocks of all later work in TCS formalization.
2. Conjunction (∧)
A conjunction proof P ∧ Q contains two components: a proof of P and a proof of Q.
Constructing a conjunction
example (P Q : Prop) (hP : P) (hQ : Q) : P ∧ Q := by
constructor
· exact hP
· exact hQ
Using a conjunction
We can extract the components using cases.
example (P Q : Prop) (h : P ∧ Q) : Q := by
cases h with
| intro hP hQ =>
exact hQ
3. Disjunction (∨)
To prove a disjunction, choose the appropriate side.
example (P Q : Prop) (hP : P) : P ∨ Q := by
left
exact hP
Using a disjunction:
example (P Q : Prop) (h : P ∨ Q) : (¬P → Q) := by
intro hnotP
cases h with
| inl hP => contradiction
| inr hQ => exact hQ
4. Negation and Contradiction
Negation is defined as:
¬P := P → False
To prove ¬P:
example (P : Prop) (h : P) : ¬¬P := by
intro hnp
apply hnp
exact h
Proof by contradiction: by_contra
example (x : Nat) : x = 0 ∨ x ≠ 0 := by
-- Proof by contradiction using classical logic.
classical
by_contra h
-- Now h : ¬(x = 0 ∨ x ≠ 0)
-- obtain a decidable instance for (x = 0)
-- classical logic and Decidable will be discussed later this lecture
have hx : Decidable (x = 0) := inferInstance
cases hx with
| isTrue hx0 =>
apply h
left
exact hx0
| isFalse hx0 =>
apply h
right
exact hx0
5. Existential Quantifiers (∃)
To prove ∃ x, P x, we provide a witness and a proof.
example : ∃ n : Nat, n + 1 = 5 := by
exists 4
Using an existential:
example (P : Nat → Prop) (h : ∃ x, P x) : True := by
cases h with
| intro x hx =>
trivial
6. Universal Quantifiers (∀)
To prove a universal statement, introduce an arbitrary variable.
example : ∀ n : Nat, n + 0 = n := by
intro n
simp
To use a universal statement:
example (h : ∀ n, n < 10) : 3 < 10 := by
specialize h 3
exact h
7. Bi-implication (↔)
To prove P ↔ P, prove each direction.
example (P : Prop) : P ↔ P := by
constructor
· intro hPf
sorry
· intro hPb
sorry
A more concrete version:
example (a : Nat) : (a = 0) ↔ (a ≤ 0) := by
constructor
· intro h; simp [h]
· intro h; exact le_antisymm h (Nat.zero_le _)
Here is a polished, lecture-quality subsection with a smoother flow, corrected LaTeX, and the example placed first. All math displays now compile correctly in Markdown.
Understood — here is your subsection with your LaTeX left exactly as written, and with a concise explanation of refine added in the correct place.
Nothing in your math blocks or LaTeX has been changed.
8. Uniqueness (∃!)
A statement of the form ∃! x, P x means:
- Existence: there is some
xwithP x - Uniqueness: if
P xandP y, thenx = y
Lean defines ∃! x, P x as:
Example
example : ∃! n : ℕ, n = 0 := by
refine ⟨0, rfl, ?_⟩
intro y hy
simp [hy]
What refine Does
The refine tactic allows you to partially construct the final term of a goal.
It accepts a term that may contain placeholders (?_) and generates new subgoals for each missing piece.
For ∃!, Lean expects three components:
- a witness
n, - a proof that
P nholds, - a proof that any
ysatisfyingP ymust equaln.
The line
refine ⟨0, rfl, ?_⟩
fills in the first two components (0 and rfl) and leaves the third as a new goal.
This is why refine is the natural tool for ∃!, whose definition is a triple of data.
Understanding Lean’s Uniqueness Goal
Running the example produces a uniqueness goal of the form:
∀ (y : ℕ), (fun n => n = 0) y → y = ?w
This goal looks complicated, but each part has a simple meaning:
(fun n => n = 0) yis just Lean’s internal way of writing the predicatey = 0. Lean uses this functional form because the definition of∃!contains a lambda expression.?wis a metavariable representing the witness you supplied earlier usingrefine ⟨w, hw, ?_⟩. Lean will later fill this with0.
Thus the goal is logically equivalent to:
\[\forall y \in \mathbb{N}, (y = 0) \to (y = w).\]After Lean resolves ?w = 0, the goal becomes:
This is discharged by rewriting with the hypothesis:
intro y hy
simp [hy]
This completes the uniqueness part: any number satisfying the predicate must be the same as the chosen witness.
9. Example: Deterministic Automata
Deterministic Automata: Acceptance Is Deterministic
A deterministic finite automaton (DFA) has exactly one possible transition from any state on any input symbol. As a consequence:
Fact. For any input string, a DFA has a unique run. Therefore, if a DFA accepts a string, the accepting run is unique.
This is the simplest setting in which we can later talk about uniqueness (∃!) in a concrete, computational way.
Defining a DFA in Lean
We represent a DFA as a structure consisting of:
- a type of states,
- a start state,
- a deterministic transition function,
- and a Boolean acceptance test.
/-- A tiny deterministic finite automaton (DFA). -/
structure DFA (α : Type) where
State : Type
start : State
step : State → α → State
accept : State → Bool
Here α is the alphabet type, and State is an arbitrary type representing the DFA’s internal states.
Running a DFA
To process an input word, we recursively apply the transition function symbol by symbol.
/-- Run the DFA on a word. -/
def run {α : Type} (M : DFA α) : M.State → List α → M.State
| q, [] => q
| q, a :: w => run M (M.step q a) w
- On the empty input
[], the DFA stays in the current state. - On
a :: w, it takes one deterministic step and continues recursively.
Because the transition function is deterministic, this run is unique.
Acceptance
A word is accepted if the final state reached by run is accepting.
/-- Does the DFA accept the word? -/
def accepts {α : Type} (M : DFA α) (w : List α) : Bool :=
M.accept (run M M.start w)
This is a computable notion of acceptance: it returns a Bool.
A Tiny Alphabet
We now define a minimal alphabet with two symbols.
inductive Bit where
| O | I
deriving Repr, DecidableEq
Opening the namespace lets us write O and I instead of Bit.O and Bit.I.
open Bit
Example 1: Words That End with I
This DFA remembers only the last symbol read.
/-- Accepts exactly the words that end with `I`. -/
def endsWithI : DFA Bit :=
{ State := Bool
, start := false
, step := fun _ b =>
match b with
| O => false
| I => true
, accept := fun st => st
}
- The state is a
Boolindicating whether the last symbol wasI. - Reading
Oalways moves tofalse. - Reading
Ialways moves totrue.
Example evaluations:
#eval accepts endsWithI [O, I, O, I] -- true
#eval accepts endsWithI [I, O] -- false
#eval accepts endsWithI [] -- false
Example 2: Words with an Even Number of I
This DFA tracks the parity of how many Is have been seen so far.
/-- DFA that accepts words with an even number of `I`. -/
def evenIs : DFA Bit :=
{ State := Bool -- false = even, true = odd
, start := false
, step := fun st b =>
match b with
| O => st
| I => !st
, accept := fun st => !st
}
- Reading
Itoggles the state. - Reading
Oleaves the state unchanged. - Acceptance means ending in the “even” state.
Example evaluations:
#eval accepts evenIs [] -- true
#eval accepts evenIs [I, I] -- true
#eval accepts evenIs [O, I, O, I] -- true
#eval accepts evenIs [I, O, O] -- false
10. DFA’s computation is fully determined by its input
theorem run_deterministic {α : Type} (M : DFA α) (q : M.State) (w : List α) :
∃! q' : M.State, q' = run M q w := by
refine ⟨run M q w, rfl, ?_⟩
intro q' hq'
simpa [hq']
1. What does ∃! mean in Lean?
In Lean,
∃! x, P x
is definitionally shorthand for:
There exists an
xsuch thatP x, and for anyy, ifP yholds, theny = x.
Formally (simplified):
∃ x, P x ∧ ∀ y, P y → y = x
So a proof of ∃! x, P x has three parts:
- A witness
x - A proof that
P xholds - A proof that any other
ysatisfyingP yequalsx
2. What is our P here?
In our theorem:
∃! q' : M.State, q' = run M q w
The predicate is:
P q' := q' = run M q w
So we are saying:
There is exactly one state equal to
run M q w.
This is intentionally trivial — and that’s the point. Determinism here comes from function evaluation, not from a deep argument.
3. The refine ⟨ … ⟩ line
refine ⟨run M q w, rfl, ?_⟩
This is constructing the triple required by ∃!.
Part 1: the witness
run M q w
We are saying:
“The unique final state is
run M q w.”
Part 2: existence proof
rfl
This proves:
run M q w = run M q w
which is exactly P (run M q w).
So existence is done.
Part 3: uniqueness proof (the hard-looking part)
We are left with the goal:
⊢ ∀ q' : M.State, q' = run M q w → q' = run M q w
Lean replaces this with a ?_ placeholder, which we now fill.
4. Entering the uniqueness proof
intro q' hq'
This introduces:
- an arbitrary state
q' - a hypothesis
hq' : q' = run M q w
So now the goal is:
⊢ q' = run M q w
But… that’s exactly what hq' already says.
5. The simpa line
simpa [hq']
This tells Lean:
“Rewrite the goal using
hq', and it becomes trivial.”
Indeed, replacing q' by run M q w turns the goal into:
run M q w = run M q w
which Lean closes automatically.
6. One paragraph explanation
To prove uniqueness (
∃!) in Lean, we must give an explicit witness, prove it satisfies the property, and then prove that anything else satisfying the same property must be equal to it. For DFAs, determinism is trivial because the run function already computes a unique final state.
11. Classical Logic, Constructive Logic, and Decidable
Lean is constructive by default, meaning it does not assume classical principles such as the law of excluded middle (P ∨ ¬P) unless they can be justified computationally.
The mechanism that governs this behavior is the Decidable typeclass, which expresses whether Lean can compute whether a proposition is true or false.
Understanding this distinction is essential for structured proofs, especially when using tactics such as by_cases, by_contra, or reasoning about computability in TCS.
The Decidable Typeclass
For any proposition P : Prop, Lean may have an instance of:
Decidable P
which is defined as:
inductive Decidable (P : Prop) : Type
| isTrue : P → Decidable
| isFalse : (¬ P) → Decidable
So a value of type Decidable P contains:
- a proof of
P, or - a proof of
¬P.
This is stronger than a Boolean—Decidable carries logical evidence.
Lean automatically provides Decidable instances for many propositions:
instance : Decidable (x = y)
instance : Decidable (n < m)
instance : Decidable (P ∧ Q)
instance : Decidable (P ∨ Q)
Thus, the following works constructively:
example (x : Nat) : x = 0 ∨ x ≠ 0 := by
by_cases h : x = 0
· left; exact h
· right; exact h
because equality on naturals is decidable.
However, for an undecidable conjecture:
example : GoldbachConjecture ∨ ¬GoldbachConjecture := by
by_cases h : GoldbachConjecture -- ERROR: not decidable
Lean refuses unless classical logic is activated.
Classical Logic in Lean
To enable classical reasoning, write:
classical
This imports a non-computable global instance:
instance (P : Prop) : Decidable P := Classical.decEq _
meaning:
Under
classical, every proposition is treated as decidable.
This unlocks:
by_cases h : Pby_contraem : P ∨ ¬P- classical theorems relying on excluded middle or choice
Example:
example (P : Prop) : ¬¬P → P := by
classical
intro h
by_contra hP
apply h; exact hP
Without classical, the final step (¬¬P → P) is not constructively valid.
Why This Matters in TCS
Constructive reasoning aligns naturally with computability:
- If
Pis decidable, we have an algorithm deciding it. - Proofs become executable procedures.
- Lean can extract algorithms from constructive proofs.
Classical reasoning allows:
- non-constructive existence (e.g., diagonalization arguments),
- pigeonhole arguments,
- cardinality arguments,
- proofs involving infinitely many primes or undecidable problems.
Both styles appear frequently in TCS. Explicitly controlling when you step into classical logic helps maintain the computational meaning of your proofs.
Summary of Logical Worlds in Lean
| Mode | Excluded Middle | by_cases on arbitrary P |
Computable? | How to Enable |
|---|---|---|---|---|
| Constructive | Only when decidable | Only when Decidable P |
Yes | Default |
| Classical | Always | Always | No | classical |
When writing proofs, Lean makes you aware of which world you’re in. This clarity is especially valuable when reasoning about decision procedures, reductions, or undecidability results in theoretical CS.
12. Exercises
Exercise 1
Prove:
theorem my_and_comm (P Q : Prop) : P ∧ Q ↔ Q ∧ P := by
sorry
Exercise 2
Show that for any relation R, if R is symmetric and transitive and there exists x with R x y, then R y x.