Quotient α s
is the same as Quot α r
, but it is specialized to a setoid s
(that is, an equivalence relation) instead of an arbitrary relation.
Prefer Quotient
over Quot
if your relation is actually an equivalence relation.
3.5. Quotients
Quotient types allow a new type to be formed by decreasing the granularity of an existing type's propositional equality.
In particular, given an type A
and an equivalence relation \sim
, the quotient A / \sim
contains the same elements as A
, but every pair of elements that are related by \sim
are considered equal.
Equality is respected universally; nothing in Lean's logic can observe any difference between two equal terms.
Thus, quotient types provide a way to build an impenetrable abstraction barrier.
In particular, all functions from a quotient type must prove that they respect the equivalence relation.
Quotient.{u} {α : Sort u} (s : Setoid α) : Sort u
A proof that two elements of the underlying type are related by the equivalence relation is sufficient to prove that they are equal in the Quotient
.
However, definitional equality is unaffected by the use of Quotient
: two elements in the quotient are definitionally equal if and only if they are definitionally equal in the underlying type.
Quotient types are not widely used in programming. However, they occur regularly in mathematics:
- Integers
The integers are traditionally defined as a pair of natural numbers
(n, k)
that encodes the integern - k
. In this encoding, two integers(n_1, k_1)
and(n_2, k_2)
are equal ifn_1 + k_2 = n_2 + k_1
.- Rational Numbers
The number
\frac{n}{d}
can be encoded as the pair(n, d)
, whered \neq 0
. Two rational numbers\frac{n_1}{d_1}
and\frac{n_2}{d_2}
are equal ifn_1 d_2 = n_2 d_1
.- Real Numbers
The real numbers can be represented as a Cauchy sequence, but this encoding is not unique. Using a quotient type, two Cauchy sequences can be made equal when their difference converges to zero.
- Finite Sets
Finite sets can be represented as lists of elements. With a quotient types, two finite sets can be made equal if they contain the same elements; this definition does not impose any requirements (such as decidable equality or an ordering relation) on the type of elements.
One alternative to quotient types would be to reason directly about the equivalence classes introduced by the relation.
The downside of this approach is that it does not allow computation: in addition to knowing that there is an integer that is the sum of 5 and 8, it is useful for 5 + 8 = 13
to not be a theorem that requires proof.
Defining functions out of sets of equivalence classes relies on non-computational classical reasoning principles, while functions from quotient types are ordinary computational functions that additionally respect an equivalence relation.
3.5.1. Alternatives to Quotient Types
While Quotient
is a convenient way to form quotients with reasonable computational properties, it is often possible to define quotients in other ways.
In general, a type Q
is said to be the quotient of A
by an equivalence relation \sim
if it respects the universal property of quotients: there is a function q:A\to Q
with the property that q(a)=q(b)
if and only if a\sim b
for all a
and b
in A
.
Quotients formed with Quotient
have this property up to propositional equality: elements of A
that are related by \sim
are equal, so they cannot be distinguished.
However, members of the same equivalence class are not necessarily definitionally equal in the quotient.
Quotients may also be implemented by designating a single representative of each equivalence class in A
itself, and then defining Q
as pair of elements in A
with proofs that they are such a canonical representative.
Together with a function that maps each a
in A
to its canonical representative, Q
is a quotient of A
.
Due to proof irrelevance, representatives in Q
of the same equivalence class are definitionally equal.
Such a manually implemented quotient Q
can be easier to work with than Quotient
.
In particular, because each equivalence class is represented by its single canonical representative, there's no need to prove that functions from the quotient respect the equivalence relation.
It can also have better computational properties due to the fact that the computations give normalized values (in contrast, elements of Quotient
can be represented in multiple ways).
Finally, because the manually implemented quotient is an inductive type, it can be used in contexts where other kinds of types cannot, such as when defining a nested inductive type.
However, not all quotients can be manually implemented.
Manually Quotiented Integers
When implemented as pairs of Nat
s, each equivalence class according to the desired equality for integers has a canonical representative in which at least one of the Nat
s is zero.
This can be represented as a Lean structure:
structure Z where
a : Nat
b : Nat
canonical : a = 0 ∨ b = 0
Due to proof irrelevance, every value of this structure type that represents the same integer is already equal.
Constructing a Z
can be made more convenient with a wrapper that uses the fact that subtraction of natural numbers truncates at zero to automate the construction of the proof:
def Z.mk' (n k : Nat) : Z where
a := n - k
b := k - n
canonical := n:Natk:Nat⊢ n - k = 0 ∨ k - n = 0 All goals completed! 🐙
This construction respects the equality demanded of integers:
theorem Z_mk'_respects_eq :
(Z.mk' n k = Z.mk' n' k') ↔ (n + k' = n' + k) := n:Natk:Natn':Natk':Nat⊢ Z.mk' n k = Z.mk' n' k' ↔ n + k' = n' + k
n:Natk:Natn':Natk':Nat⊢ n - k = n' - k' ∧ k - n = k' - n' ↔ n + k' = n' + k
All goals completed! 🐙
To use this type in examples, it's convenient to have Neg
, OfNat
, and ToString
instances.
These instances make it easier to read or write examples.
instance : Neg Z where
neg n := Z.mk' n.b n.a
instance : OfNat Z n where
ofNat := Z.mk' n 0
instance : ToString Z where
toString n :=
if n.a = 0 then
if n.b = 0 then "0"
else s!"-{n.b}"
else toString n.a
#eval (5 : Z)
5
#eval (-5 : Z)
-5
Addition is addition of the underlying Nat
s:
instance : Add Z where
add n k := Z.mk' (n.a + k.a) (n.b + k.b)
#eval (-5 + 22: Z)
17
Because each equivalence class is uniquely represented, there's no need to write a proof that these functions from Z
respect the equivalence relation.
However, in practice, the API for quotients should be implemented for manually-constructed quotients and proved to respect the universal property.
Built-In Integers as Quotients
Lean's built-in integer type Int
satisfies the universal property of quotients, and can thus be thought of as a quotient of pairs of Nat
s.
The canonical representative of each equivalence class can be computed via comparison and subtraction:This toInt
function is called Int.subNatNat
in the standard library.
def toInt (n k : Nat) : Int :=
if n < k then - (k - n : Nat)
else if n = k then 0
else (n - k : Nat)
It satisfies the universal property.
Two pairs of Nat
s are represent the same integer if and only if toInt
computes the same Int
for both pairs:
theorem toInt_sound :
n + k' = k + n' ↔
toInt n k = toInt n' k' := n:Natk':Natk:Natn':Nat⊢ n + k' = k + n' ↔ toInt n k = toInt n' k'
n:Natk':Natk:Natn':Nat⊢ n + k' = k + n' ↔
(if n < k then -↑(k - n) else if n = k then 0 else ↑(n - k)) =
if n' < k' then -↑(k' - n') else if n' = k' then 0 else ↑(n' - k')
n:Natk':Natk:Natn':Nath✝:n < k⊢ n + k' = k + n' ↔ -↑(k - n) = if n' < k' then -↑(k' - n') else if n' = k' then 0 else ↑(n' - k')n:Natk':Natk:Natn':Nath✝:¬n < k⊢ n + k' = k + n' ↔ (if n = k then 0 else ↑(n - k)) = if n' < k' then -↑(k' - n') else if n' = k' then 0 else ↑(n' - k') n:Natk':Natk:Natn':Nath✝:n < k⊢ n + k' = k + n' ↔ -↑(k - n) = if n' < k' then -↑(k' - n') else if n' = k' then 0 else ↑(n' - k')n:Natk':Natk:Natn':Nath✝:¬n < k⊢ n + k' = k + n' ↔ (if n = k then 0 else ↑(n - k)) = if n' < k' then -↑(k' - n') else if n' = k' then 0 else ↑(n' - k') n:Natk':Natk:Natn':Nath✝¹:¬n < kh✝:n = k⊢ n + k' = k + n' ↔ 0 = if n' < k' then -↑(k' - n') else if n' = k' then 0 else ↑(n' - k')n:Natk':Natk:Natn':Nath✝¹:¬n < kh✝:¬n = k⊢ n + k' = k + n' ↔ ↑(n - k) = if n' < k' then -↑(k' - n') else if n' = k' then 0 else ↑(n' - k') n:Natk':Natk:Natn':Nath✝¹:n < kh✝:n' < k'⊢ n + k' = k + n' ↔ -↑(k - n) = -↑(k' - n')n:Natk':Natk:Natn':Nath✝¹:n < kh✝:¬n' < k'⊢ n + k' = k + n' ↔ -↑(k - n) = if n' = k' then 0 else ↑(n' - k')n:Natk':Natk:Natn':Nath✝¹:¬n < kh✝:n = k⊢ n + k' = k + n' ↔ 0 = if n' < k' then -↑(k' - n') else if n' = k' then 0 else ↑(n' - k')n:Natk':Natk:Natn':Nath✝¹:¬n < kh✝:¬n = k⊢ n + k' = k + n' ↔ ↑(n - k) = if n' < k' then -↑(k' - n') else if n' = k' then 0 else ↑(n' - k') All goals completed! 🐙
3.5.2. Setoids
Quotient types are built on setoids. A setoid is a type paired with a distinguished equivalence relation. Unlike a quotient type, the abstraction barrier is not enforced, and proof automation designed around equality cannot be used with the setoid's equivalence relation. Setoids are useful on their own, in addition to being a building block for quotient types.
Setoid.{u} (α : Sort u) : Sort (max 1 u)
A setoid is a type with a distinguished equivalence relation, denoted ≈
.
This is mainly used as input to the Quotient
type constructor.
Instance Constructor
Setoid.mk.{u}
Methods
r : α → α → Prop
x ≈ y
is the distinguished equivalence relation of a setoid.
iseqv : Equivalence Setoid.r
The relation x ≈ y
is an equivalence relation.
3.5.3. Equivalence Relations
An equivalence relation is a relation that is reflexive, symmetric, and transitive.
Equivalence according to some canonical equivalence relation for a type is written using ≈
, which is overloaded using the type class HasEquiv
.
term ::= ...
| `x ≈ y` says that `x` and `y` are equivalent. Because this is a typeclass,
the notion of equivalence is type-dependent.
Conventions for notations in identifiers:
* The recommended spelling of `≈` in identifiers is `equiv`.
term ≈ term
HasEquiv.{u, v} (α : Sort u) : Sort (max u (v + 1))
HasEquiv α
is the typeclass which supports the notation x ≈ y
where x y : α
.
Instance Constructor
HasEquiv.mk.{u, v}
Methods
Equiv : α → α → Sort v
x ≈ y
says that x
and y
are equivalent. Because this is a typeclass,
the notion of equivalence is type-dependent.
Conventions for notations in identifiers:
-
The recommended spelling of
≈
in identifiers isequiv
.
The fact that a relation r
is actually an equivalence relation is stated Equivalence r
.
Equivalence.{u} {α : Sort u} (r : α → α → Prop) : Prop
An equivalence relation ~ : α → α → Prop
is a relation that is:
-
reflexive:
x ~ x
-
symmetric:
x ~ y
impliesy ~ x
-
transitive:
x ~ y
andy ~ z
impliesx ~ z
Equality is an equivalence relation, and equivalence relations share many of
the properties of equality. In particular, Quot α r
is most well behaved
when r
is an equivalence relation, and in this case we use Quotient
instead.
Constructor
Equivalence.mk.{u}
Fields
refl : ∀ (x : α), r x x
An equivalence relation is reflexive: x ~ x
symm : ∀ {x y : α}, r x y → r y x
An equivalence relation is symmetric: x ~ y
implies y ~ x
trans : ∀ {x y z : α}, r x y → r y z → r x z
An equivalence relation is transitive: x ~ y
and y ~ z
implies x ~ z
Every Setoid
instance leads to a corresponding HasEquiv
instance.
3.5.4. Quotient API
The quotient API relies on a pre-existing Setoid
instance.
3.5.4.1. Introducing Quotients
The type Quotient
expects an instance of Setoid
as an ordinary parameter, rather than as an instance implicit parameter.
This helps ensure that the quotient uses the intended equivalence relation.
The instance can be provided either by naming the instance or by using inferInstance
.
A value in the quotient is a value from the setoid's underlying type, wrapped in Quotient.mk
.
Quotient.mk.{u} {α : Sort u} (s : Setoid α) (a : α) : Quotient s
The canonical quotient map into a Quotient
.
Quotient.mk'.{u} {α : Sort u} [s : Setoid α] (a : α) : Quotient s
The canonical quotient map into a Quotient
.
(This synthesizes the setoid by typeclass inference.)
The Integers as a Quotient Type
The integers, defined as pairs of natural numbers where the represented integer is the difference of the two numbers, can be represented via a quotient type.
This representation is not unique: both (4, 7)
and (1, 4)
represent -3
.
Two encoded integers should be considered equal when they are related by Z.eq
:
def Z' : Type := Nat × Nat
def Z.eq (n k : Z') : Prop :=
n.1 + k.2 = n.2 + k.1
This relation is an equivalence relation:
def Z.eq.eqv : Equivalence Z.eq where
refl := ⊢ ∀ (x : Z'), eq x x
x:Naty:Nat⊢ eq (x, y) (x, y)
All goals completed! 🐙
symm := ⊢ ∀ {x y : Z'}, eq x y → eq y x
x:Naty:Natx':Naty':Natheq:eq (x, y) (x', y')⊢ eq (x', y') (x, y)
x:Naty:Natx':Naty':Natheq:x + y' = y + x'⊢ x' + y = y' + x
All goals completed! 🐙
trans := ⊢ ∀ {x y z : Z'}, eq x y → eq y z → eq x z
x:Naty:Natx':Naty':Natx'':Naty'':Nat⊢ eq (x, y) (x', y') → eq (x', y') (x'', y'') → eq (x, y) (x'', y'')
x:Naty:Natx':Naty':Natx'':Naty'':Natheq1:eq (x, y) (x', y')heq2:eq (x', y') (x'', y'')⊢ eq (x, y) (x'', y'')
x:Naty:Natx':Naty':Natx'':Naty'':Natheq1:x + y' = y + x'heq2:x' + y'' = y' + x''⊢ x + y'' = y + x''
All goals completed! 🐙
Thus, it can be used as a Setoid
:
instance Z.instSetoid : Setoid Z' where
r := Z.eq
iseqv := Z.eq.eqv
The type Z
of integers is then the quotient of Z'
by the Setoid
instance:
def Z : Type := Quotient Z.instSetoid
The helper Z.mk
makes it simpler to create integers without worrying about the choice of Setoid
instance:
def Z.mk (n : Z') : Z := Quotient.mk _ n
However, numeric literals are even more convenient.
An OfNat
instance allows numeric literals to be used for integers:
instance : OfNat Z n where
ofNat := Z.mk (n, 0)
3.5.4.2. Eliminating Quotients
Functions from quotients can be defined by proving that a function from the underlying type respects the quotient's equivalence relation.
This is accomplished using Quotient.lift
or its binary counterpart Quotient.lift₂
.
The variants Quotient.liftOn
and Quotient.liftOn₂
place the quotient parameter first rather than last in the parameter list.
Quotient.lift.{u, v} {α : Sort u} {β : Sort v} {s : Setoid α} (f : α → β) : (∀ (a b : α), a ≈ b → f a = f b) → Quotient s → β
Quotient.liftOn.{u, v} {α : Sort u} {β : Sort v} {s : Setoid α} (q : Quotient s) (f : α → β) (c : ∀ (a b : α), a ≈ b → f a = f b) : β
The analogue of Quot.liftOn
: if f : α → β
respects the equivalence relation ≈
,
then it lifts to a function on Quotient s
such that liftOn (mk a) f h = f a
.
Quotient.lift₂.{uA, uB, uC} {α : Sort uA} {β : Sort uB} {φ : Sort uC} {s₁ : Setoid α} {s₂ : Setoid β} (f : α → β → φ) (c : ∀ (a₁ : α) (b₁ : β) (a₂ : α) (b₂ : β), a₁ ≈ a₂ → b₁ ≈ b₂ → f a₁ b₁ = f a₂ b₂) (q₁ : Quotient s₁) (q₂ : Quotient s₂) : φ
Lift a binary function to a quotient on both arguments.
Quotient.liftOn₂.{uA, uB, uC} {α : Sort uA} {β : Sort uB} {φ : Sort uC} {s₁ : Setoid α} {s₂ : Setoid β} (q₁ : Quotient s₁) (q₂ : Quotient s₂) (f : α → β → φ) (c : ∀ (a₁ : α) (b₁ : β) (a₂ : α) (b₂ : β), a₁ ≈ a₂ → b₁ ≈ b₂ → f a₁ b₁ = f a₂ b₂) : φ
Lift a binary function to a quotient on both arguments.
Integer Negation and Addition
Given the encoding Z
of integers as a quotient of pairs of natural numbers, negation can be implemented by swapping the first and second projections:
def neg' : Z' → Z
| (x, y) => .mk (y, x)
This can be transformed into a function from Z
to Z
by proving that negation respects the equivalence relation:
instance : Neg Z where
neg :=
Quotient.lift neg' <| ⊢ ∀ (a b : Z'), a ≈ b → neg' a = neg' b
n:Z'k:Z'equiv:n ≈ k⊢ neg' n = neg' k
n:Z'k:Z'equiv:n ≈ k⊢ (n.snd, n.fst) ≈ (k.snd, k.fst)
n:Z'k:Z'equiv:n.fst + k.snd = n.snd + k.fst⊢ n.snd + k.fst = n.fst + k.snd
All goals completed! 🐙
Similarly, Quotient.lift₂
is useful for defining binary functions from a quotient type.
Addition is defined point-wise:
def add' (n k : Nat × Nat) : Z :=
.mk (n.1 + k.1, n.2 + k.2)
Lifting it to the quotient requires a proof that addition respects the equivalence relation:
instance : Add Z where
add (n : Z) :=
n.lift₂ add' <| n:Z⊢ ∀ (a₁ : Nat × Nat) (b₁ : Z') (a₂ : Nat × Nat) (b₂ : Z'), a₁ ≈ a₂ → b₁ ≈ b₂ → add' a₁ b₁ = add' a₂ b₂
n✝:Zn:Nat × Natk:Z'n':Nat × Natk':Z'⊢ n ≈ n' → k ≈ k' → add' n k = add' n' k'
n✝:Zn:Nat × Natk:Z'n':Nat × Natk':Z'heq:n ≈ n'heq':k ≈ k'⊢ add' n k = add' n' k'
n✝:Zn:Nat × Natk:Z'n':Nat × Natk':Z'heq:n ≈ n'heq':k ≈ k'⊢ (n.fst + k.fst, n.snd + k.snd) ≈ (n'.fst + k'.fst, n'.snd + k'.snd)
n:Zk:Z'n':Nat × Natk':Z'heq':k ≈ k'fst✝:Natsnd✝:Natheq:(fst✝, snd✝) ≈ n'⊢ ((fst✝, snd✝).fst + k.fst, (fst✝, snd✝).snd + k.snd) ≈ (n'.fst + k'.fst, n'.snd + k'.snd); n:Zn':Nat × Natk':Z'fst✝¹:Natsnd✝¹:Natheq:(fst✝¹, snd✝¹) ≈ n'fst✝:Natsnd✝:Natheq':(fst✝, snd✝) ≈ k'⊢ ((fst✝¹, snd✝¹).fst + (fst✝, snd✝).fst, (fst✝¹, snd✝¹).snd + (fst✝, snd✝).snd) ≈ (n'.fst + k'.fst, n'.snd + k'.snd); n:Zk':Z'fst✝²:Natsnd✝²:Natfst✝¹:Natsnd✝¹:Natheq':(fst✝¹, snd✝¹) ≈ k'fst✝:Natsnd✝:Natheq:(fst✝², snd✝²) ≈ (fst✝, snd✝)⊢ ((fst✝², snd✝²).fst + (fst✝¹, snd✝¹).fst, (fst✝², snd✝²).snd + (fst✝¹, snd✝¹).snd) ≈
((fst✝, snd✝).fst + k'.fst, (fst✝, snd✝).snd + k'.snd); n:Zfst✝³:Natsnd✝³:Natfst✝²:Natsnd✝²:Natfst✝¹:Natsnd✝¹:Natheq:(fst✝³, snd✝³) ≈ (fst✝¹, snd✝¹)fst✝:Natsnd✝:Natheq':(fst✝², snd✝²) ≈ (fst✝, snd✝)⊢ ((fst✝³, snd✝³).fst + (fst✝², snd✝²).fst, (fst✝³, snd✝³).snd + (fst✝², snd✝²).snd) ≈
((fst✝¹, snd✝¹).fst + (fst✝, snd✝).fst, (fst✝¹, snd✝¹).snd + (fst✝, snd✝).snd)
n:Zfst✝³:Natsnd✝³:Natfst✝²:Natsnd✝²:Natfst✝¹:Natsnd✝¹:Natfst✝:Natsnd✝:Natheq:fst✝³ + snd✝¹ = snd✝³ + fst✝¹heq':fst✝² + snd✝ = snd✝² + fst✝⊢ fst✝³ + fst✝² + (snd✝¹ + snd✝) = snd✝³ + snd✝² + (fst✝¹ + fst✝)
All goals completed! 🐙
When the function's result type is a subsingleton, Quotient.recOnSubsingleton
or Quotient.recOnSubsingleton₂
can be used to define the function.
Because all elements of a subsingleton are equal, such a function automatically respects the equivalence relation, so there is no proof obligation.
Quotient.recOnSubsingleton.{u, v} {α : Sort u} {s : Setoid α} {motive : Quotient s → Sort v} [h : ∀ (a : α), Subsingleton (motive (Quotient.mk s a))] (q : Quotient s) (f : (a : α) → motive (Quotient.mk s a)) : motive q
The analogue of Quot.recOnSubsingleton
for Quotient
. See Quot.recOnSubsingleton
.
Quotient.recOnSubsingleton₂.{uA, uB, uC} {α : Sort uA} {β : Sort uB} {s₁ : Setoid α} {s₂ : Setoid β} {motive : Quotient s₁ → Quotient s₂ → Sort uC} [s : ∀ (a : α) (b : β), Subsingleton (motive (Quotient.mk s₁ a) (Quotient.mk s₂ b))] (q₁ : Quotient s₁) (q₂ : Quotient s₂) (g : (a : α) → (b : β) → motive (Quotient.mk s₁ a) (Quotient.mk s₂ b)) : motive q₁ q₂
Lift a binary function to a quotient on both arguments.
3.5.4.3. Proofs About Quotients
The fundamental tools for proving properties of elements of quotient types are the soundness axiom and the induction principle.
The soundness axiom states that if two elements of the underlying type are related by the quotient's equivalence relation, then they are equal in the quotient type.
The induction principle follows the structure of recursors for inductive types: in order to prove that a predicate holds all elements of a quotient type, it suffices to prove that it holds for an application of Quotient.mk
to each element of the underlying type.
Because Quotient
is not an inductive type, tactics such as cases
and induction
require that Quotient.ind
be specified explicitly with the using
modifier.
Quotient.sound.{u} {α : Sort u} {s : Setoid α} {a b : α} : a ≈ b → Quotient.mk s a = Quotient.mk s b
The analogue of Quot.sound
: If a
and b
are related by the equivalence relation,
then they have equal equivalence classes.
Quotient.ind.{u} {α : Sort u} {s : Setoid α} {motive : Quotient s → Prop} : (∀ (a : α), motive (Quotient.mk s a)) → ∀ (q : Quotient s), motive q
The analogue of Quot.ind
: every element of Quotient s
is of the form Quotient.mk s a
.
Proofs About Quotients
Given the definition of integers as a quotient type from the prior examples, Quotient.ind
and Quotient.sound
can be used to prove that negation is an additive inverse.
First, Quotient.ind
is used to replace instances of n
with applications of Quotient.mk
.
Having done so, the left side of the equality becomes definitionally equal to a single application of Quotient.mk
, via unfolding definitions and the computation rule for Quotient.lift
.
This makes Quotient.sound
applicable, which yields a new goal: to show that both sides are related by the equivalence relation.
This is provable using simp_arith
.
theorem Z.add_neg_inverse (n : Z) : n + (-n) = 0 := n:Z⊢ n + -n = 0
a✝:Z'⊢ Quotient.mk instSetoid a✝ + -Quotient.mk instSetoid a✝ = 0
a✝:Z'⊢ (a✝.fst + (a✝.snd, a✝.fst).fst, a✝.snd + (a✝.snd, a✝.fst).snd) ≈ (0, 0)
All goals completed! 🐙
For more specialized use cases, Quotient.rec
, Quotient.recOn
, and Quotient.hrecOn
can be used to define dependent functions from a quotient type to a type in any other universe.
Stating that a dependent function respects the quotient's equivalence relation requires a means of dealing with the fact that the dependent result type is instantiated with different values from the quotient on each side of the equality.
Quotient.rec
and Quotient.recOn
use the Quotient.sound
to equate the related elements, inserting the appropriate cast into the statement of equality, while Quotient.hrecOn
uses heterogeneous equality.
Quotient.rec.{u, v} {α : Sort u} {s : Setoid α} {motive : Quotient s → Sort v} (f : (a : α) → motive (Quotient.mk s a)) (h : ∀ (a b : α) (p : a ≈ b), ⋯ ▸ f a = f b) (q : Quotient s) : motive q
Quotient.recOn.{u, v} {α : Sort u} {s : Setoid α} {motive : Quotient s → Sort v} (q : Quotient s) (f : (a : α) → motive (Quotient.mk s a)) (h : ∀ (a b : α) (p : a ≈ b), ⋯ ▸ f a = f b) : motive q
The analogue of Quot.recOn
for Quotient
. See Quot.recOn
.
Quotient.hrecOn.{u, v} {α : Sort u} {s : Setoid α} {motive : Quotient s → Sort v} (q : Quotient s) (f : (a : α) → motive (Quotient.mk s a)) (c : ∀ (a b : α), a ≈ b → HEq (f a) (f b)) : motive q
The analogue of Quot.hrecOn
for Quotient
. See Quot.hrecOn
.
If two elements of a type are equal in a quotient, then they are related by the setoid's equivalence relation.
This property is called Quotient.exact
.
Quotient.exact.{u} {α : Sort u} {s : Setoid α} {a b : α} : Quotient.mk s a = Quotient.mk s b → a ≈ b
3.5.5. Logical Model
Like functions and universes, quotient types are a built-in feature of Lean's type system.
However, the underlying primitives are based on the somewhat simpler Quot
type rather than on Quotient
, and Quotient
is defined in terms of Quot
.
The primary difference is that Quot
is based on an arbitrary relation, rather than a Setoid
instance.
The provided relation need not be an equivalence relation; the rules that govern Quot
and Eq
automatically extend the provided relation into its reflexive, transitive, symmetric closure.
When the relation is already an equivalence relation, Quotient
should be used instead of Quot
so Lean can make use of the fact that the relation is an equivalence relation.
The fundamental quotient type API consists of Quot
, Quot.mk
, Quot.lift
, Quot.ind
, and Quot.sound
.
These are used in the same way as their Quotient
-based counterparts.
Quot.{u} {α : Sort u} (r : α → α → Prop) : Sort u
Let α
be any type, and let r
be an equivalence relation on α
.
It is mathematically common to form the "quotient" α / r
, that is, the type of
elements of α
"modulo" r
. Set theoretically, one can view α / r
as the set
of equivalence classes of α
modulo r
. If f : α → β
is any function that
respects the equivalence relation in the sense that for every x y : α
,
r x y
implies f x = f y
, then f "lifts" to a function f' : α / r → β
defined on each equivalence class ⟦x⟧
by f' ⟦x⟧ = f x
.
Lean extends the Calculus of Constructions with additional constants that
perform exactly these constructions, and installs this last equation as a
definitional reduction rule.
Given a type α
and any binary relation r
on α
, Quot r
is a type. Note
that r
is not required to be an equivalence relation. Quot
is the basic
building block used to construct later the type Quotient
.
Quot.mk.{u} {α : Sort u} (r : α → α → Prop) (a : α) : Quot r
Quot.lift.{u, v} {α : Sort u} {r : α → α → Prop} {β : Sort v} (f : α → β) (a : ∀ (a b : α), r a b → f a = f b) : Quot r → β
Given a type α
, any binary relation r
on α
, a function f : α → β
, and a proof h
that f
respects the relation r
, then Quot.lift f h
is the corresponding function on Quot r
.
The idea is that for each element a
in α
, the function Quot.lift f h
maps Quot.mk r a
(the r
-class containing a
) to f a
, wherein h
shows that this function is well defined.
In fact, the computation principle is declared as a reduction rule.
Quot.ind.{u} {α : Sort u} {r : α → α → Prop} {β : Quot r → Prop} (mk : ∀ (a : α), β (Quot.mk r a)) (q : Quot r) : β q
Quot.sound.{u} {α : Sort u} {r : α → α → Prop} {a b : α} : r a b → Quot.mk r a = Quot.mk r b
The quotient axiom, or at least the nontrivial part of the quotient
axiomatization. Quotient types are introduced by the init_quot
command
in Init.Prelude
which introduces the axioms:
opaque Quot {α : Sort u} (r : α → α → Prop) : Sort u opaque Quot.mk {α : Sort u} (r : α → α → Prop) (a : α) : Quot r opaque Quot.lift {α : Sort u} {r : α → α → Prop} {β : Sort v} (f : α → β) : (∀ a b : α, r a b → f a = f b) → Quot r → β opaque Quot.ind {α : Sort u} {r : α → α → Prop} {β : Quot r → Prop} : (∀ a : α, β (Quot.mk r a)) → ∀ q : Quot r, β q
All of these axioms are true if we assume Quot α r = α
and Quot.mk
and
Quot.lift
are identity functions, so they do not add much. However this axiom
cannot be explained in that way (it is false for that interpretation), so the
real power of quotient types come from this axiom.
It says that the quotient by r
maps elements which are related by r
to equal
values in the quotient. Together with Quot.lift
which says that functions
which respect r
can be lifted to functions on the quotient, we can deduce that
Quot α r
exactly consists of the equivalence classes with respect to r
.
It is important to note that r
need not be an equivalence relation in this axiom.
When r
is not an equivalence relation, we are actually taking a quotient with
respect to the equivalence relation generated by r
.
In addition to the above constants, Lean's kernel contains a reduction rule for Quot.lift
that causes it to reduce when used with Quot.mk
, analogous to ι-reduction for inductive types.
Given a relation r
over α
, a function f
from α
to β
, and a proof resp
that f
respects r
, the term Quot.lift f resp (Quot.mk r x)
is definitionally equal to f x
.
variable
(r : α → α → Prop)
(f : α → β)
(ok : ∀ x y, r x y → f x = f y)
(x : α)
example : Quot.lift f ok (Quot.mk r x) = f x := rfl
Quot.liftOn
is an version of Quot.lift
that takes the quotient type's value first, by analogy to Quotient.liftOn
.
Quot.liftOn.{u, v} {α : Sort u} {β : Sort v} {r : α → α → Prop} (q : Quot r) (f : α → β) (c : ∀ (a b : α), r a b → f a = f b) : β
Quot.liftOn q f h
is the same as Quot.lift f h q
. It just reorders
the argument q : Quot r
to be first.
Because Quot
is not an inductive type, types implemented as quotients may not occur around nested occurrences in inductive type declarations.
These types declarations must be rewritten to remove the nested quotient, which can often be done by defining a quotient-free version and then separately defining an equivalence relation that implements the desired equality relation.
Nested Inductive Types and Quotients
The nested inductive type of rose trees nests the recursive occurrence of RoseTree
under List
:
inductive RoseTree (α : Type u) where
| leaf : α → RoseTree α
| branch : List (RoseTree α) → RoseTree α
However, taking a quotient of the List
that identifies all elements in the style of squash types causes Lean to reject the declaration:
inductive SetTree (α : Type u) where
| leaf : α → SetTree α
| branch :
Quot (fun (xs ys : List (SetTree α)) => True) →
SetTree α
(kernel) arg #2 of 'SetTree.branch' contains a non valid occurrence of the datatypes being declared
Lean also provides convenient elimination from Quot
into any subsingleton without further proof obligations, along with dependent elimination principles that correspond to those used for Quotient
.
Quot.recOnSubsingleton.{u, v} {α : Sort u} {r : α → α → Prop} {motive : Quot r → Sort v} [h : ∀ (a : α), Subsingleton (motive (Quot.mk r a))] (q : Quot r) (f : (a : α) → motive (Quot.mk r a)) : motive q
Dependent induction principle for a quotient, when the target type is a Subsingleton
.
In this case the quotient's side condition is trivial so any function can be lifted.
Quot.rec.{u, v} {α : Sort u} {r : α → α → Prop} {motive : Quot r → Sort v} (f : (a : α) → motive (Quot.mk r a)) (h : ∀ (a b : α) (p : r a b), ⋯ ▸ f a = f b) (q : Quot r) : motive q
Dependent recursion principle for Quot
. This constructor can be tricky to use,
so you should consider the simpler versions if they apply:
-
Quot.lift
, for nondependent functions -
Quot.ind
, for theorems / proofs of propositions about quotients -
Quot.recOnSubsingleton
, when the target type is aSubsingleton
-
Quot.hrecOn
, which usesHEq (f a) (f b)
instead of asound p ▸ f a = f b
assummption
Quot.recOn.{u, v} {α : Sort u} {r : α → α → Prop} {motive : Quot r → Sort v} (q : Quot r) (f : (a : α) → motive (Quot.mk r a)) (h : ∀ (a b : α) (p : r a b), ⋯ ▸ f a = f b) : motive q
Dependent recursion principle for Quot
. This constructor can be tricky to use,
so you should consider the simpler versions if they apply:
-
Quot.lift
, for nondependent functions -
Quot.ind
, for theorems / proofs of propositions about quotients -
Quot.recOnSubsingleton
, when the target type is aSubsingleton
-
Quot.hrecOn
, which usesHEq (f a) (f b)
instead of asound p ▸ f a = f b
assummption
Quot.hrecOn.{u, v} {α : Sort u} {r : α → α → Prop} {motive : Quot r → Sort v} (q : Quot r) (f : (a : α) → motive (Quot.mk r a)) (c : ∀ (a b : α), r a b → HEq (f a) (f b)) : motive q
Heterogeneous dependent recursion principle for a quotient.
This may be easier to work with since it uses HEq
instead of
an Eq.ndrec
in the hypothesis.
3.5.6. Quotients and Function Extensionality
Because Lean's definitional equality includes a computational reduction rule for Quot.lift
, quotient types are used in the standard library to prove function extensionality, which would need to be an axiom otherwise.
This is done by first defining a type of functions quotiented by extensional equality, for which extensional equality holds by definition.
variable {α : Sort u} {β : α → Sort v}
def extEq (f g : (x : α) → β x) : Prop :=
∀ x, f x = g x
def ExtFun (α : Sort u) (β : α → Sort v) :=
Quot (@extEq α β)
Extensional functions can be applied just like ordinary functions. Application respects extensional equality by definition: if applying to functions gives equal results, then applying them gives equal results.
def extApp
(f : ExtFun α β)
(x : α) :
β x :=
f.lift (· x) fun g g' h => α:Sort uβ:α → Sort vf:ExtFun α βx:αg:(x : α) → β xg':(x : α) → β xh:extEq g g'⊢ (fun x_1 => x_1 x) g = (fun x_1 => x_1 x) g'
All goals completed! 🐙
To show that two functions that are extensionally equal are in fact equal, it suffices to show that the functions that result from extensionally applying the corresponding extensional functions are equal. This is because
extApp (Quot.mk _ f)
is definitionally equal to
fun x => (Quot.mk extEq f).lift (· x) (fun _ _ h => h x)
which is definitionally equal to fun x => f x
, which is definitionally equal (by η-equivalence) to f
.
A propositional version of the computation rule for Quot.lift
would not suffice, because the reducible expression occurs in the body of a function and rewriting by an equality in a function would already require function extensionality.
From here, it is enough to show that the extensional versions of the two functions are equal.
This is true due to Quot.sound
: the fact that they are in the quotient's equivalence relation is an assumption.
This proof is a much more explicit version of the one in the standard library:
theorem funext'
{f g : (x : α) → β x}
(h : ∀ x, f x = g x) :
f = g := α:Sort uβ:α → Sort vf:(x : α) → β xg:(x : α) → β xh:∀ (x : α), f x = g x⊢ f = g
suffices extApp (Quot.mk _ f) = extApp (Quot.mk _ g) α:Sort uβ:α → Sort vf:(x : α) → β xg:(x : α) → β xh:∀ (x : α), f x = g xthis:extApp (Quot.mk extEq f) = extApp (Quot.mk extEq g)⊢ f = g
α:Sort uβ:α → Sort vf:(x : α) → β xg:(x : α) → β xh:∀ (x : α), f x = g xthis:(fun x => Quot.lift (fun x_1 => x_1 x) ⋯ (Quot.mk extEq f)) = fun x => Quot.lift (fun x_1 => x_1 x) ⋯ (Quot.mk extEq g)⊢ f = g
α:Sort uβ:α → Sort vf:(x : α) → β xg:(x : α) → β xh:∀ (x : α), f x = g xthis:(fun x => f x) = fun x => g x⊢ f = g
All goals completed! 🐙
suffices Quot.mk extEq f = Quot.mk extEq g α:Sort uβ:α → Sort vf:(x : α) → β xg:(x : α) → β xh:∀ (x : α), f x = g xthis:Quot.mk extEq f = Quot.mk extEq g⊢ extApp (Quot.mk extEq f) = extApp (Quot.mk extEq g)
α:Sort uβ:α → Sort vf:(x : α) → β xg:(x : α) → β xh:∀ (x : α), f x = g xthis:Quot.mk extEq f = Quot.mk extEq g⊢ Quot.mk extEq f = Quot.mk extEq g
All goals completed! 🐙
α:Sort uβ:α → Sort vf:(x : α) → β xg:(x : α) → β xh:∀ (x : α), f x = g x⊢ extEq f g
All goals completed! 🐙
3.5.7. Squash Types
Squash types are a quotient by the relation that relates all elements, transforming it into a subsingleton.
In other words, if α
is inhabited, then Squash α
has a single element, and if α
is uninhabited, then Squash α
is also uninhabited.
Unlike Nonempty α
, which is a proposition stating that α
is inhabited and is thus represented by a dummy value at runtime, Squash α
is a type that is represented identically to α
.
Because Squash α
is in the same universe as α
, it is not subject to the restrictions on computing data from propositions.
Squash.{u} (α : Sort u) : Sort u
Squash α
is the quotient of α
by the always true relation.
It is empty if α
is empty, otherwise it is a singleton.
(Thus it is unconditionally a Subsingleton
.)
It is the "universal Subsingleton
" mapped from α
.
It is similar to Nonempty α
, which has the same properties, but unlike
Nonempty
this is a Type u
, that is, it is "data", and the compiler
represents an element of Squash α
the same as α
itself
(as compared to Nonempty α
, whose elements are represented by a dummy value).
Squash.lift
will extract a value in any subsingleton β
from a function on α
,
while Nonempty.rec
can only do the same when β
is a proposition.