I don't think it's possible to write a function with signature:
changeReaderT :: (MonadTrans m)
=> (r -> r')
-> m (ReaderT r IO) a
-> m (ReaderT r' IO) a
the issue being that the only operation possible, in general, on the second argument is lifting it to t (m (ReaderT r IO)) a
for some monad transformer t
, which doesn't buy you anything.
That is, the MonadTrans m
constraint alone doesn't provide enough structure to do what you want. You either need m
to be an instance of a typeclass like MFunctor
in the mmorph
package that allows you to modify an inner layer of the monad stack in a general way by providing a function like:
hoist :: Monad m => (forall a. m a -> n a) -> t m b -> t n b
(which is what @Juan Pablo Santos was saying), or else you need an ability to dig into the structure of your m
monad transformer to partially run and rebuild it (which will be transformer-specific).
The first approach (using hoist
from the mmorph
package) will be most convenient if your m
is already made up of transformers supported by the mmorph
package. For example, the following typechecks, and you don't have 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
You'll need a hoist
for each layer in M
.
The second approach avoids hoist
and requisite MFunctor
instances but requires tailoring to your specific M
. For the above type , it looks something like:
desired' :: M (ReaderT Int IO) a
desired' = MaybeT $ StateT $ \s ->
(withReaderT fromIntegral . flip runStateT s . runMaybeT) action
You basically need to run the monad down to the ReaderT
layer and then rebuild it back up, treating layers like StateT
with care. This is exactly what the MFunctor
instances in mmorph
are doing automatically.