将函数及其参数提升到不同的monadic上下文

时间:2014-08-03 07:30:27

标签: haskell monads

我不确定如何科学地准确地提出这个问题,所以我只想给你举个例子。

我在StateT变换器中使用状态。基础是IO。在StateT IO操作中,我需要使用alloca。但是,我无法将alloca提升为StateT IO,因为它需要(Ptr a -> IO a)类型的参数,而我需要使用(Ptr a -> StateT IO MyState a)的参数。

(但是,这是关于monad变换器的一般性问题,而不是特定于IOStateTalloca。)

我提出了以下工作解决方案:

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

但是,我似乎不应该去除StateT动作并重新构建alloca动作,以便与StateT一起使用。此外,我在一些变体中不止一次地看到过这种模式,并不像{{1}}那样简单安全。

有更好的方法吗?

2 个答案:

答案 0 :(得分:4)

这可以使用MonadBaseControl中的monad-control来完成,What is MonadBaseControl for?完全是出于此目的而设计的:

{-# 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)

alloca的此增强版可以与基于实现IO的{​​{1}}的任何monad堆栈一起使用,包括MonadBaseControl

StateT s IO实例允许将monadic值编码在基本monad(此处为MonadBaseControl)中,传递给基本monad中的函数(如IO),然后重构它们回来。

另见lifted-base

包{{3}}包含许多提升到F.alloca的标准IO功能,但MonadBaseControl IO尚未包含在其中。

答案 1 :(得分:1)

下午好,

AFAIK,没有将(a -> m b) -> m b类型的函数转换为(a -> t m b) -> t m b的一般方法,因为这意味着存在类型为MonadTrans t => (a -> t m b) -> (a -> m b)的函数。

这样的功能不可能存在,因为大多数变形金刚都不能从类型签名中轻易剥离(如何将MaybeT m a变为m a所有a?)。因此,将(a -> m b) -> m b变为(a -> t m b) -> t m b的最常用方法是undefined

StateT s m的情况下,有一个漏洞允许您无论如何定义它。自StateT s m a === s -> m (s,a)起,我们可以将类型等式重写为:

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

现在解决这个新类型的签名是微不足道的:

liftedState f s run = f (run s)
allocaS :: Storable a => (Ptr a -> StateT IO b) -> StateT IO b
allocaS = isomorphic (liftedState alloca)

这就是我们在代码重用方面所做的最好的事情,而不是为所有表现出相同行为的monad定义MonadTrans的新子类。

我希望自己足够清楚(因为害怕混淆,我不想详细说明)

度过美好的一天: - )