I looked hard to see if this may be a duplicate question but couldn't find anything that addressed specifically this. My apologies if there actually is something.
So, I get how lift
works, it lifts a monadic action (fully defined) from the outer-most transformer into the transformed monad. Cool.
But what if I want to apply a (>>=)
from one level under the transformer into the transformer? I'll explain with an example.
Say MyTrans
is a MonadTrans
, and there is also an instance Monad m => Monad (MyTrans m)
. Now, the (>>=)
from this instance will have this signature:
instance Monad m => Monad (MyTrans m) where
(>>=) :: MyTrans m a -> (a -> MyTrans m b) -> MyTrans m b
but what I need is something like this:
(>>=!) :: Monad m => MyTrans m a -> (m a -> MyTrans m b) -> MyTrans m b
In general:
(>>=!) :: (MonadTrans t, Monad m) => t m a -> (m a -> t m b) -> t m b
It looks like a combination of the original (>>=)
and lift
, except it really isn't. lift
can only be used on covariant arguments of type m a
to transform them into a t m a
, not the other way around. In other words, the following has the wrong type:
(>>=!?) :: Monad m => MyTrans m a -> (a -> m b) -> MyTrans m b
x >>=!? f = x >>= (lift . f)
Of course a general colift :: (MonadTrans t, Monad m) => t m a -> m a
makes absolutely zero sense, because surely the transformer is doing something that we cannot just throw away like that in all cases.
But just like (>>=)
introduces contravariant arguments into the monad by ensuring that they will always "come back", I thought something along the lines of the (>>=!)
function would make sense: Yes, it in some way makes an m a
from a t m a
, but only because it does all of this within t
, just like (>>=)
makes an a
from an m a
in some way.
I've thought about it and I don't think (>>=!)
can be in general defined from the available tools. In some sense it is more than what MonadTrans
gives. I haven't found any related type classes that offer this either. MFunctor
is related but it is a different thing, for changing the inner monad, but not for chaining exclusively transformer-related actions.
By the way, here is an example of why you would want to do this:
EDIT: I tried to present a simple example but I realized that that one could be solved with the regular (>>=)
from the transformer. My real example (I think) cannot be solved with this. If you think every case can be solved with the usual (>>=)
, please do explain how.
Should I just define my own type class for this and give some basic implementations? (I'm interested in StateT
, and I'm almost certain it can be implemented for it) Am I doing something in a twisted way? Is there something I overlooked?
Thanks.
EDIT: The answer provided by Fyodor matches the types, but does not do what I want, since by using pure
, it is ignoring the monadic effects of the m
monad. Here is an example of it giving the wrong answer:
Take t
= StateT Int
and m
= []
.
x1 :: StateT Int [] Int
x1 = StateT (\s -> [(1,s),(2,s),(3,s)])
x2 :: StateT Int [] Int
x2 = StateT (\s -> [(1,s),(2,s),(3,s),(4,s))])
f :: [Int] -> StateT Int [] Int
f l = StateT (\s -> if (even s) then [] else (if (even (length l)) then (fmap (\z -> (z,z+s)) l) else [(123,123)]))
runStateT (x1 >>= (\a -> f (pure a))) 1
returns [(123,123),(123,123),(123,123)]
as expected, since both 1
is odd and the list in x1
has odd length.
But runStateT (x2 >>= (\a -> f (pure a))) 1
returns [(123,123),(123,123),(123,123),(123,123)]
, whereas I would have expected it to return [(1,2),(2,3),(3,4),(4,5)]
, since the 1
is odd and the length of the list is even. Instead, the evaluation of f
is happening on the lists [(1,1)]
, [(2,1)]
, [(3,1)]
and [(4,1)]
independently, due to the pure
call.