module Cat.Diagram.Product where

ProductsπŸ”—

The product of two objects and if it exists, is the smallest object equipped with β€œprojection” maps and This situation can be visualised by putting the data of a product into a commutative diagram, as the one below: To express that is the smallest object with projections to and we ask that any other object with projections through and factors uniquely through

In the sense that (univalent) categories generalise posets, the product of and β€” if it exists β€” generalises the binary meet Since products are unique when they exist, we may safely denote any product of and by

For a diagram to be a product diagram, it must be able to cough up an arrow given the data of another span which must not only fit into the diagram above but be unique among the arrows that do so.

This factoring is called the pairing of the arrows and since in the special case where is the terminal object (hence the two arrows are global elements of resp. the pairing is a global element of the product

  record is-product {A B P} (π₁ : Hom P A) (Ο€β‚‚ : Hom P B) : Type (o βŠ” β„“) where
    field
      ⟨_,_⟩ : βˆ€ {Q} (p1 : Hom Q A) (p2 : Hom Q B) β†’ Hom Q P
      Ο€β‚βˆ˜βŸ¨βŸ© : βˆ€ {Q} {p1 : Hom Q _} {p2} β†’ π₁ ∘ ⟨ p1 , p2 ⟩ ≑ p1
      Ο€β‚‚βˆ˜βŸ¨βŸ© : βˆ€ {Q} {p1 : Hom Q _} {p2} β†’ Ο€β‚‚ ∘ ⟨ p1 , p2 ⟩ ≑ p2

      unique : βˆ€ {Q} {p1 : Hom Q A} {p2}
             β†’ {other : Hom Q P}
             β†’ π₁ ∘ other ≑ p1
             β†’ Ο€β‚‚ ∘ other ≑ p2
             β†’ other ≑ ⟨ p1 , p2 ⟩

    uniqueβ‚‚ : βˆ€ {Q} {pr1 : Hom Q A} {pr2}
            β†’ βˆ€ {o1} (p1 : π₁ ∘ o1 ≑ pr1) (q1 : Ο€β‚‚ ∘ o1 ≑ pr2)
            β†’ βˆ€ {o2} (p2 : π₁ ∘ o2 ≑ pr1) (q2 : Ο€β‚‚ ∘ o2 ≑ pr2)
            β†’ o1 ≑ o2
    uniqueβ‚‚ p1 q1 p2 q2 = unique p1 q1 βˆ™ sym (unique p2 q2)

    ⟨⟩∘ : βˆ€ {Q R} {p1 : Hom Q A} {p2 : Hom Q B} (f : Hom R Q)
        β†’ ⟨ p1 , p2 ⟩ ∘ f ≑ ⟨ p1 ∘ f , p2 ∘ f ⟩
    ⟨⟩∘ f = unique (pulll Ο€β‚βˆ˜βŸ¨βŸ©) (pulll Ο€β‚‚βˆ˜βŸ¨βŸ©)

    ⟨⟩-Ξ· : ⟨ π₁ , Ο€β‚‚ ⟩ ≑ id
    ⟨⟩-η = sym $ unique (idr _) (idr _)

A product of and is an explicit choice of product diagram:

  record Product (A B : Ob) : Type (o βŠ” β„“) where
    no-eta-equality
    field
      apex : Ob
      π₁ : Hom apex A
      Ο€β‚‚ : Hom apex B
      has-is-product : is-product π₁ Ο€β‚‚

    open is-product has-is-product public

UniquenessπŸ”—

Products, when they exist, are unique. It’s easiest to see this with a diagrammatic argument: If we have product diagrams and we can fit them into a β€œcommutative diamond” like the one below:

Since both and are products, we know that the dashed arrows in the diagram below exist, so the overall diagram commutes: hence we have an isomorphism

We construct the map as the pairing of the projections from and symmetrically for

  Γ—-Unique : (p1 p2 : Product C A B) β†’ apex p1 β‰… apex p2
  Γ—-Unique p1 p2 = make-iso p1β†’p2 p2β†’p1 p1β†’p2β†’p1 p2β†’p1β†’p2
    where
      module p1 = Product p1
      module p2 = Product p2

      p1β†’p2 : Hom (apex p1) (apex p2)
      p1β†’p2 = p2.⟨ p1.π₁ , p1.Ο€β‚‚ ⟩

      p2β†’p1 : Hom (apex p2) (apex p1)
      p2β†’p1 = p1.⟨ p2.π₁ , p2.Ο€β‚‚ ⟩

These are unique because they are maps into products which commute with the projections.

      p1β†’p2β†’p1 : p1β†’p2 ∘ p2β†’p1 ≑ id
      p1β†’p2β†’p1 =
        p2.uniqueβ‚‚
          (assoc _ _ _ Β·Β· ap (_∘ _) p2.Ο€β‚βˆ˜βŸ¨βŸ© Β·Β· p1.Ο€β‚βˆ˜βŸ¨βŸ©)
          (assoc _ _ _ Β·Β· ap (_∘ _) p2.Ο€β‚‚βˆ˜βŸ¨βŸ© Β·Β· p1.Ο€β‚‚βˆ˜βŸ¨βŸ©)
          (idr _) (idr _)

      p2β†’p1β†’p2 : p2β†’p1 ∘ p1β†’p2 ≑ id
      p2β†’p1β†’p2 =
        p1.uniqueβ‚‚
          (assoc _ _ _ Β·Β· ap (_∘ _) p1.Ο€β‚βˆ˜βŸ¨βŸ© Β·Β· p2.Ο€β‚βˆ˜βŸ¨βŸ©)
          (assoc _ _ _ Β·Β· ap (_∘ _) p1.Ο€β‚‚βˆ˜βŸ¨βŸ© Β·Β· p2.Ο€β‚‚βˆ˜βŸ¨βŸ©)
          (idr _) (idr _)

  is-product-iso
    : βˆ€ {A A' B B' P} {π₁ : Hom P A} {Ο€β‚‚ : Hom P B}
        {f : Hom A A'} {g : Hom B B'}
    β†’ is-invertible f
    β†’ is-invertible g
    β†’ is-product C π₁ Ο€β‚‚
    β†’ is-product C (f ∘ π₁) (g ∘ Ο€β‚‚)
  is-product-iso f-iso g-iso prod = prod' where
    module fi = is-invertible f-iso
    module gi = is-invertible g-iso

    open is-product
    prod' : is-product _ _ _
    prod' .⟨_,_⟩ qa qb = prod .⟨_,_⟩ (fi.inv ∘ qa) (gi.inv ∘ qb)
    prod' .Ο€β‚βˆ˜βŸ¨βŸ© = pullr (prod .Ο€β‚βˆ˜βŸ¨βŸ©) βˆ™ cancell fi.invl
    prod' .Ο€β‚‚βˆ˜βŸ¨βŸ© = pullr (prod .Ο€β‚‚βˆ˜βŸ¨βŸ©) βˆ™ cancell gi.invl
    prod' .unique p q = prod .unique
      (sym (ap (_ ∘_) (sym p) βˆ™ pulll (cancell fi.invr)))
      (sym (ap (_ ∘_) (sym q) βˆ™ pulll (cancell gi.invr)))

  is-product-iso-apex
    : βˆ€ {A B P P'} {π₁ : Hom P A} {Ο€β‚‚ : Hom P B}
        {π₁' : Hom P' A} {Ο€β‚‚' : Hom P' B}
        {f : Hom P' P}
    β†’ is-invertible f
    β†’ π₁ ∘ f ≑ π₁'
    β†’ Ο€β‚‚ ∘ f ≑ Ο€β‚‚'
    β†’ is-product C π₁ Ο€β‚‚
    β†’ is-product C π₁' Ο€β‚‚'
  is-product-iso-apex {f = f} f-iso f-π₁ f-Ο€β‚‚ prod = prod' where
    module fi = is-invertible f-iso

    open is-product
    prod' : is-product _ _ _
    prod' .⟨_,_⟩ qa qb = fi.inv ∘ prod .⟨_,_⟩ qa qb
    prod' .Ο€β‚βˆ˜βŸ¨βŸ© = pulll (rswizzle (sym f-π₁) fi.invl) βˆ™ prod .Ο€β‚βˆ˜βŸ¨βŸ©
    prod' .Ο€β‚‚βˆ˜βŸ¨βŸ© = pulll (rswizzle (sym f-Ο€β‚‚) fi.invl) βˆ™ prod .Ο€β‚‚βˆ˜βŸ¨βŸ©
    prod' .unique p q = sym $ lswizzle
      (sym (prod .unique (pulll f-π₁ βˆ™ p) (pulll f-Ο€β‚‚ βˆ™ q))) fi.invr

Categories with all binary productsπŸ”—

Categories with all binary products are quite common, so we provide an API for working with them. In order to get better printing in goals, we define an unnested record where all operations are top-level fields; this means that goals willl print as ⟨ f , g ⟩ instead of is-product.⟨_,_⟩ (product A B) f g.

record Binary-products {o β„“} (C : Precategory o β„“) : Type (o βŠ” β„“) where
  no-eta-equality
  open Cat.Reasoning C
  field
    _βŠ—β‚€_ : Ob β†’ Ob β†’ Ob
    π₁ : βˆ€ {A B} β†’ Hom (A βŠ—β‚€ B) A
    Ο€β‚‚ : βˆ€ {A B} β†’ Hom (A βŠ—β‚€ B) B
    ⟨_,_⟩ : βˆ€ {A B X} β†’ Hom X A β†’ Hom X B β†’ Hom X (A βŠ—β‚€ B)
    Ο€β‚βˆ˜βŸ¨βŸ© : βˆ€ {A B X} {p1 : Hom X A} {p2 : Hom X B} β†’ π₁ ∘ ⟨ p1 , p2 ⟩ ≑ p1
    Ο€β‚‚βˆ˜βŸ¨βŸ© : βˆ€ {A B X} {p1 : Hom X A} {p2 : Hom X B} β†’ Ο€β‚‚ ∘ ⟨ p1 , p2 ⟩ ≑ p2
    ⟨⟩-unique
      : βˆ€ {A B X}
      β†’ {p1 : Hom X A} {p2 : Hom X B}
      β†’ {other : Hom X (A βŠ—β‚€ B)}
      β†’ π₁ ∘ other ≑ p1
      β†’ Ο€β‚‚ ∘ other ≑ p2
      β†’ other ≑ ⟨ p1 , p2 ⟩

  infixr 7 _βŠ—β‚€_
  infix 50 _βŠ—β‚_

If a category has all binary products, we can define a bifunctor that sets to their product.

  _βŠ—β‚_ : βˆ€ {a b x y} β†’ Hom a x β†’ Hom b y β†’ Hom (a βŠ—β‚€ b) (x βŠ—β‚€ y)
  f βŠ—β‚ g = ⟨ f ∘ π₁ , g ∘ Ο€β‚‚ ⟩

  Γ—-functor : Functor (C Γ—αΆœ C) C
  Γ—-functor .Fβ‚€ (a , b) = a βŠ—β‚€ b
  Γ—-functor .F₁ (f , g) = f βŠ—β‚ g
  Γ—-functor .F-id = sym $ ⟨⟩-unique id-comm id-comm
  Γ—-functor .F-∘ (f , g) (h , i) =
    sym $ ⟨⟩-unique
      (pulll Ο€β‚βˆ˜βŸ¨βŸ© βˆ™ extendr Ο€β‚βˆ˜βŸ¨βŸ©)
      (pulll Ο€β‚‚βˆ˜βŸ¨βŸ© βˆ™ extendr Ο€β‚‚βˆ˜βŸ¨βŸ©)

We also define a handful of common morphisms.

  Ξ΄ : βˆ€ {A} β†’ Hom A (A βŠ—β‚€ A)
  δ = ⟨ id , id ⟩

  swap : βˆ€ {A B} β†’ Hom (A βŠ—β‚€ B) (B βŠ—β‚€ A)
  swap = ⟨ Ο€β‚‚ , π₁ ⟩

  Γ—-assoc : βˆ€ {A B C} β†’ Hom (A βŠ—β‚€ (B βŠ—β‚€ C)) ((A βŠ—β‚€ B) βŠ—β‚€ C)
  Γ—-assoc = ⟨ ⟨ π₁ , π₁ ∘ Ο€β‚‚ ⟩ , Ο€β‚‚ ∘ Ο€β‚‚ ⟩

Representability of productsπŸ”—

The collection of maps into a product is equivalent to the collection of pairs of maps into and The forward direction of the equivalence is given by postcomposition of the projections, and the reverse direction by the universal property of products.

  product-repr
    : βˆ€ {a b}
    β†’ (prod : Product C a b)
    β†’ (x : Ob)
    β†’ Hom x (Product.apex prod) ≃ (Hom x a Γ— Hom x b)
  product-repr prod x = Iso→Equiv λ where
      .fst f β†’ π₁ ∘ f , Ο€β‚‚ ∘ f
      .snd .is-iso.inv (f , g) β†’ ⟨ f , g ⟩
      .snd .is-iso.rinv (f , g) β†’ Ο€β‚βˆ˜βŸ¨βŸ© ,β‚š Ο€β‚‚βˆ˜βŸ¨βŸ©
      .snd .is-iso.linv f β†’ sym (⟨⟩∘ f) βˆ™ eliml ⟨⟩-Ξ·
    where open Product prod