Jacques Carette
Jacques Carette

Reputation: 3135

Trying to make a wrapped type family into a something Functor-like

Here is a simple example where the identity Functor works well:

newtype R a = R a

instance Functor R where
    fmap f (R a) = R $ f a

but if I add an intermediate type family, things get wonky:

data IntT
data a :-> b

type family   Sem a         :: *
type instance Sem IntT      = Int
type instance Sem (a :-> b) = Sem a -> Sem b

newtype S a = S (Sem a)

and now I can't make S into a Functor. I can easily define a new class of Functor-like things, but then I will also need a class of Applicative-like and Monad-like, and that seems like an unhappy road. Especially as

smap f (S a) = S $ f a

actually has the type I want, namely smap :: (Sem a -> Sem b) -> S a -> S b. But, of course, this is not the type of a Functor. (Don't you just hate it when the "same" code has 2 different, incompatible types?)

I have explored Data.Category.Functor as well as Generics.Pointless.Functors, but neither seemed to quite solve my problem either. PointlessTypeFamilies seemed to have further good ideas, and yet I am still unsure how to get something sufficiently Functor-like out of this.

It has dawned onto me that even though the code for smap is identical to that of fmap for R, what is going on is slightly different. In a way, if I had a natural transformation from Sem to S, then somehow I ought to be able to lift that to obtain smap. At that point, I figured I might as well ask here, that might save me quite a bit of trouble!

Upvotes: 7

Views: 429

Answers (1)

Edward Kmett
Edward Kmett

Reputation: 29962

When I encounter a situation like this I usually switch to something like:

data S b = forall a. S (a -> b) (Sem a)

which can easily be made into a law-abiding Functor

instance Functor S where
  fmap f (S g s) = S (f . g) s

or I turn to Coyoneda to package up that behavior for me.

Upvotes: 3

Related Questions