This post is about a problem I recently proved that seemed simple going in, but I struggled with it for quite a while. The problem statement is as follows:
Show that:
[!admonition: WARNING] :warning: This post describes exercise 2.13 of the Homotopy Type Theory book. If you are planning to attempt this problem yourself, spoilers ahead!
The following line imports some of the definitions used in this post.
open import Prelude
open Ξ£
The problem explained
With just that notation, the problem may not be clear. represents the set with only two elements in it, which is the booleans. The elements of are and .
data π : Set where
true : π
false : π
In the problem statement, denotes homotopy equivalence between sets, which you can think of as basically a fancy isomorphism. For some function , we say that is an equivalence1 if:
- there exists a
- is homotopic to the identity map
- is homotopic to the identity map
We can write this in Agda like this:
record isEquiv {A B : Set} (f : A β B) : Set where
constructor mkEquiv
field
g : B β A
fβg : (f β g) βΌ id
gβf : (g β f) βΌ id
Then, the expression simply expresses an equivalence between the boolean set to itself. Hereβs an example of a function that satisfies this equivalence:
bool-id : π β π
bool-id b = b
We can prove that it satisfies the equivalence, by giving it an inverse function (itself), and proving that the inverse is homotopic to identity (which is true definitionally):
bool-id-eqv : isEquiv bool-id
bool-id-eqv = mkEquiv bool-id id-homotopy id-homotopy
where
id-homotopy : (bool-id β bool-id) βΌ id
id-homotopy _ = refl
We can package these together into a single definition that combines the function along with proof of its equivalence properties using a dependent pair2:
_β_ : (A B : Set) β Set
A β B = Ξ£ (A β B) (Ξ» f β isEquiv f)
Hint: (click the to see how itβs defined!)
This gives us an equivalence:
bool-eqv : π β π
bool-eqv = bool-id , bool-id-eqv
Great! Now what?
The space of equivalences
Turns out, the definition I gave above is just one of multiple possible equivalences between the boolean type and itself. Here is another possible definition:
bool-neg : π β π
bool-neg true = false
bool-neg false = true
The remarkable thing about this is that nothing is changed by flipping true and false. Someone using booleans would not be able to tell if you gave them a boolean type where true and false were flipped. Simply put, the constructors true and false are simply names and nothing can distinguish them.
We can show this by proving that bool-neg also forms an equivalence by using
itself as the inverse.
bool-neg-eqv : isEquiv bool-neg
bool-neg-eqv = mkEquiv bool-neg neg-homotopy neg-homotopy
where
neg-homotopy : (bool-neg β bool-neg) βΌ id
neg-homotopy true = refl
neg-homotopy false = refl
bool-eqv2 : π β π
bool-eqv2 = bool-neg , bool-neg-eqv
You could imagine more complicated functions over booleans that may also exhibit this equivalence property. So how many different elements of the type exist?
Proving only has 2 inhabitants
It turns out there are only 2 equivalences, the 2 that we already found. But how do we know this for a fact? Well, if there are only 2 equivalences, that means there are only 2 inhabitants of the type . We can prove that this type only has 2 inhabitants by establishing an equivalence between this type and another type that is known to have 2 inhabitants, such as the boolean type!
Thatβs where the main exercise statement comes in: βshow thatβ , or in other words, find an inhabitant of this type.
main-theorem : (π β π) β π
How do we do this? Well, remember what it means to define an equivalence: first define a function, then show that it is an equivalence. Since equivalences are symmetrical, it doesnβt matter which way we start first. Iβll choose to first define a function . This is fairly easy to do by case-splitting:
f : π β (π β π)
f true = bool-eqv
f false = bool-eqv2
This maps true to the equivalence derived from the identity function, and
false to the function derived from the negation function.
Now we need another function in the other direction. We canβt case-split on
functions, but we can certainly case-split on their output. Specifically, we can
differentiate id from neg by their behavior when being called on true:
g : (π β π) β π
g eqv = (fst eqv) true
-- same as this:
-- let (f , f-is-equiv) = eqv in f true
Hold on. If you know about the cardinality of functions, youβll observe that in the space of functions , there are equivalence classes of functions. So if we mapped 4 functions into 2, how could this be considered an equivalence?
A key observation here is that weβre not mapping from all possible functions . Weβre mapping functions that have already been proven to be equivalences. This means we can count on them to be structure-preserving, and can rule out cases like the function that maps everything to true, since it canβt possibly have an inverse.
Weβll come back to this later.
First, letβs show that . This one is easy, we can
just case-split. Each of the cases reduces to something that is definitionally
equal, so we can use refl.
gβf : (g β f) βΌ id
gβf true = refl
gβf false = refl
Now comes the complicated case: proving .
[!admonition: NOTE] Since Agdaβs comment syntax is
--, the horizontal lines in the code below are simply a visual way of separating out our proof premises from our proof goals.
module fβg-case where
goal :
(eqv : π β π)
--------------------
β f (g eqv) β‘ id eqv
fβg : (f β g) βΌ id
fβg eqv = goal eqv
Now our goal is to show that for any equivalence , applying to it is the same as not doing anything. We can evaluate the a little bit to give us a more detailed goal:
goal2 :
(eqv : π β π)
--------------------------
β f ((fst eqv) true) β‘ eqv
-- Solving goal with goal2, leaving us to prove goal2
goal eqv = goal2 eqv
The problem is if you think about equivalences as encoding some rich data about a function, converting it to a boolean and back is sort of like shoving it into a lower-resolution domain and then bringing it back. How can we prove that the equivalence is still the same equivalence, and as a result proving that there are only 2 possible equivalences?
Note that the function ultimately still only produces 2 values. That means if we want to prove this, we can case-split on βs output. In Agda, this uses a syntax known as with-abstraction:
goal3 :
(eqv : π β π) β (b : π) β (p : fst eqv true β‘ b)
------------------------------------------------
β f b β‘ eqv
-- Solving goal2 with goal3, leaving us to prove goal3
goal2 eqv with b β (fst eqv) true in p = goal3 eqv b p
We can now case-split on , which is the output of calling on the
equivalence returned by . This means that for the true case, we need to
show that (which is based on id) is equivalent to
the equivalence that generated the true.
Letβs start with the id case; we just need to show that for every equivalence
where running the equivalence function on true also returned true, .
Unfortunately, we donβt know if this is true unless our equivalences are mere propositions, meaning if two functions are identical, then so are their equivalences.
Definition 3.3.1 from the HoTT book
Applying this to , we get the property:
This proof is shown later in the book, so I will use it here directly without proof3:
postulate
equiv-isProp : {A B : Set}
β (e1 e2 : A β B) β (Ξ£.fst e1 β‘ Ξ£.fst e2)
-----------------------------------------
β e1 β‘ e2
Now weβre going to need our key observation that we made earlier, that
equivalences must not map both values to a single one.
This way, we can pin the behavior of the function on all inputs by just using
its behavior on true, since its output on false must be different.
We can use a proof that that Iβve shown previously.
trueβ’false : true β’ false
-- read: true β‘ false β β₯
trueβ’false p = bottom-generator p
where
map : π β Set
map true = β€
map false = β₯
bottom-generator : true β‘ false β β₯
bottom-generator p = transport map p tt
Letβs transform this into information about βs output on different inputs:
observation : (f : π β π) β isEquiv f β f true β’ f false
-- read: f true β‘ f false β β₯
observation f (mkEquiv g fβg gβf) p =
let
-- Given p : f true β‘ f false
-- Proof strategy is to call g on it to get (g β f) true β‘ (g β f) false
-- Then, use our equivalence properties to reduce it to true β‘ false
-- Then, apply the lemma trueβ’false we just proved
step1 = ap g p
step2 = sym (gβf true) β step1 β (gβf false)
step3 = trueβ’false step2
in step3
For convenience, letβs rewrite this so that it takes in an arbitrary and returns the version of the inequality we want:
observation' : (f : π β π) β isEquiv f β (b : π) β f b β’ f (bool-neg b)
-- ^^^^^^^^^^^^^^^^^^^^
observation' f eqv true p = observation f eqv p
observation' f eqv false p = observation f eqv (sym p)
Then, solving the true case is simply a matter of using function
extensionality (functions are equal if they are point-wise equal) to show that
just the function part of the equivalences are identical, and then using this
property to prove that the equivalences must be identical as well.
goal3 eqv true p = equiv-isProp bool-eqv eqv functions-equal
where
e = fst eqv
pointwise-equal : (x : π) β e x β‘ x
pointwise-equal true = p
pointwise-equal false with ef β e false in eq = helper ef eq
where
helper : (ef : π) β e false β‘ ef β ef β‘ false
helper true eq = rec-β₯ (observation' e (snd eqv) false (eq β sym p))
helper false eq = refl
-- NOTE: fst bool-eqv = id, definitionally
functions-equal : id β‘ e
functions-equal = sym (funExt pointwise-equal)
The false case is proved similarly.
goal3 eqv false p = equiv-isProp bool-eqv2 eqv functions-equal
where
e = fst eqv
pointwise-equal : (x : π) β e x β‘ bool-neg x
pointwise-equal true = p
pointwise-equal false with ef β e false in eq = helper ef eq
where
helper : (ef : π) β e false β‘ ef β ef β‘ true
helper true eq = refl
helper false eq = rec-β₯ (observation' e (snd eqv) true (p β sym eq))
functions-equal : bool-neg β‘ e
functions-equal = sym (funExt pointwise-equal)
Putting this all together, we get the property we want to prove:
open fβg-case
-- main-theorem : (π β π) β π
main-theorem = g , mkEquiv f gβf fβg
Now that Agdaβs all happy, our work here is done!
Going through all this taught me a lot about how the basics of equivalences work and how to express a lot of different ideas into the type system. Thanks for reading!
Footnotes
-
There are other ways to define equivalences. As weβll show, an important property that is missed by this definition is that equivalences should be mere propositions. The reason why this definition falls short of that requirement is shown by Theorem 4.1.3 in the HoTT book. β©
-
A dependent pair (or -type) is like a regular pair , but where can depend on . For example, . In this case itβs useful since we can carry the equivalence information along with the function itself. This type is rather core to Martin-LΓΆf Type Theory, you can read more about it here. β©
-
This is shown in Theorem 4.2.13 in the HoTT book. I might write a separate post about that when I get there, it seems like an interesting proof as well! β©