Grant B. asked me to post the derivation for the right and left Kan extension formula used in previous Kan Extension posts (1,2). For that we can turn to the definition of Kan extensions in terms of ends, but first we need to take a couple of steps back to find a way to represent (co)ends in Haskell.
Dinatural Transformations
Rather than repeat the definition here, we'll just note we can define a dinatural transformation in Haskell letting polymorphism represent the family of morphisms.
type Dinatural f g = forall a. f a a -> g a a
Ends
So what is an end?
An end is a universal dinatural transformation from some object e to some functor s.
Diving into the formal definition:
Given a functor , and end of is a pair where is an object of and omega is a dinatural transformation from e to S such that given any other dinatural transformation to S from another object x in , there exists a unique morphism , such that for every in .
We usually choose to write ends as , and abuse terminology calling the end of .
Note this uses a dinatural transformation from an object in , which we can choose to represent an arbitrary dinatural transformation from an object to a functor in terms of a dinatural transformation from the constant bifunctor:
newtype Const x a b = Const { runConst :: x }
This leaves us with the definition of a dinatural transformation from an object as:
Dinatural (Const x) s ~ forall a. Const x a a -> s a a
but the universal quantification over the Const term is rather useless and the Const bifunctor is supplying no information so we can just specialize that down to:
type DinaturalFromObject x s = x -> forall a. s a a
Now, clearly for any such transformation, we could rewrite it trivially using the definition:
type End s = forall a. s a a
with = id.
type DinaturalFromObject x s = x -> End s
And so End above fully complies with the definition for an end, and we just say e = End s abusing the terminology as earlier. The function = id is implicit.
For End to be a proper end, we assume that s is contravariant in its first argument and covariant in its second argument.
Example: Hom
A good example of this is (->) in Haskell, which is as category theory types would call it the Hom functor for Hask. Then End (->) = forall a. a -> a which has just one inhabitant id if you discard cheating inhabitants involving fix or undefined.
We write or to denote a -> b. Similarly we use to denote an exponential object within a category, and since in Haskell we have first class functions using the same syntax this also translates to a -> b. We'll need these later.
Example: Natural Transformations
If we define:
newtype HomFG f g a b =
HomFG { runHomFG :: f a -> g b }
then we could of course choose to define natural transformations in terms of End as:
type Nat f g = End (HomFG f g) -- forall a. f a -> g a
Right Kan Extension as an End
Turning to Wikipedia or Categories for the Working Mathematician you can find the following definition for right Kan extension of along in terms of ends.
Working by rote, we note that is just c -> K m as noted above, and that is then just (c -> K m) -> T m'. So now we just have to take the end over that, and read off:
newtype RanT k t c m m' = (c -> k m) -> t m'
type Ran k t c = End (RanT k t c)
Which, modulo newtype noise is the same as the type previously supplied type:
newtype Ran f g c = Ran { runRan :: forall m. (c -> f m) -> g m }
Coends
The derivation for the left Kan extension follows similarly from defining coends over Hask in terms of existential quantification and copowers as products.
The coend derivation is complicated slightly by Haskell's semantics. Disposing of the constant bifunctor as before we get:
type DinaturalToObject s c = forall a. (s a a -> c)
Which since we want to be able to box up the s a a term separately, we need to use existential quantification.
(forall a. s a a -> c) ~ (exists a. s a a) -> c
We cannot represent this directly in terms of a type annotation in Haskell, but we can do so with a data type:
data Coend f = forall a. Coend (f a a)
Recall that in Haskell, existential quantification is represented by using universal quantification outside of the type constructor.
The main difference is that in our coend the function is now runCoend instead of id, because we have a Coend data constructor around the existential. Technicalities make it a little more complicated than that even, because you can't define a well-typed runCoend and have to use pattern matching on the Coend data constructor to avoid having an existentially quantified type escape the current scope, but the idea is the same.
Left Kan Extension as a Coend
Then given the definition for the left Kan extension of along as a coend:
we can read off:
data LanT k t c m m' = LanT (k m -> c) (t m')
type Lan k t c = Coend (LanT k t c)
Which is almost isomorphic to the previously supplied type:
data Lan k t c = forall m. Lan (k m -> c) (t m)
except for the fact that we had to use two layers of data declarations when using the separate Coend data type, so we introduced an extra place for a to hide.
A newtype isn't allowed to use existential quantification in Haskell, so this form forces a spurious case analysis that we'd prefer to do without. This motivates why category-extras uses a separate definition for Lan rather than the more elaborate definition in terms of Coend.
[Edit: Fixed a typo in the definition of RanT]
Discussion
I’m curious as to how you would motivate Kan extensions as a functional programming (specifically Haskell) tool. The right Kan extension almost looks like the CPS-transform of “a”, except with variant answer types. Shan’s thesis on page 73 shows how the ordinary function type is modified via the presence of delimited control into something like a right Kan extension.
Care to shed some light on this (and its dual)?
Well, you can look at the
Codensitymonad(Ran m m)of a monad m as a weaker form ofContTwhich isn’t allowed to use the type of the ultimatereturnvalue to ‘pull any tricks’. Consequently, it has fewer inhabitants thanContTand we can say more about them. My favorite example so far is in:http://wwwtcs.inf.tu-dresden.de/~voigt/mpc08.pdf
That paper shows that you can substitute a reference to the
Codensitymonad of a free monad in many places where you’d use the original free monad and change the asymptotic performance of the resulting algorithm because the continuation ‘linearizes’ the cost of repeatedly traversing the tree by piling the cost up in the continuation so you only traverse once.I’d conjecture that the left Kan extension of a cofree comonad should let us pile up the cost of mapping or extending cofree comonads similarly. Though here I have yet to actually generate any performance results here due to other distractions.
We _can_ however definitely do the same thing with the Yoneda and Coyoneda lemmas. The resulting (co)monads fit very closely to the operational characteristics of the base monad.
If you transform a monad with Yoneda you get a monad that has the same overall costs for
returnand>>=, but where if your monad had a non constant cost formap(such as in the free monad of anything other than the trivial functor that maps everything to _|_), you can fuse the costs of all of your maps together by accumulating them and applying them all at once during the next(>>=)(or when you extract f from theYoneda f).Similarly
CoYonedaaccumulates maps for comonads, which is a win ifmapis expensive (such as in the cofree comonad case).You can get a feel for why this works by looking at:
http://comonad.com/haskell/category-extras/src/Control/Functor/Yoneda.hs
and noting what
fmapdoes and how>>=works for Yoneda. and howfmapandextendwork forCoYoneda.In category-extras I have generalized all of the constructive algorithmic morphisms that use the free monad or cofree comonad (histomorphisms, futumorphisms, dynamorphisms, chronomorphisms) to support the codensity monad of the free monad, density comonad of the cofree comonad, Yoneda of the free monad, Coyoneda of the cofree comonad, etc by defining a couple of extra typeclasses that express their ‘Free-Like’ behavior in the spirit of the above linked paper by Voigtlaender in
http://comonad.com/haskell/category-extras/src/Control/Monad/Free.hs
The generalization can be seen in use here:
http://comonad.com/haskell/category-extras/src/Control/Morphism/Futu.hs
and the instances for
Codensityhere:http://comonad.com/haskell/category-extras/src/Control/Monad/Codensity.hs
Now that I’ve been thinking about these things I can point out a tiny tiny typo:
data LanT k t c m m' = LanT (k m -> c) (t m)should probably be
data LanT k t c m m' = LanT (k m -> c) (t m')though it makes no difference in the definition of
Lan.On a less trivial level, I still don’t know what theorem I could quote to justify that, say,
type DinaturalFromObject x s = x -> forall a. s a adefines dinaturals from a constant functor. For a specific choice of s dinaturality is a free theorem. But how do you uniformly prove this for all s, implemented in Haskell, with the right c/contravariance in the arguments?
Hey Dan,
Fixed the
LanTdefinition. (Woops!)Re the dinatural transformation piece, I’m not as deep as I used to be in this area, so I’ll go back and see what I can dig up from whatever notes I still have from when I wrote this. I recall it seeming quite obvious at the time, but now I can only barely remember what dinaturality is. :) I’d be quite surprised (and pleased) if you came up with a counterexample, however. ;)
All of the various types of naturality (including dinaturality and extranaturality) seem to arise as free theorems but I’ve not seen a paper spelling this out clearly – at least not in a form I’d recognise. I can’t tell if it’s because nobody has figured it the details, or if the papers are just over my head.
Just a minor typo note:
> newtype RanT f g c m m' = (c -> K m) -> T m'Should be:
> newtype RanT k t c m m' = (c -> k m) -> t m'right?
Wren: Fixed!
Isn’t the type
(exists a. f a a)the coproduct rather than the coend? The coend is the coproduct modulo the relation imposed by the commuting square (degenerate hexagon) in the definition of a dinatural transformation.