I am not sure how to formulate this question scientifically exact, so I am just going to show you an example.
I am using state in a StateT
transformer. Underlying is IO
. Inside the StateT IO
operation I need to use alloca
. However, I can't lift alloca
to StateT IO
because it expects an argument of type (Ptr a -> IO a)
while I require it to work with an argument of (Ptr a -> StateT IO MyState a)
.
(However, this is a generic question about monad transformers rather than specific to IO
, StateT
or alloca
.)
I came up with the following, working solution:
-- for reference
-- alloca :: (Storable a) => (Ptr a -> IO b) -> IO b
allocaS :: (Storable a) => (Ptr a -> StateT s IO b) -> StateT s IO b
allocaS f = do
state <- get
(res, st) <- liftIO $ alloca $ \ptr -> (runStateT (f ptr) state)
put st
return res
However, it seems wrong to me that I should have to de- and reconstruct the StateT
action in order to use it with alloca
. Also, I have seen this pattern in some variations more than once and it's not always as simple and safe as here with StateT
.
Is there a better way to do this?
Good afternoon,
AFAIK, there is no general way to turn a function of type
(a -> m b) -> m b
into(a -> t m b) -> t m b
because that would imply the existence of a function of typeMonadTrans t => (a -> t m b) -> (a -> m b)
.Such a function cannot possibly exist, since most transformers cannot be stripped so easily from a type signature (how do you turn a
MaybeT m a
into anm a
for alla
?). Hence, the most general way to turn(a -> m b) -> m b
to(a -> t m b) -> t m b
isundefined
.In the case of
StateT s m
, there is a loophole that allows you to define it anyway. SinceStateT s m a === s -> m (s,a)
, we can rewrite the type equation to :Solving this new type signature is now trivial :
That is about the best we can do in terms of code reuse, short of defining a new subclass of MonadTrans for all monads that exhibit the same behaviour.
I hope I made myself clear enough (I didn't want to go into too much detail for fear of being confusing)
Have an excellent day :-)
This can be accomplished using
MonadBaseControl
in monad-control, which has been devised exactly for this purpose:This enhanced version of
alloca
can be used with any monad stack based onIO
that implementsMonadBaseControl
, includingStateT s IO
.Instances of
MonadBaseControl
allow their monadic values to be encoded in the base monad (hereIO
), passed to a function in the base monad (likeF.alloca
) and then reconstruct them back.See also What is MonadBaseControl for?
Package lifted-base contains many of the standard
IO
functions lifted toMonadBaseControl IO
, butalloca
isn't (yet) among them.