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?

+3


source to share


2 answers


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.

+4


source


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: -)

+1


source







All Articles