Reputation: 4895
Let's say I have function that composes two monad actions:
co :: Monad m => m a -> m a -> m a
You can think of co
as a higher order function that describes how two monadic actions may cooperate with each other to complete a task.
But now I find that the first monadic action may be wrapped inside a monad transformer, while the second one is not:
one :: (MonadTrans t, Monad m) => t m a
two :: Monad m => m a
But would like to compose them together still, so I need a function:
co' :: (MonadTrans t, Monad m) => t m a -> m a -> t m a
So that the first t m a
can cooperate with m a
by just lifting all m
primitives into the context t
.
The trick here is to build co
without really knowing the implementation of m
or t
. I feel like the answer is somewhere in the MFunctor package, and in fact asked a similar question yesterday. But cannot come up with anything good, any thoughts?
Upvotes: 2
Views: 214
Reputation: 35099
You can do this using hoist
from the mmorph
package. hoist
lets you modify the base monad of anything that implements MFunctor
(which is most monad transformers):
hoist :: (MFunctor t) => (forall x . m x -> n x) -> t m r -> t n r
You can then use it for your problem like this:
co' tma ma = hoist (co ma) tma
Then you're done!
To understand why that works, let's go through the types step by step:
co :: (Monad m) => m a -> m a -> m a
ma :: (Monad m) => m a
co ma :: (Monad m) => m a -> m a
hoist (co ma) :: (Monad m, MFunctor t) => t m a -> t m a
tma :: (Monad m, MFunctor t) => t m a
hoist (co ma) tma :: (Monad m, MFunctor t) => t m a
Note that hoist
has certain laws it must satisfy that ensure that it does "the right thing" that you would expect:
hoist id = id
hoist (f . g) = hoist f . hoist g
These are just functor laws, guaranteeing that hoist
behaves intuitively.
hoist
is provided in the Control.Monad.Morph
module of the mmorph
package, which you can find here. The bottom of the main module has a tutorial teaching how to use the package
Upvotes: 5
Reputation: 11218
You need t m
to have a monad instance for this to work.
import Control.Monad
import Control.Monad.Trans
co :: Monad m => m a -> m a -> m a
co = undefined
co' :: (MonadTrans t, Monad m, Monad (t m)) => t m a -> m a -> t m a
co' one two = lift . flip co two . return =<< one
Seeing the definition of lift
from transformers
package should help you find the answer as you want something similar
lift . return = return
lift (m >>= f) = lift m >>= (lift . f)
As you have flip co one :: Monad m => m a -> m a
and you want to lift it to get a function of type (MonadTrans t, Monad m, Monad (t m)) => t m a -> t m a
. So following on the footsteps of lift
lift' :: (MonadTrans t, Monad m, Monad (t m)) => (m a -> m a) -> t m a -> t m a
lift' f tma = tma >>= (lift . f . return)
Now defining co'
is trivial
co' one two = lift' (flip co two) one
The above solutions just satisfy the type, but do they satisfy the semantics too?
To see the problem lets take a co
function which always returns the second action without even looking at the first. Now the above co'
will never be able to do that as it always runs the first action before deciding anything. So the side effect of first action will still occur in the co'
even though they dont occur in co
.
I suppose not. Because what you want to actually implement that function generically, is a function like t m a -> (m a -> m b) -> t m b
, where you need to get action m a
out of t m a
without actually running the side effects of m a
.
So suppose m
is IO
monad and t
is State transformer with some state and your one
action actually launches a missile and depending on the success or failure of that modifies the State
. Now what you want is to actually modify the state without launching the missiles. In general that is not possible.
If you know some information about co
like which action is run first, then you can implement co'
using the above method.
Upvotes: 2
Reputation: 27766
The monad-control package can be used to lift functions and control operations from a base monad into a monad transformer, even those functions that take callbacks.
The lifted-base package builds on monad-control and contains lifted versions of many common functions dealing with exceptions, concurrency... In particular, it has a lifted version of finally from Control.Exception, whose signature is very similar to that of co
.
Here are some links:
http://www.yesodweb.com/book/monad-control
http://www.yesodweb.com/blog/2013/08/exceptions-and-monad-transformers
Upvotes: 0