module Cat.Abelian.Base where

Abelian categoriesπŸ”—

This module defines the sequence of properties which β€œwork up to” abelian categories: Ab-enriched categories, pre-additive categories, pre-abelian categories, and abelian categories. Each concept builds on the last by adding a new categorical property on top of a precategory.

Ab-enriched categoriesπŸ”—

An category is one where each set carries the structure of an Abelian group, such that the composition map is bilinear, hence extending to an Abelian group homomorphism

where the term on the left is the tensor product of the corresponding As the name implies, every such category has a canonical (made monoidal using but we do not use the language of enriched category theory in our development of Abelian categories.

record Ab-category {o β„“} (C : Precategory o β„“) : Type (o βŠ” lsuc β„“) where
  open Cat C public
  field
    Abelian-group-on-hom : βˆ€ A B β†’ Abelian-group-on (Hom A B)

  module Hom {A B} = Abelian-group-on (Abelian-group-on-hom A B) renaming (_⁻¹ to inverse)
  open Hom
    using (zero-diff)
    renaming (_β€”_ to _-_ ; _*_ to _+_ ; 1g to 0m)
    public

  Hom-grp : βˆ€ A B β†’ Abelian-group β„“
  Hom-grp A B = (el (Hom A B) (Hom-set A B)) , Abelian-group-on-hom A B

  field
    -- Composition is multilinear:
    ∘-linear-l
      : βˆ€ {A B C} (f g : Hom B C) (h : Hom A B)
      β†’ (f ∘ h) + (g ∘ h) ≑ (f + g) ∘ h
    ∘-linear-r
      : βˆ€ {A B C} (f : Hom B C) (g h : Hom A B)
      β†’ (f ∘ g) + (f ∘ h) ≑ f ∘ (g + h)

  ∘map : βˆ€ {A B C} β†’ Ab.Hom (Hom-grp B C βŠ— Hom-grp A B) (Hom-grp A C)
  ∘map {A} {B} {C} =
    from-bilinear-map (Hom-grp B C) (Hom-grp A B) (Hom-grp A C)
      (record { map     = _∘_
              ; pres-*l = Ξ» x y z β†’ sym (∘-linear-l x y z)
              ; pres-*r = Ξ» x y z β†’ sym (∘-linear-r x y z)
              })
Note that from multilinearity of composition, it follows that the addition of and composition1 operations satisfy familiar algebraic identities, e.g.Β  etc.
  ∘-zero-r : βˆ€ {A B C} {f : Hom B C} β†’ f ∘ 0m {A} {B} ≑ 0m
  ∘-zero-r {f = f} =
    f ∘ 0m                     β‰‘βŸ¨ Hom.intror Hom.inverser βŸ©β‰‘
    f ∘ 0m + (f ∘ 0m - f ∘ 0m) β‰‘βŸ¨ Hom.associative βŸ©β‰‘
    (f ∘ 0m + f ∘ 0m) - f ∘ 0m β‰‘βŸ¨ ap (_- f ∘ 0m) (∘-linear-r _ _ _) βŸ©β‰‘
    (f ∘ (0m + 0m)) - f ∘ 0m   β‰‘βŸ¨ ap ((_- f ∘ 0m) βŠ™ (f ∘_)) Hom.idl βŸ©β‰‘
    (f ∘ 0m) - f ∘ 0m          β‰‘βŸ¨ Hom.inverser βŸ©β‰‘
    0m                         ∎

  ∘-zero-l : βˆ€ {A B C} {f : Hom A B} β†’ 0m ∘ f ≑ 0m {A} {C}
  ∘-zero-l {f = f} =
    0m ∘ f                                   β‰‘βŸ¨ Hom.introl Hom.inversel βŸ©β‰‘
    (Hom.inverse (0m ∘ f) + 0m ∘ f) + 0m ∘ f β‰‘βŸ¨ sym Hom.associative βŸ©β‰‘
    Hom.inverse (0m ∘ f) + (0m ∘ f + 0m ∘ f) β‰‘βŸ¨ ap (Hom.inverse (0m ∘ f) +_) (∘-linear-l _ _ _) βŸ©β‰‘
    Hom.inverse (0m ∘ f) + ((0m + 0m) ∘ f)   β‰‘βŸ¨ ap ((Hom.inverse (0m ∘ f) +_) βŠ™ (_∘ f)) Hom.idl βŸ©β‰‘
    Hom.inverse (0m ∘ f) + (0m ∘ f)          β‰‘βŸ¨ Hom.inversel βŸ©β‰‘
    0m                                       ∎

  ∘-negatel
    : βˆ€ {A B C} {g : Hom B C} {h : Hom A B}
    β†’ Hom.inverse (g ∘ h) ≑ Hom.inverse g ∘ h
  ∘-negatel {g = g} {h} = monoid-inverse-unique Hom.has-is-monoid (g ∘ h) _ _
    Hom.inversel
    (∘-linear-l _ _ _ βˆ™ ap (_∘ h) Hom.inverser βˆ™ ∘-zero-l)

  ∘-negater
    : βˆ€ {A B C} {g : Hom B C} {h : Hom A B}
    β†’ Hom.inverse (g ∘ h) ≑ g ∘ Hom.inverse h
  ∘-negater {g = g} {h} = monoid-inverse-unique Hom.has-is-monoid (g ∘ h) _ _
    Hom.inversel
    (∘-linear-r _ _ _ βˆ™ ap (g ∘_) Hom.inverser βˆ™ ∘-zero-r)

  ∘-minus-l
    : βˆ€ {A B C} (f g : Hom B C) (h : Hom A B)
    β†’ (f ∘ h) - (g ∘ h) ≑ (f - g) ∘ h
  ∘-minus-l f g h =
    f ∘ h - g ∘ h               β‰‘βŸ¨ ap (f ∘ h +_) ∘-negatel βŸ©β‰‘
    f ∘ h + (Hom.inverse g ∘ h) β‰‘βŸ¨ ∘-linear-l _ _ _ βŸ©β‰‘
    (f - g) ∘ h                 ∎

  ∘-minus-r
    : βˆ€ {A B C} (f : Hom B C) (g h : Hom A B)
    β†’ (f ∘ g) - (f ∘ h) ≑ f ∘ (g - h)
  ∘-minus-r f g h =
    f ∘ g - f ∘ h               β‰‘βŸ¨ ap (f ∘ g +_) ∘-negater βŸ©β‰‘
    f ∘ g + (f ∘ Hom.inverse h) β‰‘βŸ¨ ∘-linear-r _ _ _ βŸ©β‰‘
    f ∘ (g - h)                 ∎

Before moving on, we note the following property of If is an object s.t. then is a zero object.

module _ {o β„“} {C : Precategory o β„“} (A : Ab-category C) where
  private module A = Ab-category A

  id-zeroβ†’zero : βˆ€ {X} β†’ A.id {X} ≑ A.0m β†’ is-zero C X
  id-zero→zero idm .is-zero.has-is-initial B = contr A.0m λ h → sym $
    h                                β‰‘βŸ¨ A.intror refl βŸ©β‰‘
    h A.∘ A.id                       β‰‘βŸ¨ A.refl⟩∘⟨ idm βŸ©β‰‘
    h A.∘ A.0m                       β‰‘βŸ¨ A.∘-zero-r βŸ©β‰‘
    A.0m                             ∎
  id-zero→zero idm .is-zero.has-is-terminal x = contr A.0m λ h → sym $
    h                              β‰‘βŸ¨ A.introl refl βŸ©β‰‘
    A.id A.∘ h                     β‰‘βŸ¨ idm A.⟩∘⟨refl βŸ©β‰‘
    A.0m A.∘ h                     β‰‘βŸ¨ A.∘-zero-l βŸ©β‰‘
    A.0m                           ∎

Perhaps the simplest example of an is.. any ring! In the same way that a monoid is a category with one object, and a group is a groupoid with one object, a ring is a ringoid with one object; Ringoid being another word for rather than a horizontal categorification of the drummer for the Beatles. The next simplest example is itself:

module _ where
  open Ab-category
  Ab-ab-category : βˆ€ {β„“} β†’ Ab-category (Ab β„“)
  Ab-ab-category .Abelian-group-on-hom A B = Ab.Abelian-group-on-hom A B
  Ab-ab-category .∘-linear-l f g h = trivial!
  Ab-ab-category .∘-linear-r f g h = ext Ξ» _ β†’
    sym (f .preserves .is-group-hom.pres-⋆ _ _)

Additive categoriesπŸ”—

An is additive when its underlying category has a terminal object and finite products; By the yoga above, this implies that the terminal object is also a zero object, and the finite products coincide with finite coproducts.

record is-additive {o β„“} (C : Precategory o β„“) : Type (o βŠ” lsuc β„“) where
  field has-ab : Ab-category C
  open Ab-category has-ab public

  field
    has-terminal : Terminal C
    has-prods    : βˆ€ A B β†’ Product C A B

  βˆ… : Zero C
  βˆ… .Zero.βˆ… = has-terminal .Terminal.top
  βˆ… .Zero.has-is-zero = id-zeroβ†’zero has-ab $
    is-contrβ†’is-prop (has-terminal .Terminal.has⊀ _) _ _
  module βˆ… = Zero βˆ…

  0m-unique : βˆ€ {A B} β†’ βˆ….zeroβ†’ {A} {B} ≑ 0m
  0m-unique = apβ‚‚ _∘_ (βˆ….hasβŠ₯ _ .paths _) refl βˆ™ ∘-zero-l

Coincidence of finite products and finite coproducts leads to an object commonly called a (finite) biproduct. The coproduct coprojections are given by the pair of maps

respectively, and the comultiplication of and is given by We can calculate, for the first coprojection followed by comultiplication,

and analogously for the second coprojection followed by comultiplication.

  private module Prod = Binary-products C has-prods
  open Prod

  has-coprods : βˆ€ A B β†’ Coproduct C A B
  has-coprods A B = coprod where
    open Coproduct
    open is-coproduct
    coprod : Coproduct C A B
    coprod .coapex = A βŠ—β‚€ B
    coprod .ι₁ = ⟨ id , 0m ⟩
    coprod .ΞΉβ‚‚ = ⟨ 0m , id ⟩
    coprod .has-is-coproduct .[_,_] f g = f ∘ π₁ + g ∘ Ο€β‚‚
    coprod .has-is-coproduct .[]βˆ˜ΞΉβ‚ {inj0 = f} {g} =
      (f ∘ π₁ + g ∘ Ο€β‚‚) ∘ ⟨ id , 0m ⟩ β‰‘βŸ¨ sym (∘-linear-l _ _ _) βŸ©β‰‘
      (f ∘ π₁) ∘ ⟨ id , 0m ⟩ + _      β‰‘βŸ¨ Hom.elimr (pullr Ο€β‚‚βˆ˜βŸ¨βŸ© βˆ™ ∘-zero-r) βŸ©β‰‘
      (f ∘ π₁) ∘ ⟨ id , 0m ⟩          β‰‘βŸ¨ cancelr Ο€β‚βˆ˜βŸ¨βŸ© βŸ©β‰‘
      f                               ∎
    coprod .has-is-coproduct .[]βˆ˜ΞΉβ‚‚ {inj0 = f} {g} =
      (f ∘ π₁ + g ∘ Ο€β‚‚) ∘ ⟨ 0m , id ⟩ β‰‘βŸ¨ sym (∘-linear-l _ _ _) βŸ©β‰‘
      _ + (g ∘ Ο€β‚‚) ∘ ⟨ 0m , id ⟩      β‰‘βŸ¨ Hom.eliml (pullr Ο€β‚βˆ˜βŸ¨βŸ© βˆ™ ∘-zero-r) βŸ©β‰‘
      (g ∘ Ο€β‚‚) ∘ ⟨ 0m , id ⟩          β‰‘βŸ¨ cancelr Ο€β‚‚βˆ˜βŸ¨βŸ© βŸ©β‰‘
      g                               ∎

For uniqueness, we use distributivity of composition over addition of morphisms and the universal property of the product to establish the desired equation. Check it out:

    coprod .has-is-coproduct .unique {inj0 = f} {g} {other} p q = sym $
      f ∘ π₁ + g ∘ Ο€β‚‚                                         β‰‘βŸ¨ apβ‚‚ _+_ (pushl (sym p)) (pushl (sym q)) βŸ©β‰‘
      (other ∘ ⟨ id , 0m ⟩ ∘ π₁) + (other ∘ ⟨ 0m , id ⟩ ∘ Ο€β‚‚) β‰‘βŸ¨ ∘-linear-r _ _ _ βŸ©β‰‘
      other ∘ (⟨ id , 0m ⟩ ∘ π₁ + ⟨ 0m , id ⟩ ∘ Ο€β‚‚)           β‰‘βŸ¨ elimr lemma βŸ©β‰‘
      other                                                   ∎
      where
        lemma : ⟨ id , 0m ⟩ ∘ π₁ + ⟨ 0m , id ⟩ ∘ Ο€β‚‚
              ≑ id
        lemma = Prod.uniqueβ‚‚ {pr1 = π₁} {pr2 = Ο€β‚‚}
          (sym (∘-linear-r _ _ _) βˆ™ apβ‚‚ _+_ (cancell Ο€β‚βˆ˜βŸ¨βŸ©) (pulll Ο€β‚βˆ˜βŸ¨βŸ© βˆ™ ∘-zero-l) βˆ™ Hom.elimr refl)
          (sym (∘-linear-r _ _ _) βˆ™ apβ‚‚ _+_ (pulll Ο€β‚‚βˆ˜βŸ¨βŸ© βˆ™ ∘-zero-l) (cancell Ο€β‚‚βˆ˜βŸ¨βŸ©) βˆ™ Hom.eliml refl)
          (elimr refl)
          (elimr refl)

  module Coprod = Binary-coproducts C has-coprods
  open Coprod

Thus every additive category is semiadditive.

  additive→semiadditive : is-semiadditive C
  additiveβ†’semiadditive .is-semiadditive.has-zero = βˆ…
  additive→semiadditive .is-semiadditive.has-biproducts {A} {B} = bp where
    open is-biproduct
    bp : Biproduct C A B
    bp .Biproduct.biapex = A βŠ—β‚€ B
    bp .Biproduct.π₁ = π₁
    bp .Biproduct.Ο€β‚‚ = Ο€β‚‚
    bp .Biproduct.ι₁ = ι₁
    bp .Biproduct.ΞΉβ‚‚ = ΞΉβ‚‚
    bp .Biproduct.has-is-biproduct .has-is-product = Prod.has-is-product
    bp .Biproduct.has-is-biproduct .has-is-coproduct = Coprod.has-is-coproduct
    bp .Biproduct.has-is-biproduct .πι₁ = Ο€β‚βˆ˜βŸ¨βŸ©
    bp .Biproduct.has-is-biproduct .πι₂ = Ο€β‚‚βˆ˜βŸ¨βŸ©
    bp .Biproduct.has-is-biproduct .ΞΉΟ€-comm =
      ι₁ ∘ π₁ ∘ ΞΉβ‚‚ ∘ Ο€β‚‚ β‰‘βŸ¨ refl⟩∘⟨ pulll Ο€β‚βˆ˜βŸ¨βŸ© βŸ©β‰‘
      ι₁ ∘ 0m ∘ Ο€β‚‚      β‰‘βŸ¨ pulll ∘-zero-r βˆ™ ∘-zero-l βŸ©β‰‘
      0m                β‰‘Λ˜βŸ¨ pulll ∘-zero-r βˆ™ ∘-zero-l βŸ©β‰‘Λ˜
      ΞΉβ‚‚ ∘ 0m ∘ π₁      β‰‘Λ˜βŸ¨ refl⟩∘⟨ pulll Ο€β‚‚βˆ˜βŸ¨βŸ© βŸ©β‰‘Λ˜
      ΞΉβ‚‚ ∘ Ο€β‚‚ ∘ ι₁ ∘ π₁ ∎

  open is-semiadditive additiveβ†’semiadditive hiding (∘-linear-l; ∘-linear-r)

As described there, every semiadditive category has its own enrichment in commutative monoids. Since we already know that the zero morphisms agree (0m-unique), it would be natural to expect that the additions also agree; this is straightforward to check by linearity.

  enrichments-agree : βˆ€ {A B} {f g : Hom A B} β†’ f +β†’ g ≑ f + g
  enrichments-agree {f = f} {g} =
    (id ∘ π₁ + id ∘ Ο€β‚‚) ∘ (f βŠ—β‚ g) ∘ Ξ΄      β‰‘βŸ¨ apβ‚‚ _+_ (idl _) (idl _) ⟩∘⟨refl βŸ©β‰‘
    (π₁ + Ο€β‚‚) ∘ (f βŠ—β‚ g) ∘ Ξ΄                β‰‘Λ˜βŸ¨ ∘-linear-l _ _ _ βŸ©β‰‘Λ˜
    (π₁ ∘ (f βŠ—β‚ g) ∘ Ξ΄ + Ο€β‚‚ ∘ (f βŠ—β‚ g) ∘ Ξ΄) β‰‘βŸ¨ apβ‚‚ _+_ (pulll Ο€β‚βˆ˜βŸ¨βŸ© βˆ™ cancelr Ο€β‚βˆ˜βŸ¨βŸ©) (pulll Ο€β‚‚βˆ˜βŸ¨βŸ© βˆ™ cancelr Ο€β‚‚βˆ˜βŸ¨βŸ©) βŸ©β‰‘
    f + g                                   ∎

Therefore, in order to get an additive category from a semiadditive category, it suffices to ask for inverses for every morphism, so that each becomes a group.

  semiadditive+group→additive
    : (inv : βˆ€ {A B} β†’ Hom A B β†’ Hom A B)
    β†’ (invl : βˆ€ {A B} {f : Hom A B} β†’ inv f +β†’ f ≑ zeroβ†’)
    β†’ is-additive C
  semiadditive+group→additive inv invl .is-additive.has-ab = ab where
    mk : βˆ€ {A B} β†’ make-abelian-group (Hom A B)
    mk .make-abelian-group.ab-is-set = hlevel 2
    mk .make-abelian-group.mul = _+β†’_
    mk .make-abelian-group.inv = inv
    mk .make-abelian-group.1g = zero→
    mk .make-abelian-group.idl _ = +-idl
    mk .make-abelian-group.assoc _ _ _ = +-assoc
    mk .make-abelian-group.invl _ = invl
    mk .make-abelian-group.comm _ _ = +-comm

    ab : Ab-category C
    ab .Ab-category.Abelian-group-on-hom _ _  = to-abelian-group-on mk
    ab .Ab-category.∘-linear-l _ _ _ = ∘-linear-l
    ab .Ab-category.∘-linear-r _ _ _ = ∘-linear-r

  semiadditive+group→additive inv invl .is-additive.has-terminal = terminal
  semiadditive+group→additive inv invl .is-additive.has-prods _ _ = Biprod.product

Pre-abelian & abelian categoriesπŸ”—

An additive category is pre-abelian when it additionally has kernels and cokernels, hence binary equalisers and coequalisers where one of the maps is zero.

record is-pre-abelian {o β„“} (C : Precategory o β„“) : Type (o βŠ” lsuc β„“) where
  field has-additive : is-additive C
  open is-additive has-additive public
  field
    kernel   : βˆ€ {A B} (f : Hom A B) β†’ Kernel C βˆ… f
    cokernel : βˆ€ {A B} (f : Hom A B) β†’ Coequaliser C 0m f

  module Ker {A B} (f : Hom A B) = Kernel (kernel f)
  module Coker {A B} (f : Hom A B) = Coequaliser (cokernel f)

Every morphism in a preabelian category admits a canonical decomposition as

where, as indicated, the map is an epimorphism (indeed a regular epimorphism, since it is a cokernel) and the map is a regular monomorphism.

  decompose
    : βˆ€ {A B} (f : Hom A B)
    β†’ Ξ£[ f' ∈ Hom (Coker.coapex (Ker.kernel f)) (Ker.ker (Coker.coeq f)) ]
       (f ≑ Ker.kernel (Coker.coeq f) ∘ f' ∘ Coker.coeq (Ker.kernel f))
  decompose {A} {B} f = map , sym path
    where
      proj' : Hom (Coker.coapex (Ker.kernel f)) B
      proj' = Coker.universal (Ker.kernel f) {e' = f} $ sym path
      map : Hom (Coker.coapex (Ker.kernel f)) (Ker.ker (Coker.coeq f))
      map = Ker.universal (Coker.coeq f) {e' = proj'} $ sym path

The existence of the map and indeed of the maps and follow from the universal properties of kernels and cokernels. The map is the canonical quotient map and the map is the canonical subobject inclusion

A pre-abelian category is abelian when the map in the above decomposition is an isomorphism.

record is-abelian {o β„“} (C : Precategory o β„“) : Type (o βŠ” lsuc β„“) where
  field has-is-preab : is-pre-abelian C
  open is-pre-abelian has-is-preab public
  field
    coker-ker≃ker-coker
      : βˆ€ {A B} (f : Hom A B) β†’ is-invertible (decompose f .fst)

This implies in particular that any monomorphism is a kernel, and every epimorphism is a cokernel. Let’s investigate the case for β€œevery mono is a kernel” first: Suppose that is some monomorphism; We’ll show that it’s isomorphic to in the slice category

  module _ {A B} (f : Hom A B) (monic : is-monic f) where
    private
      module m = Cat (Slice C B)

The map is obtained as the composite

where the isomorphism is our canonical map from before.

      f→kercoker : m.Hom (cut f) (cut (Ker.kernel (Coker.coeq f)))
      fβ†’kercoker ./-Hom.map = decompose f .fst ∘ Coker.coeq (Ker.kernel f)
      f→kercoker ./-Hom.commutes = sym (decompose f .snd)

Conversely, map is the composite

where the second map arises from the universal property of the cokernel: We can map out of it with the map since (using that is mono), we have from

      kercoker→f : m.Hom (cut (Ker.kernel (Coker.coeq f))) (cut f)
      kercoker→f ./-Hom.map =
        Coker.universal (Ker.kernel f) {e' = id} (monic _ _ path) ∘
          coker-ker≃ker-coker f .is-invertible.inv
        where abstract
          path : f ∘ id ∘ 0m ≑ f ∘ id ∘ Ker.kernel f
          path =
            f ∘ id ∘ 0m              β‰‘βŸ¨ ap (f ∘_) (eliml refl) βˆ™ ∘-zero-r βŸ©β‰‘
            0m                       β‰‘Λ˜βŸ¨ βˆ….zero-∘r _ βˆ™ 0m-unique βŸ©β‰‘Λ˜
            (βˆ….zeroβ†’ ∘ Ker.kernel f) β‰‘Λ˜βŸ¨ Ker.equal f βŸ©β‰‘Λ˜
            f ∘ Ker.kernel f         β‰‘βŸ¨ ap (f ∘_) (introl refl) βŸ©β‰‘
            f ∘ id ∘ Ker.kernel f    ∎

This is indeed a map in the slice using that both isomorphisms and coequalisers are epic to make progress.

      kercoker→f ./-Hom.commutes = path where
        lemma =
          is-coequaliser→is-epic (Coker.coeq _) (Coker.has-is-coeq _) _ _ $
               pullr (Coker.factors _)
            Β·Β· elimr refl
            Β·Β· (decompose f .snd βˆ™ assoc _ _ _)

        path =
          invertibleβ†’epic (coker-ker≃ker-coker _) _ _ $
            (f ∘ Coker.universal _ _ ∘ _) ∘ decompose f .fst   β‰‘βŸ¨ apβ‚‚ _∘_ (assoc _ _ _) refl βŸ©β‰‘
            ((f ∘ Coker.universal _ _) ∘ _) ∘ decompose f .fst β‰‘βŸ¨ cancelr (coker-ker≃ker-coker _ .is-invertible.invr) βŸ©β‰‘
            f ∘ Coker.universal _ _                            β‰‘βŸ¨ lemma βŸ©β‰‘
            Ker.kernel _ ∘ decompose f .fst                     ∎

Using the universal property of the cokernel (both uniqueness and universality), we establish that the maps defined above are inverses in thus assemble into an isomorphism in the slice.

    mono→kernel : cut f m.≅ cut (Ker.kernel (Coker.coeq f))
    mono→kernel = m.make-iso f→kercoker kercoker→f f→kc→f kc→f→kc where
      fβ†’kcβ†’f : fβ†’kercoker m.∘ kercokerβ†’f ≑ m.id
      f→kc→f = ext $
        (decompose f .fst ∘ Coker.coeq _) ∘ Coker.universal _ _ ∘ _  β‰‘βŸ¨ cancel-inner lemma βŸ©β‰‘
        decompose f .fst ∘ _                                         β‰‘βŸ¨ coker-ker≃ker-coker f .is-invertible.invl βŸ©β‰‘
        id                                                           ∎
        where
          lemma = Coker.uniqueβ‚‚ _
            {e' = Coker.coeq (Ker.kernel f)}
            (∘-zero-r βˆ™ sym (sym (Coker.coequal _) βˆ™ ∘-zero-r))
            (pullr (Coker.factors (Ker.kernel f)) βˆ™ elimr refl)
            (eliml refl)

      kcβ†’fβ†’kc : kercokerβ†’f m.∘ fβ†’kercoker ≑ m.id
      kc→f→kc = ext $
        (Coker.universal _ _ ∘ _) ∘ decompose f .fst ∘ Coker.coeq _ β‰‘βŸ¨ cancel-inner (coker-ker≃ker-coker f .is-invertible.invr) βŸ©β‰‘
        Coker.universal _ _ ∘ Coker.coeq _                          β‰‘βŸ¨ Coker.factors _ βŸ©β‰‘
        id                                                          ∎

  1. β€œmultiplicationβ€β†©οΈŽ