Haskell-MMorph-Library icon indicating copy to clipboard operation
Haskell-MMorph-Library copied to clipboard

Add MCoyoneda

Open juliapath opened this issue 8 years ago • 1 comments

Sometimes it can be difficult to implement an MFunctor instance for a given type due to the lack of a Monad constraint on the result type of hoist. Especially if you only have access to what is exported in the API for that type (e.g. 1, 2). However this can be worked around using a coyoneda construction like this (from my answer in the second link):

{-# LANGUAGE RankNTypes, GADTs #-}

import Control.Monad.Morph

-- Slightly weaker than MFunctor due to the monad constraint on n.
class MFunctor' t where
  hoist' :: (Monad m, Monad n) => (forall b. m b -> n b) -> t m a -> t n a

data MCoyoneda t n a where
  MCoyoneda :: Monad m => (forall b. m b -> n b) -> t m a -> MCoyoneda t n a

liftMCoyoneda :: Monad m => t m a -> MCoyoneda t m a
liftMCoyoneda = MCoyoneda id

lowerMCoyoneda' :: (MFunctor' t, Monad n) => MCoyoneda t n a -> t n a
lowerMCoyoneda' (MCoyoneda f tma) = hoist' f tma

-- The result is actually slightly stronger than 'MFunctor', as we do not need
-- a monad for 'm' either.
hoistMCoyoneda :: (forall b. m b -> n b) -> MCoyoneda t m a -> MCoyoneda t n a
hoistMCoyoneda f (MCoyoneda trans tma) = MCoyoneda (f . trans) tma

instance MFunctor (MCoyoneda t) where
  hoist = hoistMCoyoneda

Would this be a useful addition to mmorph?

juliapath avatar Jan 23 '17 23:01 juliapath

The only suggestion I'd make is to not add the MFunctor' class (mainly because I'd like to avoid type class proliferation) and instead replace lowerMCoyoneda with another function which I'm calling withCoyoneda:

{-# LANGUAGE RankNTypes                #-}
{-# LANGUAGE ExistentialQuantification #-}

import Control.Monad.Morph

-- Minor simplification to use just `ExistentialQuantification`
data MCoyoneda t n a =
    forall m . Monad m => MCoyoneda (forall b . m b -> n b) (t m a)

liftMCoyoneda :: Monad m => t m a -> MCoyoneda t m a
liftMCoyoneda = MCoyoneda id

withCoyoneda
    :: (forall m . Monad m => (forall b . m b -> n b) -> t m a -> r)
    -> MCoyoneda t n a
    -> r
withCoyoneda k (MCoyoneda f tma) = k f tma

instance MFunctor (MCoyoneda t) where
  hoist f (MCoyoneda trans tma) = MCoyoneda (f . trans) tma

Then you can choose what hoist function to supply to withCoyoneda. For example, if you supply withCoyoneda with hoist, then you get this inferred type:

>>> :type withCoyoneda hoist
withCoyoneda hoist :: MFunctor t => MCoyoneda t n a -> t n a

... but if you supply withCoyoneda with hoist' then you get this inferred type:

>>> :type withCoyoneda hoist'
withCoyoneda hoist'
  :: (MFunctor' t, Monad n) => MCoyoneda t n a -> t n a

That then would let you keep MCoyoneda in mmorph but you could define MFunctor' separately and supply your hoist' function to withCoyoneda

Gabriella439 avatar Jan 25 '17 17:01 Gabriella439