Haskell-MMorph-Library
Haskell-MMorph-Library copied to clipboard
Add MCoyoneda
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
?
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