{-# OPTIONS --without-K --rewriting #-} open import new-prelude module Lecture4-notes where
The circle
In general, a type should be thought of as an infinity-groupoid: it has points, paths (or identifications, equalities, depending on what you want to call them), and then paths between paths, and so on. The regular inductive types that we’ve, like booleans or natural numbers, have constructors only for points. Higher inductive types (HITs) also have constructors for paths, paths between paths, etc.
A good first example is the circle, which we will write as S1. As as a higher inductive type, it is presented as “the free infinity groupoid generated by a point and a loop at that point”.
postulate S1 : Type base : S1 loop : base ≡ base
base is a point constructor, like zero for the natural numbers or true and false for the booleans. loop is a path constructor, which means that instead of constructing an element of S1, it constructs a path in S1 from base to base. We represent this using the identity type ≡. Since Agda doesn’t have HITs built-in (until we get to Cubical Agda in Lecture 7), we will “postulate” these, which means to assume constants of these types for the rest of the development. A postulate is roughly the same as making the whole rest of the codebase depend on a variable of that type.
Recall from previous lectures that for any type we can define operations of path concatenation and path inverses. We will write concatenation as p ∙ q and inverse as ! p. (This was written (-)⁻¹ in previous lectures, but I find a prefix notation easier to read when Agda displays terms.) You can review the definitions in the prelude.
Using these we can construct other paths on the circle from loop:
example-path : base ≡ base example-path = loop ∙ ! loop ∙ loop ∙ ! loop ∙ loop
There should be a homotopy between example (forwards, backwards, forwards, backwards, forwards) and loop (forwards). To define it, we can generalize and prove the groupoid laws for paths.
Groupoid laws for paths
If you’ve seen some abstract algebra, you know that the group laws say that the “multiplication” of the group is associative and has the neutral element as a unit on both sides, and that the inverse of an element multiplied with that element is the neutral element. The groupoid laws for paths are a “typed” version of these. They say that the identity path (refl) is a unit for path concatenation, path concatenation is associative, and the inverse of a path composed with that path is the identity. Let’s state and prove them.
∙unit-r : {A : Type} {x y : A} → (p : x ≡ y) → (p ∙ refl _) ≡ p ∙unit-r p = refl _ ∙unit-l : {A : Type} {x y : A} → (p : x ≡ y) → (refl _ ∙ p) ≡ p ∙unit-l (refl _) = refl _ ∙assoc : {A : Type} {x y z w : A} (p : x ≡ y) (q : y ≡ z) (r : z ≡ w) → p ∙ (q ∙ r) ≡ (p ∙ q) ∙ r ∙assoc p (refl _) (refl _) = (refl _) !-inv-l : {A : Type} {x y : A} → (p : x ≡ y) → (! p ∙ p) ≡ refl _ !-inv-l (refl _) = refl _ !-inv-r : {A : Type} {x y : A} → (p : x ≡ y) → (p ∙ ! p) ≡ refl _ !-inv-r (refl _) = refl _
It’s kind of amazing that all of this structure emerges from a very simple axiom (path induction!).
OK, now we can use these to construct a path/homotopy between the path above and loop:
example : (loop ∙ ! loop ∙ loop ∙ ! loop ∙ loop) ≡ loop [ base ≡ base ] example = loop ∙ ! loop ∙ loop ∙ ! loop ∙ loop ≡⟨ refl _ ⟩ (((loop ∙ ! loop) ∙ loop) ∙ ! loop) ∙ loop ≡⟨ ap (\ H → H ∙ loop ∙ ! loop ∙ loop) (!-inv-r loop) ⟩ refl _ ∙ loop ∙ ! loop ∙ loop ≡⟨ ap (\ H → H ∙ ! loop ∙ loop) (∙unit-l (loop)) ⟩ loop ∙ ! loop ∙ loop ≡⟨ ! (∙assoc _ (! loop) loop) ⟩ loop ∙ (! loop ∙ loop) ≡⟨ ap (\ H → loop ∙ H) (!-inv-l loop) ⟩ loop ∙ (refl _) ≡⟨ refl _ ⟩ loop ∎
We’ll use the notation x ≡ y [ A ] if we want to make the type a path is in explicit. We’ll sometimes write ( x → e) for (λ x → e) because it easier to type and de-emphasizes the function a little (since ap almost always needs a lambda abstraction as input, it would IMO be nicer to have a syntax with a bound variable like ap (H → …) p).
Up to homotopy, there should be ℤ many paths on the circle: … , ! loop ∙ ! loop, ! loop, refl, loop, loop ∙ loop, …
Circle “recursion”
Terminology note: for an inductive type like booleans, natural numbers, lists, etc., it is common to call the dependent elimination principle the “elimination” or “induction” principle, and the non-dependent elimination principle the “recursion” principle. This makes perfect sense for natural numbers, but the analogy gets a little strained for types like booleans. Since bool isn’t properly inductive (there are no constructors for bool that take a boolean as input, like successor for natural numbers), the “recursion” principle is just if-then-else, and the “induction” principle is just proof by case analysis. Nonetheless, for uniformity, we will refer to non-dependent elimination rules as “recursion principles” even when the type is not properly inductive.
With this in mind, the “recursion” principle for the circle expresses that it is the initial type with a point and a loop at that point: given any other type with a point and a loop, we can map the circle into it (in a unique way, but we’ll get to that later).
postulate S1-rec : {l : Level} {X : Type l} (x : X) (p : x ≡ x) → S1 → X S1-rec-base : {l : Level} {X : Type l} (x : X) (p : x ≡ x) → S1-rec {l} {X} x p base ≡ x S1-rec-loop' : {l : Level} {X : Type l} (x : X) (p : x ≡ x) → ap (S1-rec x p) loop ≡ (S1-rec-base x p) ∙ p ∙ ! (S1-rec-base x p)
The base and loop postulates are the reduction rules for S1-rec. Note that we use ap to apply S1-rec to the loop.
It’s common to assume that at least the point reductions like S1-rec-base are definitional equalities. We can do this in Agda like this:{-# BUILTIN REWRITE _≡_ #-} {-# REWRITE S1-rec-base #-}Then, for example, we don’t need to explicitly mention S1-rec-base to state the loop reduction.
postulate S1-rec-loop : {l : Level} {X : Type l} (x : X) (p : x ≡ x) → ap (S1-rec x p) loop ≡ p
This is mainly for convenience, but it’s a big convenience! When we get to Cubical Agda, both of these reductions will be definitional, but for axiomatic HoTT we only put in the point one.
Doubling function
As a first example of circle recursion, we can define the function from the circle to the circle that sends the loop to the concatenation of two loops. Thinking of the paths as integers, this sends x to 2*x.
double : S1 → S1 double = S1-rec base (loop ∙ loop) calculate-double-loop : ap double loop ≡ (loop ∙ loop) calculate-double-loop = S1-rec-loop _ _
How do we “reduce” this function on two loops? We need a general fact that functions are functors, which in particular means that they are group homomorphisms on paths, seen as groups with composition as the group operation.
ap-∙ : {A B : Type} {f : A → B} {x y z : A} (p : x ≡ y) (q : y ≡ z) → ap f (p ∙ q) ≡ ap f p ∙ ap f q ap-∙ (refl _) (refl _) = refl _ calculate-double-2-loops : ap double (loop ∙ loop) ≡ loop ∙ loop ∙ loop ∙ loop calculate-double-2-loops = ap double (loop ∙ loop) ≡⟨ ap-∙ loop loop ⟩ ap double loop ∙ ap double loop ≡⟨ ap₂ (\ p q → p ∙ q) (S1-rec-loop _ _) (S1-rec-loop _ _) ⟩ (loop ∙ loop) ∙ (loop ∙ loop) ≡⟨ (∙assoc (loop ∙ loop) loop loop ) ⟩ ((loop ∙ loop) ∙ loop) ∙ loop ∎
2 point version of the circle
We can also “subdivide” the circle into two points with two paths between them:
postulate Circle2 : Type north : Circle2 south : Circle2 west : north ≡ south east : north ≡ south Circle2-rec : {X : Type} (n : X) (s : X) (w : n ≡ s) (e : n ≡ s) → Circle2 → X Circle2-rec-north : {X : Type} (n : X) (s : X) (w : n ≡ s) (e : n ≡ s) → Circle2-rec n s w e north ≡ n Circle2-rec-south : {X : Type} (n : X) (s : X) (w : n ≡ s) (e : n ≡ s) → Circle2-rec n s w e south ≡ s {-# REWRITE Circle2-rec-north #-} {-# REWRITE Circle2-rec-south #-} postulate Circle2-rec-west : {X : Type} (n : X) (s : X) (w : n ≡ s) (e : n ≡ s) → ap (Circle2-rec n s w e) west ≡ w Circle2-rec-east : {X : Type} (n : X) (s : X) (w : n ≡ s) (e : n ≡ s) → ap (Circle2-rec n s w e) east ≡ e
Let’s work towards showing that these two definitions of the circle give equivalent types. This means defining functions back and forth whose composites are homotopic to the identity function. But inside type theory, this looks just like giving a “isomorphism” in the sense defined in the Lecture 3 exercises.
First, to map the 1-point circle to the 2-point circle, we need a point and a loop. Of course, there are lots of choices, but let’s pickto : S1 → Circle2 to = S1-rec north (east ∙ ! west)In the other direction, we need to map north and south each to some point of the circle, but we’ve only got one point, base. For the paths, we can go “twice as fast” on one and “stand still” on the other.
from : Circle2 → S1 from = Circle2-rec base base (refl base) loop
We can check that on the constructors, the composite from-then-to is the identity:
from-to-north : to (from north) ≡ north from-to-north = refl _ from-to-south : to (from south) ≡ south from-to-south = west from-to-west : ap to (ap from west) ∙ from-to-south ≡ west from-to-west = ap to (ap from west) ∙ west ≡⟨ ap (\ H → ap to H ∙ west) (Circle2-rec-west _ _ _ _) ⟩ ap to (refl base) ∙ west ≡⟨ ∙unit-l west ⟩ west ∎ from-to-east : ap to (ap from east) ∙ from-to-south ≡ east from-to-east = ap to (ap from east) ∙ west ≡⟨ ap (\ H → ap to H ∙ west) (Circle2-rec-east _ _ _ _) ⟩ ap to loop ∙ west ≡⟨ ap (\ H → H ∙ west) (S1-rec-loop _ _) ⟩ east ∙ ! west ∙ west ≡⟨ ! (∙assoc _ (! west) west) ⟩ east ∙ (! west ∙ west) ≡⟨ ap (\ H → east ∙ H) (!-inv-l west) ⟩ east ∎
Note that it doesn’t even type check to ask that ap to (ap from west) ≡ west, because to (from south) ≡ north. So we need to compare them “up to” a path. We’ll talk about why (- ∙ from-to-south) is the right path to pick next time.
The torus
The torus is a nice example of a type with a path-between-paths constructor:
postulate Torus : Type baseT : Torus pT : baseT ≡ baseT qT : baseT ≡ baseT sT : pT ∙ qT ≡ qT ∙ pT T-rec : {l : Level} {X : Type l} (x : X) (p : x ≡ x) (q : x ≡ x) (s : p ∙ q ≡ q ∙ p) → Torus → X T-rec-base : {l : Level} {X : Type l} (x : X) (p : x ≡ x) (q : x ≡ x) (s : p ∙ q ≡ q ∙ p) → T-rec {l} {X} x p q s baseT ≡ x {-# REWRITE T-rec-base #-} postulate T-rec-pT : {l : Level} {X : Type l} (x : X) (p : x ≡ x) (q : x ≡ x) (s : p ∙ q ≡ q ∙ p) → ap (T-rec x p q s) pT ≡ p T-rec-qT : {l : Level} {X : Type l} (x : X) (p : x ≡ x) (q : x ≡ x) (s : p ∙ q ≡ q ∙ p) → ap (T-rec x p q s) qT ≡ q -- there needs to be an analogous reduction for sT, but it's a little tricky to state it, -- so we will defer it for now
Lots of other important concepts can be defined with HITs, including quotients, suspensions, pushouts, truncations.
Suspensions
postulate Susp : Type → Type northS : {A : Type} → Susp A southS : {A : Type} → Susp A merid : {A : Type} → A → northS ≡ southS [ Susp A ] Susp-rec : {l : Level} {A : Type} {X : Type l} (n : X) (s : X) (m : A → n ≡ s) → Susp A → X
Pushouts
postulate Pushout : (C : Type) (A : Type) (B : Type) (f : C → A) (g : C → B) → Type module _ {C : Type} {A : Type} {B : Type} {f : C → A} {g : C → B} where postulate inl : A → Pushout C A B f g inr : B → Pushout C A B f g glue : (c : C) → inl (f c) ≡ inr (g c) Push-rec : {X : Type} (l : A → X) (r : B → X) (gl : (c : C) → l (f c) ≡ r (g c)) → Pushout C A B f g → X
Relation quotient
Suppressing some details, think of this as the quotient of A by the equivalence relation closure of R (technically, this description is precisely right when R(x,y) is a proposition and A is a set; otherwise, you need to think about the higher homotopies induced by the constructors).
postulate _/_ : (A : Type) → (A → A → Type) → Type i : {A : Type} {R : A → A → Type} → A → A / R q : {A : Type} {R : A → A → Type} (x y : A) → R x y → i x ≡ i y [ A / R ]
Propositional truncation
The universal way of making a type into a -1-type. This is a nice example of a (properly) inductive HIT.postulate ∥_∥₋₁ : Type → Type ∣_∣ : {A : Type} → A → ∥ A ∥₋₁ trunc : {A : Type} → is-prop ∥ A ∥₋₁