module Cat.Base where

Precategories🔗

In univalent mathematics, it makes sense to distinguish two stages in the construction of categories: A precategory is the object that directly corresponds to the definition of precategory as it is traditionally formalised, whereas a category (or univalent category) has an extra condition: Isomorphic objects must be identified.

record Precategory (o h : Level) : Type (lsuc (o ⊔ h)) where
  no-eta-equality

A precategory is a “proof-relevant preorder”. In a preordered set (A,≤)(A, \le), the inhabitants of a set AA are related by a proposition a≤ba \le b, which is

  • reflexive: a≤aa \le a
  • transitive: a≤b∧b≤c→a≤ca \le b \land b \le c \to a \le c

In a precategory, the condition that a≤ba \le b be a proposition is relaxed: A precategory has a type of objects and, between each x,yx, y, a set Hom(x,y)\mathbf{Hom}(x, y) of relations (or maps). The name “Hom\mathbf{Hom}” is historical and it betrays the original context in which categories where employed: algebra(ic topology), where the maps in question are homomorphisms.

  field
    Ob  : Type o
    Hom : Ob → Ob → Type h

Whereas reading a classical definition into a type theory where equality is a proposition, the word set may be read to mean inhabitant of a universe. But in HoTT, if we want categories to be well-behaved, we do actually mean set: A type of h-level 2

  field
    Hom-set : (x y : Ob) → is-set (Hom x y)

If you are already familiar with the definition of precategory there are two things to note here:

First is that our formalization has a family of Hom-sets rather than a single Hom-set and source/target maps. This formulation is not unique to precategory theory done internally to type theory, but it is the most reasonable way to formulate things in this context.

Second is that the word “set” in the definition of Hom-set has nothing to do with “size”. Indeed, the “set”/“not a set” (alternatively “small”/“large”) distinction makes no sense in type theory (some may argue it makes no sense in general).

Instead, the Precategory record is parametrised by two levels: o, and h. The type of objects has to fit in the universe Type o, and the family of Hom-sets is Type h valued. As an example, the thin precategory corresponding to the natural numbers with their usual ordering would live in Precategory lzero lzero.

This means, for instance, that there is no single “category of sets” - there is a family of categories of sets, parametrised by the level in which its objects live.

  field
    id  : ∀ {x}     → Hom x x
    _∘_ : ∀ {x y z} → Hom y z → Hom x y → Hom x z

  infixr 40 _∘_

The “proof-relevant” version of the reflexivity and transitivity laws are, respectively, the identity morphisms and composition of morphisms. Unlike in the proof-irrelevant case, in which an inhabitant of x≤yx \le y merely witnesses that two things are related, these operations matter, and thus must satisfy laws:

  field
    idr : ∀ {x y} (f : Hom x y) → f ∘ id ≡ f
    idl : ∀ {x y} (f : Hom x y) → id ∘ f ≡ f

The two identity laws say that the identity morphisms serve as neutral elements for the composition operation, both on the left and on the right. The “two” associativity laws (below) say that both ways of writing parentheses around a composition of three morphisms is equal: (f∘g)∘h=f∘(g∘h)(f \circ g) \circ h = f \circ (g \circ h).

    assoc : ∀ {w x y z} (f : Hom y z) (g : Hom x y) (h : Hom w x)
          → f ∘ (g ∘ h) ≡ (f ∘ g) ∘ h

We can define the type of all morphisms in a precategory as the total space of Hom:

  Mor : Type (o ⊔ h)
  Mor = Σ[ a ∈ Ob ] Σ[ b ∈ Ob ] Hom a b

Opposites🔗

A common theme throughout precategory theory is that of duality: The dual of a categorical concept is same concept, with “all the arrows inverted”. To make this formal, we introduce the idea of opposite categories: The opposite of CC, written CopC^{\mathrm{op}}, has the same objects, but with HomCop(x,y)=HomC(y,x)\mathbf{Hom}_{C^{\mathrm{op}}}(x, y) = \mathbf{Hom}_{C}(y, x).

infixl 60 _^op
_^op : ∀ {o₁ h₁} → Precategory o₁ h₁ → Precategory o₁ h₁
(C ^op) .Precategory.Ob = Precategory.Ob C
(C ^op) .Precategory.Hom x y = Precategory.Hom C y x
(C ^op) .Precategory.Hom-set x y = Precategory.Hom-set C y x
(C ^op) .Precategory.id = Precategory.id C
(C ^op) .Precategory._∘_ f g = Precategory._∘_ C g f

Composition in the opposite precategory CopC^{\mathrm{op}} is “backwards” with respect to CC: f∘opg=g∘ff \circ_{op} g = g \circ f. This inversion, applied twice, ends up equal to what we started with by the nature of computation - An equality that arises like this, automatically from what Agda computes, is called definitional.

(C ^op) .Precategory.idl x = C .Precategory.idr x
(C ^op) .Precategory.idr x = C .Precategory.idl x

The left and right identity laws are swapped for the construction of the opposite precategory: For idr one has to show f∘opid⁡=ff \circ_{op} \operatorname{id}_{} = f, which computes into having to show that id⁡∘opf=f\operatorname{id}_{} \circ_{op}{f} = f. The case for idl is symmetric.

(C ^op) .Precategory.assoc f g h i = Precategory.assoc C h g f (~ i)

For associativity, consider the case of assoc for the opposite precategory CopC^{\mathrm{op}}. What we have to show is - by the type of assoc₁ - f∘op(g∘oph)=(f∘opg)∘ophf \circ_{op} (g \circ_{op} h) = (f \circ_{op} g) \circ_{op} h. This computes into (h∘g)∘f=h∘(g∘f)(h \circ g) \circ f = h \circ (g \circ f) - which is exactly what sym (assoc C h g f) shows!

C^op^op≡C : ∀ {o ℓ} {C : Precategory o ℓ} → C ^op ^op ≡ C
C^op^op≡C {C = C} i = precat i where
  open Precategory
  precat : C ^op ^op ≡ C
  precat i .Ob = C .Ob
  precat i .Hom = C .Hom
  precat i .Hom-set = C .Hom-set
  precat i .id = C .id
  precat i ._∘_ = C ._∘_
  precat i .idr = C .idr
  precat i .idl = C .idl
  precat i .assoc = C .assoc

The precategory of Sets🔗

Given a universe level, we can consider the collection of all sets of that level. This assembles into a precategory quite nicely, since taking function types is an operation that preserves h-level.

module _ where
  open Precategory

  Sets : (o : _) → Precategory (lsuc o) o
  Sets o .Ob = Set o
  Sets o .Hom A B = ∣ A ∣ → ∣ B ∣
  Sets o .Hom-set _ B f g p q i j a =
    B .is-tr (f a) (g a) (happly p a) (happly q a) i j
  Sets o .id x = x
  Sets o ._∘_ f g x = f (g x)
  Sets o .idl f = refl
  Sets o .idr f = refl
  Sets o .assoc f g h = refl

Functors🔗

Since a category is an algebraic structure, there is a natural definition of homomorphism of categories defined in the same fashion as, for instance, a homomorphism of groups. Since this kind of morphism is ubiquitous, it gets a shorter name: Functor.

Alternatively, functors can be characterised as the “proof-relevant version” of a monotone map: A monotone map is a map F:C→DF : C \to D which preserves the ordering relation, x≤y→F(x)≤F(y)x \le y \to F(x) \le F(y). Categorifying, “preserves the ordering relation” becomes a function between Hom-sets.

  field
    F₀ : C.Ob → D.Ob
    F₁ : ∀ {x y} → C.Hom x y → D.Hom (F₀ x) (F₀ y)

A Functor F:C→DF : C \to D consists of a function between the object sets - F0:Ob(C)→Ob(D)F_0 : \mathrm{Ob}(C) \to \mathrm{Ob}(D), and a function between Hom-sets - which takes f:x→y∈Cf : x \to y \in C to F1(f):F0(x)→F0(y)∈DF_1(f) : F_0(x) \to F_0(y) \in D.

  field
    F-id : ∀ {x} → F₁ (C.id {x}) ≡ D.id
    F-∘ : ∀ {x y z} (f : C.Hom y z) (g : C.Hom x y)
        → F₁ (f C.∘ g) ≡ F₁ f D.∘ F₁ g

Furthermore, the morphism mapping F1F_1 must be homomorphic: Identity morphisms are taken to identity morphisms (F-id) and compositions are taken to compositions (F-∘).

Functors also have duals: The opposite of F:C→DF : C \to D is Fop:Cop→DopF^{\mathrm{op}} : C^{\mathrm{op}} \to D^{\mathrm{op}}.

  op : Functor (C ^op) (D ^op)
  F₀ op      = F₀
  F₁ op      = F₁
  F-id op    = F-id
  F-∘ op f g = F-∘ g f

Composition🔗

_F∘_ : ∀ {o₁ h₁ o₂ h₂ o₃ h₃}
         {C : Precategory o₁ h₁} {D : Precategory o₂ h₂} {E : Precategory o₃ h₃}
     → Functor D E → Functor C D → Functor C E
_F∘_ {C = C} {D} {E} F G = comps

Functors, being made up of functions, can themselves be composed. The object mapping of (F∘G)(F \circ G) is given by F0∘G0F_0 \circ G_0, and similarly for the morphism mapping. Alternatively, composition of functors is a categorification of the fact that monotone maps compose.

    F₀ : C.Ob → E.Ob
    F₀ x = F.F₀ (G.F₀ x)

    F₁ : {x y : C.Ob} → C.Hom x y → E.Hom (F₀ x) (F₀ y)
    F₁ f = F.F₁ (G.F₁ f)

To verify that the result is functorial, equational reasoning is employed, using the witnesses that FF and GG are functorial.

    abstract
      F-id : {x : C.Ob} → F₁ (C.id {x}) ≡ E.id {F₀ x}
      F-id {x} =
          F.F₁ (G.F₁ C.id) ≡⟨ ap F.F₁ G.F-id ⟩≡
          F.F₁ D.id        ≡⟨ F.F-id ⟩≡
          E.id             ∎

      F-∘ : {x y z : C.Ob} (f : C.Hom y z) (g : C.Hom x y)
          → F₁ (f C.∘ g) ≡ (F₁ f E.∘ F₁ g)
      F-∘ f g =
          F.F₁ (G.F₁ (f C.∘ g))     ≡⟨ ap F.F₁ (G.F-∘ f g) ⟩≡
          F.F₁ (G.F₁ f D.∘ G.F₁ g)  ≡⟨ F.F-∘ _ _ ⟩≡
          F₁ f E.∘ F₁ g             ∎

    comps : Functor _ _
    comps .Functor.F₀ = F₀
    comps .Functor.F₁ = F₁
    comps .Functor.F-id = F-id
    comps .Functor.F-∘ = F-∘

The identity functor can be defined using the identity funct_ion_ for both its object and morphism mappings. That functors have an identity and compose would seem to imply that categories form a category: However, since there is no upper bound on the h-level of Ob, we can not form a “category of categories”. If we do impose a bound, however, we can obtain a category of strict categories, those which have a set of objects.

Id : ∀ {o₁ h₁} {C : Precategory o₁ h₁} → Functor C C
Functor.F₀ Id x = x
Functor.F₁ Id f = f
Functor.F-id Id = refl
Functor.F-∘ Id f g = refl

Natural transformations🔗

Another common theme in category theory is that roughly every concept can be considered the objects of a category. This is the case for functors, as well! The functors between CC and DD assemble into a category, notated [C,D][C, D] - the functor category between CC and DD.

record _=>_ {o₁ h₁ o₂ h₂}
            {C : Precategory o₁ h₁}
            {D : Precategory o₂ h₂}
            (F G : Functor C D)
      : Type (o₁ ⊔ h₁ ⊔ h₂)
  where
  no-eta-equality
  constructor NT

The morphisms between functors are called natural transformations. A natural transformation F⇒GF \Rightarrow G can be thought of as a way of turning F(x)F(x)s into G(x)G(x)s that doesn’t involve any “arbitrary choices”.

  private
    module F = Functor F
    module G = Functor G
    module D = Precategory D
    module C = Precategory C

  field
    η : (x : _) → D.Hom (F.₀ x) (G.₀ x)

The transformation itself is given by η, the family of components, where the component at xx is a map F(x)→G(x)F(x) \to G(x). The “without arbitrary choices” part is encoded in the field is-natural, which encodes commutativity of the square below:

    is-natural : (x y : _) (f : C.Hom x y)
               → η y D.∘ F.₁ f ≡ G.₁ f D.∘ η x

Natural transformations also dualize. The opposite of η:F⇒G\eta : F \Rightarrow G is ηop:Gop⇒Fop\eta^{\mathrm{op}} : G^{\mathrm{op}} \Rightarrow F^{\mathrm{op}}.

  op : Functor.op G => Functor.op F
  op = record
    { η = η
    ; is-natural = λ x y f → sym (is-natural _ _ f)
    }

Since the type of natural transformations is defined as a record, we can not a priori reason about its h-level in a convenient way. However, using Agda’s metaprogramming facilities (both reflection and instance search), we can automatically derive an equivalence between the type of natural transformations and a certain Σ\Sigma type; This type can then be shown to be a set using the standard hlevel machinery.

  private unquoteDecl eqv = declare-record-iso eqv (quote _=>_)
  Nat-is-set : is-set (F => G)
  Nat-is-set = Iso→is-hlevel 2 eqv (hlevel 2) where
    open C.HLevel-instance
    open D.HLevel-instance

Another fundamental lemma is that equality of natural transformations depends only on equality of the family of morphisms, since being natural is a proposition:

  Nat-pathp : {F' G' : Functor C D}
            → (p : F ≡ F') (q : G ≡ G')
            → {a : F => G} {b : F' => G'}
            → (∀ x → PathP _ (a .η x) (b .η x))
            → PathP (λ i → p i => q i) a b
  Nat-pathp p q path i .η x = path x i
  Nat-pathp p q {a} {b} path i .is-natural x y f =
    is-prop→pathp
      (λ i → D.Hom-set _ _
        (path y i D.∘ Functor.F₁ (p i) f) (Functor.F₁ (q i) f D.∘ path x i))
      (a .is-natural x y f)
      (b .is-natural x y f) i

  Nat-path : {a b : F => G}
           → ((x : _) → a .η x ≡ b .η x)
           → a ≡ b
  Nat-path = Nat-pathp refl refl

  _ηₚ_ : ∀ {a b : F => G} → a ≡ b → ∀ x → a .η x ≡ b .η x
  p ηₚ x = ap (λ e → e .η x) p

  _ηᵈ_ : ∀ {F' G' : Functor C D} {p : F ≡ F'} {q : G ≡ G'}
       → {a : F => G} {b : F' => G'}
       → PathP (λ i → p i => q i) a b
       → ∀ x → PathP (λ i → D.Hom (p i .F₀ x) (q i .F₀ x)) (a .η x) (b .η x)
  p ηᵈ x = apd (λ i e → e .η x) p

  infixl 45 _ηₚ_
open Precategory
open _=>_

{-
Set-up for using natural transformations with the extensionality tactic;
See the docs in 1Lab.Extensionality for a more detailed explanation of
how it works.

This function is the actual worker which computes the preferred
identity system for natural transformations. Its type asks for

   ∀ x → Extensional (D.Hom (F # x) (G # x))

instead of the more generic ∀ x y → Extensional (D.Hom x y) so that
any specific *instances* for D.Hom involving the object parts of F and G
have a chance to fire. E.g. if G is the product functor on Sets then
(x → y) will only match the funext instance but (x → G # y) will
match funext *and* product extensionality.
-}
Extensional-natural-transformation
  : ∀ {o ℓ o' ℓ' ℓr} {C : Precategory o ℓ} {D : Precategory o' ℓ'}
  → {F G : Functor C D}
  → {@(tactic extensionalᶠ {A = Precategory.Ob C → Type _}
        (λ x → D .Hom (F .Functor.F₀ x) (G .Functor.F₀ x)))
      sa : ∀ x → Extensional (D .Hom (F .Functor.F₀ x) (G .Functor.F₀ x)) ℓr}
  → Extensional (F => G) (o ⊔ ℓr)
Extensional-natural-transformation {sa = sa} .Pathᵉ f g = ∀ i → Pathᵉ (sa i) (f .η i) (g .η i)
Extensional-natural-transformation {sa = sa} .reflᵉ x i = reflᵉ (sa i) (x .η i)
Extensional-natural-transformation {sa = sa} .idsᵉ .to-path x = Nat-path λ i →
  sa _ .idsᵉ .to-path (x i)
Extensional-natural-transformation {D = D} {sa = sa} .idsᵉ .to-path-over h =
  is-prop→pathp
    (λ i → Π-is-hlevel 1
      (λ _ → is-hlevel≃ 1 (identity-system-gives-path (sa _ .idsᵉ)) (D .Hom-set _ _ _ _)))
    _ _

-- Actually define the loop-breaker instance which tells the
-- extensionality tactic what lemma to use for a type of natural
-- transformations.

instance
  extensionality-natural-transformation
    : ∀ {o ℓ o' ℓ'} {C : Precategory o ℓ} {D : Precategory o' ℓ'}
        {F G : Functor C D}
    → Extensionality (F => G)
  extensionality-natural-transformation = record
    { lemma = quote Extensional-natural-transformation }