module Cat.Functor.Adjoint where
Adjoint functors🔗
Category theory is, in general, the study of how things can be related. For instance, structures at the level of sets (e.g. the collection of natural numbers) are often interestingly related by propositions (i.e. the proposition ). Structures at the level of groupoids (e.g. the collection of all sets) are interestingly related by sets (i.e. the set of maps ). Going further, we have structures at the level of 2-groupoids, which could be given an interesting category of relations, etc.
A particularly important relationship is, of course, “sameness”. Going up the ladder of category number, we have equality at the (-1)-level, isomorphism at the 0-level, and what’s generally referred to as “equivalence” at higher levels. It’s often interesting to weaken these relations, by making some components directed: This starts at the level of categories, where “directing” an equivalence gives us the concept of adjunction.
An equivalence of categories between and is given by a pair of functors , equipped with natural isomorphisms (the “unit”) and (the “counit”). We still want the correspondence to be bidirectional, so we can’t change the types of , ; What we can do is weaken the natural isomorphisms to natural transformations. The data of an adjunction starts as such:
record _⊣_ (L : Functor C D) (R : Functor D C) : Type (adj-level C D) where
private
module C = Precategory C
module D = Precategory D
field
: Id => (R F∘ L)
unit : (L F∘ R) => Id
counit
module unit = _=>_ unit
module counit = _=>_ counit renaming (η to ε)
Unfortunately, the data that we have here is not particularly
coherent. The unit
and counit
let us introduce
and eliminate
in a composition, which gives us two ways of mapping
.
One is the identity, and the other is going through the unit:
(the situation with
is symmetric). We must impose further equations on the natural
transformations to make sure these match:
field
: ∀ {A} → counit.ε (F₀ L A) D.∘ F₁ L (unit.η A) ≡ D.id
zig : ∀ {B} → F₁ R (counit.ε B) C.∘ unit.η (F₀ R B) ≡ C.id
zag
infixr 15 _⊣_
These are called “triangle identities” because of the shape they have as commutative diagrams:
Universal morphisms🔗
Another perspective on adjoint functors is given by finding “most efficient solutions” to the “problem” posed by a functor. For instance, the (fully faithful) inclusion of posets into strict (pre)categories poses the problem of turning a precategory into a poset. While this can’t be done in a 1:1 way (precategories are strictly more general than posets), we can still ponder whether there is some “most efficient” way to turn a category into a poset.
While we can’t directly consider maps from precategories to posets, we can consider maps from precategories to the inclusion of a poset; Let us write for a generic precategory, for a generic poset, and for considered as a precategory. Any functor can be seen as “a way to turn into a poset”, but not all of these can be the “most efficient” way. In fact, there is a vast sea of uninteresting ways to turn a precategory into a poset: turn them all into the terminal poset!
A “most efficient” solution, then, would be one through which all others factor. A “universal” way of turning a strict precategory into a poset: A universal morphism from to . The way we think about universal morphisms (reusing the same variables) is as initial objects in the comma category , where that category is conceptualised as being “the category of maps from to ”.
: C.Ob → Functor D C → Type _
Universal-morphism = Initial (X ↙ R) Universal-morphism X R
Abstracting away, suppose that has universal morphisms for every object of . To show the correspondence between these two ideas of adjunction, we show that this assignment extends to a functor , with as defined above.
Defining the L🔗
We first show that the assignment of universal morphisms restricts to a functorial assignment . Recall that an object in is given by a codomain and a map . We define to be the codomain of the universal morphism:
: C.Ob → D.Ob
L₀ = universal-map-for x .bot .↓Obj.y
L₀ x
: (c : C.Ob) → C.Hom c (R.₀ (L₀ c))
L₀' = universal-map-for x .bot .map L₀' x
Given an arrow
in
,
we can send it to a uniquely-determined object in
:
We take the universal arrow assigned to
(an object of
),
and precompose with
.
This object will then serve as the domain of the morphism part of
,
which is given by the unique assignment arrows out of the initial object
in
(see lift↓
below).
private
: ∀ {a b} → C.Hom a b → (a ↙ R) .Precategory.Ob
to-ob {a} {b} h = record { map = L₀' b C.∘ h }
to-ob
: ∀ {x y} (g : C.Hom x y)
lift↓ → Precategory.Hom (x ↙ R) (universal-map-for x .bot) (to-ob g)
{x} {y} g = ¡ (universal-map-for x) {to-ob g}
lift↓
: ∀ {a b} → C.Hom a b → D.Hom (L₀ a) (L₀ b)
L₁ {a} {b} x = lift↓ x .β L₁
It now suffices to show the functor identities hold for L₁
. They follow essentially from the
uniqueness of maps out of an initial object.
private abstract
: ∀ {a} → L₁ (C.id {a}) ≡ D.id {L₀ a}
L-id {a} = ap β (¡-unique (universal-map-for a)
L-id (record { sq = C.elimr refl
.elimr refl
·· C(C.eliml R.F-id) }))
·· sym
: ∀ {x y z} (f : C.Hom y z) (g : C.Hom x y)
lemma → R.₁ (L₁ f D.∘ L₁ g) C.∘ (L₀' x)
(f C.∘ g) .map C.∘ C.id
≡ to-ob {x} {y} {z} f g =
lemma .₁ (lift↓ f .β D.∘ lift↓ g .β) C.∘ (L₀' x) ≡⟨ C.pushl (R.F-∘ _ _) ⟩
R.₁ (lift↓ f .β) C.∘ R.₁ (lift↓ g .β) C.∘ (L₀' x) ≡⟨ ap (R.₁ (lift↓ f .β) C.∘_) (sym (lift↓ g .↓Hom.sq) ∙ C.idr _) ⟩
R.₁ (lift↓ f .β) C.∘ L₀' y C.∘ g ≡⟨ C.extendl (sym (lift↓ f .↓Hom.sq) ∙ C.idr _) ⟩
R.∘ f C.∘ g ≡˘⟨ C.idr _ ⟩
L₀' z C(f C.∘ g) .map C.∘ C.id ∎
to-ob
: ∀ {x y z} (f : C.Hom y z) (g : C.Hom x y)
L-∘ → L₁ (f C.∘ g) ≡ L₁ f D.∘ L₁ g
= ap β (¡-unique (universal-map-for _) (record { sq = sym (lemma f g) })) L-∘ f g
That out of the way, we have our functor. We now have to show that it defines a left adjoint to the we started with.
: Functor C D
universal-maps→L .F₀ = L₀
universal-maps→L .F₁ = L₁
universal-maps→L .F-id = L-id
universal-maps→L .F-∘ = L-∘ universal-maps→L
Building the adjunction🔗
We now prove that , which, recall, means giving natural transformations (the adjunction unit) and (the adjunction counit). We begin with the counit, since that’s more involved.
The construction begins by defining a function mapd
which sends each object of
to the initial object in
.
Note that this is the same as L₀
,
but returning the entire object rather than a part of it.
private
: ∀ (x : C.Ob) → Ob (x ↙ R)
mapd = universal-map-for x .bot mapd x
Now for an object , we have , so by the assumption that has a collection of universal objects, the comma category has an initial object; Let us write that object as — recall that here, .
This means, in particular, that for any other object (with and in ), there is a unique map , which breaks down as a map such that the square below commutes.
: ∀ (x : D.Ob) → Hom (R.₀ x ↙ R) (mapd (R.₀ x)) _
ε = Initial.¡ (universal-map-for (R.₀ x)) {x = record { y = x ; map = C.id }} ε x
The magic trick is that, if we pick as the object of to map into, then in the diagram above must be ! We choose this map as our adjunction counit. A tedious calculation shows that this assignment is natural, essentially because is unique.
: universal-maps→L ⊣ R
universal-maps→L⊣R .counit .η x = ε x .↓Hom.β
universal-maps→L⊣R .counit .is-natural x y f =
universal-maps→L⊣R .β (
ap ↓Hom(universal-map-for (R.₀ x)) {record { map = R.₁ f }}
¡-unique₂ (record { sq =
.₁ f C.∘ C.id ≡⟨ C.idr _ ⟩
R.₁ f ≡˘⟨ C.cancell (sym (ε y .↓Hom.sq) ∙ C.idr _) ⟩
R.₁ (ε y .β) C.∘ _ C.∘ R.₁ f ≡˘⟨ ap₂ C._∘_ refl (sym (lift↓ (R.₁ f) .↓Hom.sq) ∙ C.idr _) ⟩
R.₁ (ε y .β) C.∘ R.₁ (L₁ (R.₁ f)) C.∘ mapd (R.₀ x) .map ≡⟨ C.pulll (sym (R.F-∘ _ _)) ⟩
R.₁ (ε y .β D.∘ L₁ (R.₁ f)) C.∘ mapd (R.₀ x) .map ∎ })
R(record { sq =
.₁ f C.∘ C.id ≡˘⟨ ap (R.₁ f C.∘_) (sym (ε x .↓Hom.sq) ∙ C.idr _) ⟩
R.₁ f C.∘ R.₁ (ε x .β) C.∘ mapd (R.₀ x) .map ≡⟨ C.pulll (sym (R.F-∘ _ _)) ⟩
R.₁ (f D.∘ ε x .β) C.∘ mapd (R.₀ x) .map ∎ })) R
For the adjunction unit, the situation is a lot easier. Recall that
we defined
on objects (L₀
) to be the codomain
part of the initial object of
;
The map part of that object then gives us a natural family of
morphisms
.
By definition. It’s so “by definition” that Agda can figure out the
components by itself:
.unit .η x = _
universal-maps→L⊣R .unit .is-natural x y f = sym (C.idr _) ∙ lift↓ f .↓Hom.sq universal-maps→L⊣R
If you think back to the adjunction counit, you’ll recall that it
satisfied a triangle that looks like the one below, and that the top map
(the map component of the initial object) is what we defined the
adjunction unit to be, so.. It’s zag
.
.zag {x} = sym (ε x .↓Hom.sq) ∙ C.idr _ universal-maps→L⊣R
The other triangle identity is slightly more annoying, but it works just as well. It follows from the uniqueness of maps out of the initial object:
.zig {x} =
universal-maps→L⊣R .β (
ap ↓Hom(universal-map-for x) {record { map = α }}
¡-unique₂ (record { sq =
.∘ C.id ≡⟨ C.idr _ ⟩
α C.cancell (sym (ε (L₀ x) .↓Hom.sq) ∙ C.idr _) ⟩
α ≡˘⟨ C.₁ _ C.∘ _ C.∘ α ≡˘⟨ C.pullr (sym (lift↓ α .↓Hom.sq) ∙ C.idr _) ⟩
R(R.₁ _ C.∘ R.₁ (F₁ L α)) C.∘ α ≡˘⟨ ap (C._∘ α) (R.F-∘ _ _) ⟩
.₁ (_ D.∘ F₁ L α) C.∘ α ∎
R})
(record { sq = C.id-comm ∙ ap (C._∘ _) (sym R.F-id) })
)
where α = L₀' x
= universal-maps→L L
From an adjunction🔗
To finish the correspondence, we show that any (left) adjoint functor defines a system of universal arrows ; This means that, not only does a “universal way of solving ” give a left adjoint to , it is a left adjoint to .
So, given an object , we must find an object and a universal map . Recall that, in the previous section, we constructed the left adjoint ’s action on objects by using our system of universal arrows; Symmetrically, in this section, we take the codomain to be . We must then find an arrow , but this is exactly the adjunction unit !
: ∀ x → Precategory.Ob (x ↙ R)
L⊣R→map-to-R .↓Obj.x = tt
L⊣R→map-to-R x .↓Obj.y = L.₀ x
L⊣R→map-to-R x .↓Obj.map = adj.unit.η _ L⊣R→map-to-R x
We must now show that the unit is universal among the pairs , with a map . Recall that for our object to be initial, we must find an arrow , and prove that this is the only possible such arrow; And that morphisms in the comma category break down as maps such that the triangle below commutes:
We can actually read off the map pretty directly from the diagram: It must be a map , but we’ve been given a map (the adjunction counit) and a map ; We may then take our to be the composite
L⊣R→map-to-R-is-initial: ∀ x → is-initial (x ↙ R) (L⊣R→map-to-R x)
.centre .↓Hom.α = tt
L⊣R→map-to-R-is-initial x other-map .centre .↓Hom.β =
L⊣R→map-to-R-is-initial x other-map .counit.ε _ D.∘ L.₁ (other-map .↓Obj.map)
adj.centre .↓Hom.sq =
L⊣R→map-to-R-is-initial x other-map (
sym .₁ (adj.counit.ε _ D.∘ L.₁ om.map) C.∘ adj.unit.η _ ≡⟨ ap₂ C._∘_ (R.F-∘ _ _) refl ∙ sym (C.assoc _ _ _) ⟩
R.₁ (adj.counit.ε _) C.∘ R.₁ (L.₁ om.map) C.∘ adj.unit.η _ ≡˘⟨ C.refl⟩∘⟨ adj.unit.is-natural _ _ _ ⟩
R(R.₁ (adj.counit.ε _) C.∘ adj.unit.η _ C.∘ om.map) ≡⟨ C.cancell adj.zag ⟩
.map ≡⟨ sym (C.idr _) ⟩
om.map C.∘ C.id ∎
om)
where module om = ↓Obj other-map
Checking that the triangle above commutes is a routine application of naturality and the triangle identities; The same is true for proving that the map above is unique.
.paths y =
L⊣R→map-to-R-is-initial x other-map _ _ refl (
↓Hom-path .counit.ε _ D.∘ L.₁ om.map ≡⟨ D.refl⟩∘⟨ L.expand (sym (C.idr _) ∙ y .↓Hom.sq) ⟩
adj.counit.ε _ D.∘ L.₁ (R.₁ y.β) D.∘ L.₁ (adj.unit.η _) ≡⟨ D.pulll (adj.counit.is-natural _ _ _) ⟩ -- nvmd
adj(y.β D.∘ adj.counit.ε _) D.∘ L.₁ (adj.unit.η _) ≡⟨ D.cancelr adj.zig ⟩
.β ∎
y)
where
module om = ↓Obj other-map
module y = ↓Hom y
Hence, we can safely say that having a functor and an adjunction is the same thing as having a functor and a system of universal arrows into :
: ∀ x → Universal-morphism x R
L⊣R→universal-maps .Initial.bot = L⊣R→map-to-R x
L⊣R→universal-maps x .Initial.has⊥ = L⊣R→map-to-R-is-initial x L⊣R→universal-maps x
By going from the adjunction to universal maps and then back to an adjunction, we recover :
: universal-maps→L R L⊣R→universal-maps ≡ L
L→universal-maps→L = Functor-path (λ _ → refl) λ f → L.pushr refl ∙ D.eliml adj.zig L→universal-maps→L
Adjuncts🔗
Another view on adjunctions, one which is productive when thinking about adjoint endofunctors , is the concept of adjuncts. Any pair of natural transformations typed like a unit and counit allow you to pass between the Hom-sets and , by composing the functorial action of (resp ) with the natural transformations:
: ∀ {a b} → D.Hom (L.₀ a) b → C.Hom a (R.₀ b)
L-adjunct = R.₁ f C.∘ adj.unit.η _
L-adjunct f
: ∀ {a b} → C.Hom a (R.₀ b) → D.Hom (L.₀ a) b
R-adjunct = adj.counit.ε _ D.∘ L.₁ f R-adjunct f
The important part that the actual data of an adjunction gets you is these functions are inverse equivalences between the hom-sets .
: ∀ {a b} → is-right-inverse (R-adjunct {a} {b}) L-adjunct
L-R-adjunct =
L-R-adjunct f .₁ (adj.counit.ε _ D.∘ L.₁ f) C.∘ adj.unit.η _ ≡⟨ R.pushl refl ⟩
R.₁ (adj.counit.ε _) C.∘ R.₁ (L.₁ f) C.∘ adj.unit.η _ ≡˘⟨ C.refl⟩∘⟨ adj.unit.is-natural _ _ _ ⟩
R.₁ (adj.counit.ε _) C.∘ adj.unit.η _ C.∘ f ≡⟨ C.cancell adj.zag ⟩
R
f ∎
: ∀ {a b} → is-left-inverse (R-adjunct {a} {b}) L-adjunct
R-L-adjunct =
R-L-adjunct f .counit.ε _ D.∘ L.₁ (R.₁ f C.∘ adj.unit.η _) ≡⟨ D.refl⟩∘⟨ L.F-∘ _ _ ⟩
adj.counit.ε _ D.∘ L.₁ (R.₁ f) D.∘ L.₁ (adj.unit.η _) ≡⟨ D.extendl (adj.counit.is-natural _ _ _) ⟩
adj.∘ adj.counit.ε _ D.∘ L.₁ (adj.unit.η _) ≡⟨ D.elimr adj.zig ⟩
f D
f ∎
: ∀ {a b} → is-equiv (L-adjunct {a} {b})
L-adjunct-is-equiv = is-iso→is-equiv
L-adjunct-is-equiv (iso R-adjunct L-R-adjunct R-L-adjunct)
: ∀ {a b} → is-equiv (R-adjunct {a} {b})
R-adjunct-is-equiv = is-iso→is-equiv
R-adjunct-is-equiv (iso L-adjunct R-L-adjunct L-R-adjunct)
Furthermore, these equivalences are natural.
L-adjunct-naturall: ∀ {a b c} (f : D.Hom (L.₀ b) c) (g : C.Hom a b)
→ L-adjunct (f D.∘ L.₁ g) ≡ L-adjunct f C.∘ g
=
L-adjunct-naturall f g .₁ (f D.∘ L.₁ g) C.∘ adj.unit.η _ ≡⟨ R.F-∘ _ _ C.⟩∘⟨refl ⟩
R(R.₁ f C.∘ R.₁ (L.₁ g)) C.∘ adj.unit.η _ ≡⟨ C.extendr (sym $ adj.unit.is-natural _ _ _) ⟩
(R.₁ f C.∘ adj.unit.η _) C.∘ g ∎
L-adjunct-naturalr: ∀ {a b c} (f : D.Hom b c) (g : D.Hom (L.₀ a) b)
→ L-adjunct (f D.∘ g) ≡ R.₁ f C.∘ L-adjunct g
= C.pushl (R.F-∘ f g)
L-adjunct-naturalr f g
L-adjunct-natural₂: ∀ {a b c d} (f : D.Hom a b) (g : C.Hom c d) (x : D.Hom (L.F₀ d) a)
→ L-adjunct (f D.∘ x D.∘ L.₁ g) ≡ R.₁ f C.∘ L-adjunct x C.∘ g
=
L-adjunct-natural₂ f g x (x D.∘ L.₁ g) ∙ ap (R.₁ f C.∘_) (L-adjunct-naturall x g)
L-adjunct-naturalr f
R-adjunct-naturall: ∀ {a b c} (f : C.Hom b (R.₀ c)) (g : C.Hom a b)
→ R-adjunct (f C.∘ g) ≡ R-adjunct f D.∘ L.₁ g
= D.pushr (L.F-∘ f g)
R-adjunct-naturall f g
R-adjunct-naturalr: ∀ {a b c} (f : D.Hom b c) (g : C.Hom a (R.₀ b))
→ R-adjunct (R.₁ f C.∘ g) ≡ f D.∘ R-adjunct g
=
R-adjunct-naturalr f g .counit.ε _ D.∘ L.₁ (R.₁ f C.∘ g) ≡⟨ D.refl⟩∘⟨ L.F-∘ _ _ ⟩
adj.counit.ε _ D.∘ L.₁ (R.₁ f) D.∘ L.₁ g ≡⟨ D.extendl (adj.counit.is-natural _ _ _) ⟩
adj.∘ (adj.counit.ε _ D.∘ L.₁ g) ∎
f D
R-adjunct-natural₂: ∀ {a b c d} (f : D.Hom a b) (g : C.Hom c d) (x : C.Hom d (R.F₀ a))
→ R-adjunct (R.₁ f C.∘ x C.∘ g) ≡ f D.∘ R-adjunct x D.∘ L.₁ g
=
R-adjunct-natural₂ f g x (x C.∘ g) ∙ ap (f D.∘_) (R-adjunct-naturall x g) R-adjunct-naturalr f
Induced adjunctions🔗
Any adjunction
induces, in a very boring way, an opposite adjunction
between opposite functors
:
module _ {L : Functor C D} {R : Functor D C} (adj : L ⊣ R) where
private
module L = Functor L
module R = Functor R
module adj = _⊣_ adj
open _⊣_
open _=>_
: R.op ⊣ L.op
opposite-adjunction .unit .η _ = adj.counit.ε _
opposite-adjunction .unit .is-natural x y f = sym (adj.counit.is-natural _ _ _)
opposite-adjunction .counit .η _ = adj.unit.η _
opposite-adjunction .counit .is-natural x y f = sym (adj.unit.is-natural _ _ _)
opposite-adjunction .zig = adj.zag
opposite-adjunction .zag = adj.zig opposite-adjunction
As well as adjunctions and between postcomposition and precomposition functors, respectively:
open import Cat.Functor.Coherence
: postcompose L {D = E} ⊣ postcompose R
postcomposite-adjunction .unit .η F = cohere! (adj.unit ◂ F)
postcomposite-adjunction .unit .is-natural F G α = Nat-path λ _ → adj.unit.is-natural _ _ _
postcomposite-adjunction .counit .η F = cohere! (adj.counit ◂ F)
postcomposite-adjunction .counit .is-natural F G α = Nat-path λ _ → adj.counit.is-natural _ _ _
postcomposite-adjunction .zig = Nat-path λ _ → adj.zig
postcomposite-adjunction .zag = Nat-path λ _ → adj.zag
postcomposite-adjunction
: precompose R {D = E} ⊣ precompose L
precomposite-adjunction .unit .η F = cohere! (F ▸ adj.unit)
precomposite-adjunction .unit .is-natural F G α = Nat-path λ _ → sym (α .is-natural _ _ _)
precomposite-adjunction .counit .η F = cohere! (F ▸ adj.counit)
precomposite-adjunction .counit .is-natural F G α = Nat-path λ _ → sym (α .is-natural _ _ _)
precomposite-adjunction .zig {F} = Nat-path λ _ → Func.annihilate F adj.zag
precomposite-adjunction .zag {F} = Nat-path λ _ → Func.annihilate F adj.zig precomposite-adjunction