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?
Morphisms in this category are natural transformations between functors. Functors are often defined by universal properties; examples are the tensor product, the direct sum and direct product of groups or vector spaces, construction of free groups and modules, direct and inverse limits.
In functional programming, a functor is a design pattern inspired by the definition from category theory, that allows for a generic type to apply a function inside without changing the structure of the generic type.
Functor categories serve as the hom-categories in the strict 2-category Cat. In the context of enriched category theory the functor category is generalized to the enriched functor category.
Functor in Haskell is a typeclass that provides two methods – fmap and (<$) – for structure-preserving transformations. To implement a Functor instance for a data type, you need to provide a type-specific implementation of fmap – the function we already covered.
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:
Category (Cod g)
to Arrow (Cod g)
in which case the lifting operator becomes arr
,Wrap
and unWrap
are semantically id
at runtime, in which case the use of unsafeCoerce
is justified.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With