Case analysis is the proof pattern for using data that has more than one possible constructor.
Case analysis is the proof pattern for using data that has more than one possible constructor. In logic, it appears most often with disjunctions, existentials, booleans, natural numbers, and inductive propositions. The basic rule is simple: if a hypothesis could have been built in several ways, prove the goal for each possible way.
Problem
You have a hypothesis whose type has constructors, and you want to use the information inside it.
Typical examples are:
h : P ∨ Qh : ∃ x : α, P xn : NatTo use such a hypothesis, split it into its possible cases.
Solution
Use cases.
theorem or_case_analysis
(P Q R : Prop)
(h : P ∨ Q)
(hp : P -> R)
(hq : Q -> R) :
R := by
cases h with
| inl hp_proof =>
exact hp hp_proof
| inr hq_proof =>
exact hq hq_proofThe hypothesis h : P ∨ Q has two possible constructors:
Or.inl : P -> P ∨ Q
Or.inr : Q -> P ∨ QSo cases h creates two branches. In the first branch, Lean gives a proof of P. In the second branch, Lean gives a proof of Q.
Case Analysis on Disjunction
Disjunction is the most common logical example.
theorem or_comm_by_cases
(P Q : Prop) :
P ∨ Q -> Q ∨ P := by
intro h
cases h with
| inl hp =>
right
exact hp
| inr hq =>
left
exact hqIn the inl branch, the original proof came from the left side, so the context contains:
hp : P
⊢ Q ∨ PIn the inr branch, the context contains:
hq : Q
⊢ Q ∨ PEach branch must independently prove the same target.
Case Analysis on Conjunction
A conjunction has only one constructor, but cases is still useful because it exposes the two fields.
theorem and_case_analysis
(P Q : Prop) :
P ∧ Q -> Q ∧ P := by
intro h
cases h with
| intro hp hq =>
constructor
· exact hq
· exact hpHere there is only one branch, because And.intro is the only constructor of conjunction. The branch receives both components:
hp : P
hq : QCase Analysis on Existentials
An existential also has one constructor. Case analysis exposes the witness and the proof.
theorem exists_case_analysis
(P : Nat -> Prop)
(h : ∃ n : Nat, P n) :
∃ m : Nat, P m := by
cases h with
| intro n hn =>
use n
exact hnThe case gives:
n : Nat
hn : P nThe witness is now available as an ordinary local variable.
Case Analysis on False
False has no constructors, so case analysis closes the goal.
theorem false_cases
(P : Prop)
(h : False) :
P := by
cases hThere are no branches to prove. This is why contradiction can prove anything.
Case Analysis on True
True has one constructor and no useful fields.
theorem true_cases
(P : Prop)
(h : True)
(hp : P) :
P := by
cases h
exact hpThe proof still has to prove P. The hypothesis h : True provides no useful data.
Case Analysis on Natural Numbers
Natural numbers have two constructors:
Nat.zero : Nat
Nat.succ : Nat -> NatSo case analysis on n : Nat creates a zero case and a successor case.
theorem nat_cases_example
(n : Nat) :
n = 0 ∨ ∃ k : Nat, n = k + 1 := by
cases n with
| zero =>
left
rfl
| succ k =>
right
use kIn the zero branch, n is replaced by 0. In the successor branch, n is replaced by Nat.succ k.
Case Analysis with rcases
The rcases tactic gives compact patterns for destructuring hypotheses.
theorem rcases_or
(P Q R : Prop)
(h : P ∨ Q)
(hp : P -> R)
(hq : Q -> R) :
R := by
rcases h with hp_proof | hq_proof
· exact hp hp_proof
· exact hq hq_proofFor conjunctions and existentials:
theorem rcases_exists_and
(P Q : Nat -> Prop)
(h : ∃ n : Nat, P n ∧ Q n) :
∃ n : Nat, Q n ∧ P n := by
rcases h with ⟨n, hp, hq⟩
use n
constructor
· exact hq
· exact hpThe pattern:
⟨n, hp, hq⟩unpacks the existential witness and the two conjunction components.
Case Analysis with match
The same reasoning can be written in term mode with pattern matching.
theorem or_comm_match
(P Q : Prop) :
P ∨ Q -> Q ∨ P :=
fun h =>
match h with
| Or.inl hp => Or.inr hp
| Or.inr hq => Or.inl hqThis makes the constructors explicit. Tactic-mode cases and term-mode match perform the same logical split.
Preserving Names
When cases replaces a variable, names can become hard to read. Use branch names and field names intentionally.
theorem nat_cases_named
(n : Nat) :
n = 0 ∨ ∃ k : Nat, n = Nat.succ k := by
cases n with
| zero =>
left
rfl
| succ k =>
right
use kThe name k makes the successor predecessor explicit.
Case Analysis on Decidable Propositions
Classical logic provides case analysis on arbitrary propositions.
open Classical
theorem proposition_cases
(P Q : Prop)
(hp : P -> Q)
(hnp : ¬ P -> Q) :
Q := by
by_cases h : P
· exact hp h
· exact hnp hThe tactic by_cases h : P creates two branches:
h : Pand:
h : ¬ PThis is different from cases h. The proposition P is not being destructed. Instead, classical excluded middle is being used to split on whether P holds.
Case Analysis vs Induction
Case analysis splits only the outer constructor. Induction gives an additional induction hypothesis for recursive data.
For example, case analysis on n : Nat gives:
n = 0or:
n = Nat.succ kbut no statement about k.
Induction on n gives a successor branch with an induction hypothesis about k.
Use cases when the immediate shape is enough. Use induction when proving a property for all recursive values.
Common Patterns
To split a disjunction:
cases h with
| inl hp => ...
| inr hq => ...To unpack a conjunction:
cases h with
| intro hp hq => ...To unpack an existential:
cases h with
| intro x hx => ...To destruct compactly:
rcases h with ⟨x, hx₁, hx₂⟩To split natural numbers:
cases n with
| zero => ...
| succ k => ...To split a decidable proposition:
by_cases h : PCommon Pitfalls
Do not use case analysis when induction is required. If the property must be proved recursively, use induction.
Do not forget that every branch must prove the same final goal.
Do not assume a disjunction gives both sides. Case analysis gives one side in each branch.
Do not confuse cases h with by_cases h : P. The first destructs existing evidence. The second creates a logical split.
Takeaway
Case analysis uses the constructors of a type or proposition. Each constructor gives one proof branch. In each branch, Lean exposes the data carried by that constructor, and the goal must be proved under those assumptions. This pattern is the basis for working with disjunctions, existentials, booleans, natural numbers, and inductive proofs.