Raise a function and its argument into another monadic context
I'm not sure how to formulate this question scientifically, so I'll just show you an example.
I am using a state in a transformer StateT
. It is based on IO
. Inside the operation, StateT IO
I need to use alloca
. However, I cannot raise alloca
on StateT IO
because it expects a type argument (Ptr a -> IO a)
while I require it to work on an argument (Ptr a -> StateT IO MyState a)
.
(However, this is a general question about monad transformers, not 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 would need to delete and restore an activity StateT
in order to use it with alloca
. Also, I have seen this pattern in some variants several times and it is not always as easy and safe as it is here with StateT
.
Is there a better way to do this?
source to share
This can be done using MonadBaseControl
in monad-control , which was developed specifically for this purpose:
{-# LANGUAGE FlexibleContexts #-}
import Control.Monad
import Control.Monad.Trans.Control
import qualified Foreign.Ptr as F
import qualified Foreign.Marshal.Alloc as F
import qualified Foreign.Storable as F
alloca :: (MonadBaseControl IO m, F.Storable a) => (F.Ptr a -> m b) -> m b
alloca f = control $ \runInIO -> F.alloca (runInIO . f)
This extended version alloca
can be used with any based stack IO
that implements MonadBaseControl
, including StateT s IO
.
Instances MonadBaseControl
allow their monadic values ββto be encoded in the base monad (here IO
), passed to functions in the base monad (for example F.alloca
), and then restored back.
See also What is MonadBaseControl for?
The lifted-base package contains many of the standard features IO
raised to MonadBaseControl IO
, but is alloca
not (yet) among them.
source to share
Good day,
AFAIK there is no general way to turn a function of a type (a -> m b) -> m b
into (a -> t m b) -> t m b
, as that would mean the existence of a function of a type MonadTrans t => (a -> t m b) -> (a -> m b)
.
Such a function cannot exist as most transformers cannot be easily removed from the type signature (how do you turn MaybeT m a
to m a
for everyone a
?). Therefore, the most common way to convert (a -> m b) -> m b
to (a -> t m b) -> t m b
is undefined
.
There StateT s m
is a loophole in the case that allows you to define it anyway. Since StateT s m a === s -> m (s,a)
, we can rewrite an equation like:
(a -> StateT s m b) -> StateT s m b
=== (a -> s -> m (s,b)) -> s -> m (s,b)
=== s -> (s -> (a -> m (s,b)) -> m (s,b) -- we reorder curried arguments
=== s -> (s -> (A -> m B)) -> m B -- where A = a, B = (s,b)
The solution to this new type signature is now trivial:
liftedState f s run = f (run s)
allocaS :: Storable a => (Ptr a -> StateT IO b) -> StateT IO b
allocaS = isomorphic (liftedState alloca)
This is the best we can do in terms of code reuse, rather than defining a new subclass of MonadTrans for all monads that exhibit the same behavior.
I hope I made myself clear enough (I didn't want to go into details for fear of confusion)
Excellent day: -)
source to share