Making mapped with category's Functor

139 Views Asked by At

I love mapped from the lens package.

mapped :: Prelude.Functor f => Setter (f a) (f b) a b 

However I have recently started to use Functor from the category package (from here out I will refer to category's Functor just as Functor and use Prelude.Functor otherwise), so my mapped no longer works.

So I would like to make a version of mapped that can work with Functor. As a refresher for all who need it, the kind signature of Functor looks like:

class (Category s, Category t) => Functor (s :: α -> α -> *) (t :: β -> β -> *) (f :: α -> β) where
  map :: s a b -> t (f a) (f b)

This takes two of the (->)s in the vanilla fmap and replaces them with s and t which satisfy Category.

So if we would like to make a replacement for mapped we need to replace the appropriate (->) with generic Category satisfying types. So we dealias the Setter:

mapped ::
  Prelude.Functor f =>
    ( forall g. Settable g =>
      (a -> g b) -> (f a) -> g (f b)
    )

In order to move forward we are going to look at over since we would want it to be that:

over mapped = map

Now we look at the implementation of over (unpacking ASetter):

over :: ((a -> Identity b) -> s -> Identity t) -> (a -> b) -> s -> t
over l f = runIdentity #. l (Identity #. f)

We can get a little bit of information out of this. We know the types of runIdentity, (#.) and the goal. If we work backwards

(#.) runIdentity (mapped (Identity #. f)) ::
  Category t => t (f a) (f b)
mapped (Identity #. f) ::
  ( Category t
  , Profunctor t
  , Coercible (f b) z
  )
    => t (f a) z

Which is good evidence that the third (->) should be replaced with a generic category, and decent evidence that the second should not.

mapped ::
  ( Functor s t f
  , Category s
  , Category t
  ) =>
    ( forall g. Settable g =>
      (s a (g b)) -> t (f a) (g (f b))
    )

And here I am stuck. I feel as if I am pressing up against my conceptual backing here. I don't know if this type is correct or if I am missing something. Even if I did know the type I'm not sure how I would even implement mapped or over. I am used to using the prebuilt combinators out of the lens package to build my lenses and I don't think those are going to help me once I am no longer using a Setter.

How can I get from where I am to a working mapped implementation for Functor?

1

There are 1 best solutions below

2
On

I believe the type is definitely correct. Poking around in the code, mapped is defined as

mapped = taintedDot . fmap . untaintedDot

where taintedDot, untaintedDot are methods of Settable. As @leftaroundabout's comment makes the point, this is probably the difficulty of the translation - you would need an equivalent class for categories, though exactly what it would mean is beyond me.

A naive guess would be

class Settable f where
  untainted :: Category c => c (f a) a
  tainted   :: Category c => c a (f a)

which look a lot like Monad and Comonad operations, so maybe

class (forall c. Monad c f, forall c. Comonad c f) => Settable f where
  untainted :: Category c => c (f a) a
  untainted = counit
  tainted   :: Category c => c a (f a)
  tainted   = unit

Alternatives...

That said, you could cheat - the Profunctor equivalent in Category is just an Arrow, since you can lift Hask arrows to categorical ones. If your Categorys are also Arrows, then you could do

data Cheat c a b = Cheat { unCheat :: c a b }

instance (Arrow c) => Profunctor (Cheat c) where
  lmap f = (arr f >>>)
  rmap f = (>>> arr f)

Then sneak in the Cheat

mapped' ::
  ( Functor s t f
  , Arrow s
  , Arrow t
  ) =>
    ( forall g. Settable g =>
      (s a (g b)) -> t (f a) (g (f b))
    )
mapped' = unCheat . taintedDot . Cheat . map . unCheat . untaintedDot . Cheat