In a previous answer, Petr Pudlak defined the CFunctor
class, for functors other than those from Hask to Hask. Re-writing it a bit using type families, it looks like
class CFunctor f where
type Dom f :: * -> * -> * -- domain category
type Cod f :: * -> * -> * -- codomain category
cmap :: Dom f a b -> Cod f (f a) (f b) -- map morphisms across categories
with instances that look like, e.g.
instance CFunctor Maybe where
type Dom Maybe = (->) -- domain is Hask
type Cod Maybe = (->) -- codomain is also Hask
cmap f = \m -> case m of
Nothing -> Nothing
Just x -> Just (f x)
In category theory, whenever F : C --> D is a functor and G : D --> E is a functor, then the composition GF : C --> E is also a functor.
I'd like to express this in Haskell. Since I can't write instance CFunctor (f . g)
I introduce a wrapper class:
newtype Wrap g f a = Wrap { unWrap :: g (f a) }
In writing the CFunctor
instance I get as far as
instance (CFunctor f, CFunctor g, Cod f ~ Dom g) => CFunctor (Wrap g f) where
type Dom (Wrap g f) = Dom f
type Cod (Wrap g f) = Cod g
cmap = undefined
but I can't figure out what the implementation of cmap
should be. Any advice?
PS the eventual reason for all this is to also introduce a class Adjunction
with methods unit
and counit
, and then automatically derive monad instances from adjunctions. But first, I need to show the compiler that the composition of two functors is also a functor.
I'm aware that I could use cmap.cmap
on an object of type g (f a)
and that would work, but it seems a little like cheating - surely a functor is just a functor, and the compiler shouldn't have to know that it's actually the composition of two other functors?
DeriveFunctor
extension would certainly handle that (and many other cases) automatically. But if you have to manually implement it, I don't see why you'd expect anything simpler than mapping over composed functors by composing maps over each? – C. A. McCanncmap f = cmap (cmap f)
is the definition of the composition of functors, so why should using that be cheating in any way? – Daniel Fischercmap = cmap . cmap
as the implementation, but that's not correct for the type constructorWrap g f
(in particular, it takesDom f a b
toCod g (g (f a)) (g (f b))
rather thanCod g (Wrap g f a) (Wrap g f b)
. As stated, I'm particularly interested in cases other than endofunctors on Hask. – Chris Taylorg (f a)
isn't just the composition of two functors, it's also a functor itself, so I think I should be able to usecmap
on it (kind of similar to how you don't have to writelift . lift . lift
all over the place when using monad transformers, if you can tell the compiler how to lift certain functions automatically -- here I want to tell the compiler how to map a morphism across two categories with a single application ofcmap
). – Chris Taylorcmap
forWrap f g
. But, looking at it, I don't see how to do it for generalg
; only forCod g = (->)
is it obvious. – Daniel Fischer