Yes. Combine hoist
from the mmorph
package with lift
to do this:
bound
:: (MonadTrans t, MonadTrans s, MFunctor t, Monad m)
=> t m () -> s m () -> t (s m) ()
bound master slave = do
hoist lift master
lift slave
To understand why this works, study the type of hoist
:
hoist :: (MFunctor t) => (forall x . m x -> n x) -> t m r -> t n r
hoist
lets you modify the base monad of any monad transformer that implements MFunctor
(which is most of them).
What the code for bound
does is have the two monad transformers agree on a final target monad, which in this case is t (s m)
. The order in which you nest t
and s
is up to you, so I just assumed that you wanted t
on the outside.
Then it's just a matter of using various combinations of hoist
and lift
to get the two sub-computations to agree on the final monad stack. The first one works like this:
master :: t m r
hoist lift master :: t (s m) r
The second one works like this:
slave :: s m r
lift slave :: t (s m) r
Now they both agree so we can sequence them within the same do
block and it will "just work".
To learn more about how hoist
works, I recommend you check the documentation for the mmorph
package which has a nice tutorial at the bottom.