Reputation: 27766
I have this ReaderT
-like monad transformer (inspired by this answer):
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE StandaloneKindSignatures #-}
import Control.Monad.Reader -- from "mtl"
import Data.Kind (Type)
type DepT :: ((Type -> Type) -> Type) -> (Type -> Type) -> Type -> Type
newtype DepT env m r = DepT {toReaderT :: ReaderT (env (DepT env m)) m r}
deriving (Functor, Applicative, Monad, MonadReader (env (DepT env m)))
instance MonadTrans (DepT env) where
lift = DepT . lift
And these two parameterized records, to which I give "rank-2 functor" instances:
{-# LANGUAGE TemplateHaskell #-}
import qualified Rank2 -- form rank2classes
import qualified Rank2.TH
type Env :: (Type -> Type) -> Type
data Env m = Env
{ logger :: String -> m (),
logic :: Int -> m Int
}
$(Rank2.TH.deriveFunctor ''Env)
type BiggerEnv :: (Type -> Type) -> Type
data BiggerEnv m = BiggerEnv
{ inner :: Env m,
extra :: Int -> m Int
}
$(Rank2.TH.deriveFunctor ''BiggerEnv)
Intuitively, I expect to be able to write a conversion function with the type:
zoom :: forall a. DepT Env IO a -> DepT BiggerEnv IO a
This is because DepT Env IO a
works with "less info" than DepT BiggerEnv IO a
.
But I'm stuck. Is there a way to write zoom
?
Upvotes: 0
Views: 168
Reputation: 27766
The general solution would be a function like:
withDepT ::
forall small big m a.
Monad m =>
( forall p q.
(forall x. p x -> q x) ->
small p ->
small q
) ->
(forall t. big t -> small t) ->
DepT small m a ->
DepT big m a
withDepT mapEnv inner (DepT (ReaderT f)) =
DepT
( ReaderT
( \big ->
let small :: small (DepT small m)
-- we have a big environment at hand, so let's extract the
-- small environment, transform every function in the small
-- environment by supplying the big environment and, as a
-- finishing touch, lift from the base monad m so that it
-- matches the monad expected by f.
small = mapEnv (lift . flip runDepT big) (inner big)
in f small
)
)
Where the first argument would be, in the case of Env
, a function like
mapEnv :: (forall x. n x -> m x) -> Env n -> Env m
mapEnv f (Env {logger,logic}) =
Env { logger = f . logger, logic = f . logic }
which changes the monad of the environment. mapEnv
corresponds to Rank2.<$>
from rank2classes.
Upvotes: 1
Reputation: 33496
First, we could create a more general function, withDepT
, which is similar to the withReaderT
function.
withDepT :: forall env env' m a.
(env' (DepT env' m) -> env (DepT env m))
-> DepT env m a
-> DepT env' m a
withDepT f (DepT m) = DepT (withReaderT f m)
And then we can use this to implement zoom
by providing a function like:
biggerEnvToEnv :: BiggerEnv (DepT BiggerEnv IO) -> Env (DepT Env IO)
biggerEnvToEnv (BiggerEnv (Env logger logic) _) = Env logger' logic'
where
logger' = mystery . logger
logic' = mystery . logic
zoom = withDepT biggerEnvToEnv
But then we need to implement mystery
. Let's look at its type:
mystery :: forall a. DepT BiggerEnv IO a -> DepT Env IO a
Now we can see that mystery
is the opposite of our desired zoom
function:
zoom :: forall a. DepT Env IO a -> DepT BiggerEnv IO a
So we can conclude that it's impossible to naturally derive zoom
unless BiggerEnv
and Env
are isomorphic, which they're not because of the extra
value in BiggerEnv
.
Upvotes: 2