{-# 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 pick
to : 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 ∥₋₁