I donβt think you can write a function with a signature:
changeReaderT :: (MonadTrans m) => (r -> r') -> m (ReaderT r IO) a -> m (ReaderT r' IO) a
the problem is that the only possible operation, in the general case, according to the second argument, raises it by t (m (ReaderT r IO)) a for some monadic transformer t that does not buy anything.
That is, the MonadTrans m restriction alone does not provide enough structure to do what you want. You may need m to be an instance of a class of type MFunctor in the mmorph package, which allows you to change the internal level of the monad stack in general, providing a function such as:
hoist :: Monad m => (forall a. ma -> na) -> tmb -> tnb
(this is what @Juan Pablo Santos was talking about), or you need the ability to delve into the structure of your monad transformer m to partially start and rebuild it (which will be connected with the transformer).
The first approach (using the hoist from the hoist package) will be most convenient if your m already consists of transformers supported by the mmorph package. For example, the following typechecks, and you do not need to write any instances:
type M n = MaybeT (StateT String n) action :: M (ReaderT Double IO) a action = undefined f :: Int -> Double f = fromIntegral desired :: M (ReaderT Int IO) a desired = (hoist $ hoist $ withReaderT fromIntegral) action
For each layer in m you will need a hoist .
The second approach avoids hoist and require instances of MFunctor , but requires adaptation to your specific m . For the above type, it looks something like this:
desired' :: M (ReaderT Int IO) a desired' = MaybeT $ StateT $ \s -> (withReaderT fromIntegral . flip runStateT s . runMaybeT) action
You basically need to run monad to the ReaderT level, and then restore it back by carefully processing layers like StateT . This is exactly what MFunctor instances in mmorph automatically.