Functors are the tools to relate two categories, in turn natural transformations are the tool to relate two functors.
A natural transformation is a relation between two functors between two categories. Let’s call them f and g and say they are between categories C and D. We’re going to say that they have maps on objects f:C→D and g:C→D, as well as maps on morphisms F:C(x,y)→D(f(x),f(y)) and G:C(x,y)→D(g(x),g(y)).
A natural transformation is defined primarily by its component which says that for each object in C we obtain a morphism in D by using the map on object from functors f and g, we call the component φ. More succinctly we write φ:∀x∈∣C∣.D(f(x),g(x)).
We can already start writing this definition in Idris using a record, we are going to use the symbol ==>> for natural transformations, it is meant to represent a thick arrow.
Definition
Given two functors F,G:C→D, a natural transformation F⇒G is given by:
A component φ:∀x∈∣C∣.D(f(x),g(x))
For all morphisms m∈C(x,y), the commuting square F(m);φy≡φx;G(m)
Fix how the arrow is rendered in latex
record (=>>) {0 cObj, dObj : Type} {0 c : Category cObj} {0 d : Category dObj} (f, g : c ->> d) where constructor MkNT component : (v : cObj) -> f.mapObj v ~> g.mapObj v
In the above you see that each natural transformation needs two functors, and each of them require two categories. The component is defined as a morphism in the category d resulting from mapping the object v using both f and g.
The definition does not end here because we also need to make sure that the component respects the map on morphism from each functor. For this, we ensure that each morphism in C applied to g can be pre-composed with the components, or post-composed and applied to the functor f, for the same result. That is ∀m∈C(x,y).F(m);φy≡φx;G(m), we can represent it with the following commutative diagram.
It is left to translate this diagram into a field for our idris definition. As the diagram suggests, we need two objects in C as the source and target of a morphism m in C, and using, we ensure that the equation holds.
0 commutes : (0 x, y : cObj) -> (m : x ~> y) -> let -- We build each side of the naturality square 0 top : f.mapObj x ~> g.mapObj x top = component x 0 bot : f.mapObj y ~> g.mapObj y bot = component y 0 left : f.mapObj x ~> f.mapObj y left = f.mapHom _ _ m 0 right : g.mapObj x ~> g.mapObj y right = g.mapHom _ _ m -- And the compose them. 0 comp1 : f.mapObj x ~> g.mapObj y comp1 = top |> right 0 comp2 : f.mapObj x ~> g.mapObj y comp2 = left |> bot in comp1 === comp2
The simplest natural transformation we can build is the identity natural
transformation. Given a functor f : c -> d we can build the identity natural
transformation using f on the identity morphism in c.
identity : {0 o1, o2 : Type} -> {c : Category o1} -> {0 d : Category o2} -> {f : c ->> d} -> f =>> fidentity = MkNT (\x => f.mapHom x x (c.id x)) (\x, y, m => let 0 steps : CongPipeline ? (f.mapObj x ~> f.mapObj y) steps = (fmap (c.id x) |> fmap m) :: AddNest (f.mapHom x y) [ (c.id x |> m) , m , (m |> c.id y)] [ fmap m |> fmap (c .id y) ] in runProof steps [ sym (presComp f x x y (c.id x) m), c.idLeft _ _ m, sym (c.idRight _ _ m), presComp f x y y m (c.id y)] )
We also define an equality relation on natural transformation, this is useful
to perform higher-level equalities for example when defining the category of
functors and natural transformations. In particular, two natural
transformations are equal when their components are equal for all inputs.
Definition
Equality of natural transformations is given by equality of their component.
public exportrecord NTEq {0 o1, o2 : Type} {0 c : Category o1} {0 d : Category o2} {f, g : c ->> d} (n1, n2 : f =>> g) where constructor MkNTEq 0 sameComponent : (v : o1) -> n1.component v === n2.component v
Proposition
We can convert from natural transformation equality to propositional equality.
One of the main feature of natural transformation is that they can be composed in two different ways.
Vertical composition of natural transformations stiches two natural transformation n : f =>> g and m : g =>> h
and produces a single natural transformation (n ⨾⨾⨾ m) : f =>> h. Here f, g, and h, are all functors c -> d.
Proposition
Given two categories C and D and functors F,G,H:C→D,and natural transformations n:F⇒G and m:G⇒H, we can obtain
a natural transformation n;vm:F⇒H by composing the components of n and m, and composing their naturality squares.
We can represent this notion of composition with the diagram on figure \ref{fig:vertical-comp}, which also illustrates the “verticality” of this notion of composition.
(⨾⨾⨾) : {0 cObj, dObj : Type} -> {0 c : Category cObj} -> {d : Category dObj} -> {f, g, h : c ->> d} -> f =>> g -> g =>> h -> f =>> h(⨾⨾⨾) nat1 nat2 = let newComponent : (v : cObj) -> f.mapObj v ~> h.mapObj v newComponent x = nat1.component x |> nat2.component x 0 compProof : (0 x, y : cObj) -> (m : x ~> y) -> let 0 n1, n2 : f.mapObj x ~> h.mapObj y n1 = f.mapHom x y m |> (nat1.component y |> nat2.component y) n2 = (nat1.component x |> nat2.component x) |> h.mapHom x y m in n2 === n1 compProof x y m = sym (d.compAssoc {}) `trans` glueSquares (nat1.commutes x y m) (nat2.commutes x y m) in MkNT newComponent compProof
The second way of composing natural transformation requires 2 pairs of functors
that can compose functors end to end. For example, given functors F1:C1→D1 and G1:D1→E1 as well
as F2:C2→D2 and G2:D2→E2 (where C1,C2,D1,D2,E1,E2 are all categories), and
two natural transformations n:F1⇒F2 and m:G1⇒G2, then we can compose them to obtain
the natural transformation n∗m:G1∘F1⇒G2∘F2 where ∘ is functor composition.
Proposition
Horizontal composition is obtained given the following:
Categories C1,C2,D1,D2,E1,E2
Functors
F1:C1→D1
G1:D1→E1
F2:C2→D2
G2:D2→E2
Natural transformations n:F1⇒F2 and m:G1⇒G2
Then we can compose n and m by composing their functors to obtain n;hm
-- Operator for horizontal composition(-⨾-) : {0 o1, o2, o3 : Type} -> {0 a : Category o1} -> {0 b : Category o2} -> {c : Category o3} -> {l, k : a ->> b} -> {g, h : b ->> c} -> k =>> l -> g =>> h -> k ⨾⨾ g =>> l ⨾⨾ h(-⨾-) nt1 nt2 = MkNT (\v => nt2.component (k.mapObj v) |> h.mapHom (k.mapObj v) (l.mapObj v) (nt1.component v)) -- proof in appendix
Whiskering is the ability to precompose, or postcompose, the same functor to the source
and target of a natural transformation. Precomposing a functor f to a natural transformation g =>> h
is called “left whiskering” and results in a natural transformation (f ⨾⨾ g) =>> (f ⨾⨾ h).
Postcomposing a functor f to a natural transformation g =>> h is called “right whiskering” and results in
a natural transformation (g ⨾⨾ f) =>> (h ⨾⨾ f). Both whiskerings are obtained as a special case of horizontal
composition, namely, horizontal composition with the identity natural transformation.
(⨾-) : {a : Category _} -> {b : Category _} -> {c : Category _} -> (f : a ->> b) -> {g, h : b ->> c} -> g =>> h -> (f ⨾⨾ g) =>> (f ⨾⨾ h)(⨾-) f n = identity {f} -⨾- n
(-⨾) : {0 b : Category _} -> {c : Category _} -> {d : Category _} -> {g, h : b ->> c} -> g =>> h -> (f : c ->> d) -> (g ⨾⨾ f) =>> (h ⨾⨾ f)(-⨾) n f = n -⨾- identity {f}
Lemma
Given functors F1,G1,H1:C→D and F2,G2,H2:D→E and natural transformations:
m1:F1⇒G1
m2:F2⇒G2
n1:G1⇒H1
n2:G2⇒H2
Then we define the interchange property that relates horizontal and vertical composition with the two ways
of obtaining a natural tranformation F1;F2⇒H1;H2
Natural transformations give us a notion of “mapping” for functors, this notion can be extended to be stronger
requiring it to work in both directions. Such definition would require that the component of a natural transformation to be a bijection
record (=~=) {0 o1, o2 : Type} {0 c : Category o1} {0 d : Category o2} (0 f, g : c ->> d) where constructor MkNaturalIsomorphism nat : f =>> g tan : g =>> f 0 η_φ : (v : o1) -> (nat.component v |> tan.component v) {a = (f.mapObj v), b = (g.mapObj v), c = (f.mapObj v)} === d.id (f.mapObj v) 0 φ_η : (v : o1) -> (tan.component v |> nat.component v) {a = (g.mapObj v), b = (f.mapObj v), c = (g.mapObj v)} === d.id (g.mapObj v)
Natural isomorphism are also symetric and transitive.
Proposition
Natural isomorphisms are symmetric.
symNT : {0 o1, o2 : Type} -> {0 c : Category o1} -> {0 d : Category o2} -> {0 f, g : c ->> d} -> f =~= g -> g =~= fsymNT nt = MkNaturalIsomorphism nt.tan nt.nat nt.φ_η nt.η_φ
Proposition
Natural isomorphisms are transitive.
public exporttransNT : {0 o1, o2 : Type} -> {0 c : Category o1} -> {d : Category o2} -> {f, g, h : c ->> d} -> f =~= g -> g =~= h -> f =~= htransNT nt mt = MkNaturalIsomorphism (nt.nat ⨾⨾⨾ mt.nat) (mt.tan ⨾⨾⨾ nt.tan) -- Proofs in appendix
The above properties ensures natural transformations form a preorder, we make this fact explicit by implementing the relevant interfaces so that we can make use of it later.
Proposition
Natural transformations form a preorder.
%unbound_implicits onpublic export{a : _} -> Reflexive (a ->> b) (=>>) where reflexive = identitypublic export{b : _} -> Transitive (a ->> b) (=>>) where transitive f g = f ⨾⨾⨾ gexport{a, b : _} -> Preorder (a ->> b) (=>>) where