Working with Existential Quantifiers, Universal Quantifiers, and Classical Reasoning in Lean
This note collects a series of Lean examples from TPiL4, that involve existential quantifiers, universal quantifiers, implications, conjunctions, disjunctions, negation, and classical reasoning. Each example is written in Lean4. When classical logic is used, I specify it.
We assume open Classical at the top to allow classical reasoning such as double negation elimination and byContradiction.
From existence of a witness to a proposition
example : (∃ x : α, r) → r :=
fun h : (∃ x : α, r) =>
Exists.elim h (
fun w (hr:r )=> hr
)
Explanation: If there exists a witness w such that r holds, then r is true. Eliminating the existential gives the witness and the proof of r, so we simply return the proof.
Introducing an existential witness
example (a : α) : r → (∃ x : α, r) :=
fun hr : r => Exists.intro a hr
Explanation: If r holds, we can create an existential statement by choosing any element a and pairing it with the proof of r.
Existential with conjunction distributes over existential and r
example : (∃ x, p x ∧ r) ↔ (∃ x, p x) ∧ r :=
Iff.intro
(fun h : (∃ x, p x ∧ r) =>
Exists.elim h (fun w => fun hpr : p w ∧ r =>
And.intro (Exists.intro w hpr.left) hpr.right))
(fun h : (∃ x, p x) ∧ r =>
Exists.elim h.left (fun w => fun hp : p w =>
Exists.intro w (And.intro hp h.right)))
Explanation: Existential distributes over a conjunction. One direction extracts the witness and splits the conjunction. The other direction reassembles a witness whose property contains both parts.
Existential distributes over disjunction
example : (∃ x, p x ∨ q x) ↔ (∃ x, p x) ∨ (∃ x, q x) :=
Iff.intro
(fun h : (∃ x, p x ∨ q x) =>
Exists.elim h ( fun w => fun hpw : p w ∨ q w =>
Or.elim hpw
(fun hp : p w => Or.inl (Exists.intro w hp))
(fun hq : q w => Or.inr (Exists.intro w hq))
))
(
fun h : (∃ x, p x) ∨ (∃ x, q x) =>
Or.elim h
(fun h1 : (∃ x, p x) =>
Exists.elim h1 (fun w => fun hw : p w => Exists.intro w (Or.inl hw)))
(fun h2 : (∃ x, q x) =>
Exists.elim h2 (fun w => fun hw : q w => Exists.intro w (Or.inr hw)))
)
Explanation: An existential over a disjunction is equivalent to a disjunction of existentials. Both directions are constructive and follow from inspecting which side of the disjunction holds.
Universal quantifier and negation of an existential
example : (∀ x, p x) ↔ ¬ (∃ x, ¬ p x) :=
Iff.intro
(fun h : (∀ x, p x) =>
fun h1 : (∃ x, ¬ p x) =>
Exists.elim h1 (fun w => fun h2 : ¬ p w => show False from h2 (h w)))
(fun h : ¬ (∃ x, ¬ p x) => fun x =>
byContradiction (fun hpx => h ⟨x, hpx⟩))
Explanation: The forward direction is constructive: if everything satisfies p, then no witness of ¬p exists. The reverse direction is classical. It uses byContradiction to convert ¬¬p x into p x. This step needs classical logic.
Double negation elimination
example (t : Prop): ¬¬t → t :=
fun h : ¬¬t => byContradiction (fun ht : ¬t => h ht)
Explanation: Double negation elimination is not constructive. It requires classical logic, and Lean uses byContradiction here.
Existential and double negation
example : (∃ x, p x) ↔ ¬ (∀ x, ¬ p x) :=
Iff.intro
(fun h : (∃ x, p x) => fun h1 : (∀ x, ¬ p x) =>
Exists.elim h (fun w => fun hpw : p w => show False from (h1 w) hpw))
(fun hn : ¬ (∀ x, ¬ p x) =>
byContradiction (fun h : ¬ (∃ x, p x) =>
hn (fun x (hx:p x )=> h ⟨x, hx⟩)))
Explanation: The first direction is constructive. The second direction uses classical reasoning to convert ¬¬∃ x p x into ∃ x p x.
Negation of an existential is equivalent to a universal negation
example : (¬ ∃ x, p x) ↔ (∀ x, ¬ p x) :=
Iff.intro
(fun h : ¬ (∃ x, p x) => fun x (hx: p x) => h ⟨x, hx ⟩)
(fun h : (∀ x, ¬ p x) => fun hex : ∃ x, p x =>
Exists.elim hex (fun w => fun hw : p w => show False from (h w) hw))
Explanation: Both directions are constructive. Eliminating the existential allows you to compute the contradiction directly.
Negating a universal is equivalent to an existential negation
example : (¬ ∀ x, p x) ↔ (∃ x, ¬ p x) :=
Iff.intro
(fun h : ¬ (∀ x, p x) =>
byContradiction (fun hn : ¬ (∃ x, ¬ p x) =>
h (fun x => byContradiction (fun hpx => hn ⟨x, hpx ⟩))
))
(fun h : (∃ x, ¬ p x) => fun hn : (∀ x, p x) =>
Exists.elim h (fun x => fun hpx : ¬ p x => show False from hpx (hn x))
)
Explanation: The right direction is constructive. The left direction uses classical logic because it depends on double negation elimination and the classical equivalence between ¬∀ and ∃¬.
Relationship between universal implication and existential implication
example : (∀ x, p x → r) ↔ (∃ x, p x) → r :=
Iff.intro
(fun h: (∀ x, p x → r) => fun hp : (∃ x, p x) =>
Exists.elim hp (fun w => fun hw : p w => (h w) hw))
(fun h : (∃ x, p x) → r =>
fun w => fun hp : p w => h ⟨w, hp ⟩)
Explanation: Both directions are constructive.
Left to right: If every p w implies r, then an existential witness gives p w, and therefore r.
Right to left: Fix w and assume p w. You can always build an existential witness ⟨w, hp⟩ and then apply h to obtain r. This direction works because an existential can always be introduced from a single p w.
Thanks for reading!