Given functors F : C → D
and G : D → E
, a functor composition G ∘ F : C → E
is a mapping of objects between categories C
and E
, such that (G ∘ F)(X) = G(F(X))
and a mapping between morphisms such that (G ∘ F)(f) = G(F(f))
.
This suggests that your CFunctor
instance should be defined as follows:
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 f = cmap (cmap f)
However, composing cmap
twice gives you Dom f a b -> Cod g (g (f a)) (g (f b))
and cmap
in this instances has a type Dom f a b -> Cod g (Wrap g f a) (Wrap g f b)
.
We can get from g (f a)
to Wrap g f
and vice versa, but since the instance declaration doesn't make any assumptions about the structure of Cod g
, we are out of luck.
Since we know that functor is a mapping between categories, we can use the fact that Cod g
is a Category
(on Haskell side, this requires a Category (Cod g)
constraint), this gives us few operations to work with:
cmap f = lift? unWrap >>> cmap (cmap f) >>> lift? Wrap
This, however, requires a convenient lifting operator lift?
, which lifts a function from Hask
category to Cod g
category. Writing Cod g
as (~>)
, the type of lift?
must be:
lift? :: (a -> b) -> (a ~> b)
lift? unWrap :: Wrap g f a ~> g (f a)
cmap (cmap f) :: g (f a) ~> g (f b)
lift? Wrap :: g (f b) ~> Wrap g f b
lift? unWrap >>> cmap (cmap f) >>> lift? Wrap :: Wrap g f a ~> Wrap g f b
Now, there are at least two choices for this lifting operator:
- You could expand the constrait from
Category (Cod g)
toArrow (Cod g)
in which case the lifting operator becomesarr
, - or, as Sjoerd Visscher mentions in comments, you could use the fact that
Wrap
andunWrap
are semanticallyid
at runtime, in which case the use ofunsafeCoerce
is justified.