[英]Lift a function and its argument to a different monadic context
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. 我在
StateT
变换器中使用状态。 Underlying is IO
. 基础是
IO
。 Inside the StateT IO
operation I need to use alloca
. 在
StateT IO
操作中,我需要使用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)
. 但是,我无法将
alloca
提升到StateT IO
因为它需要一个类型的参数(Ptr a -> IO a)
而我要求它使用参数(Ptr a -> StateT IO MyState a)
。
(However, this is a generic question about monad transformers rather than specific to IO
, StateT
or alloca
.) (但是,这是关于monad变换器的一般性问题,而不是
IO
, StateT
或alloca
StateT
。)
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
. 但是,我似乎不应该为了将它与
alloca
一起使用而StateT
并重建StateT
动作。 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
. 此外,我在一些变体中不止一次地看到过这种模式,并且它并不像
StateT
那样简单和安全。
Is there a better way to do this? 有一个更好的方法吗?
This can be accomplished using MonadBaseControl
in monad-control , which has been devised exactly for this purpose: 这可以使用monad-control中的
MonadBaseControl
来完成,这是为此目的而设计的:
{-# 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 enhanced version of alloca
can be used with any monad stack based on IO
that implements MonadBaseControl
, including StateT s IO
. 这个增强版本的
alloca
可以与基于实现MonadBaseControl
IO
任何monad堆栈一起使用,包括StateT s IO
。
Instances of MonadBaseControl
allow their monadic values to be encoded in the base monad (here IO
), passed to a function in the base monad (like F.alloca
) and then reconstruct them back. MonadBaseControl
实例允许它们的MonadBaseControl
值在基本monad(这里是IO
)中编码,传递给基本monad中的函数(如F.alloca
),然后重新F.alloca
它们。
See also What is MonadBaseControl for? 另请参阅什么是MonadBaseControl?
Package lifted-base contains many of the standard IO
functions lifted to MonadBaseControl IO
, but alloca
isn't (yet) among them. Package lifted-base包含许多提升到
MonadBaseControl IO
的标准IO
函数,但alloca
还没有(还)。
Good afternoon, 下午好,
AFAIK, there is no general way to turn a function of type (a -> mb) -> mb
into (a -> tmb) -> tmb
because that would imply the existence of a function of type MonadTrans t => (a -> tmb) -> (a -> mb)
. AFAIK,没有通用的方法将类型
(a -> mb) -> mb
的函数转换为(a -> tmb) -> tmb
因为这意味着存在MonadTrans t => (a -> tmb) -> (a -> mb)
类型的MonadTrans t => (a -> tmb) -> (a -> mb)
。
Such a function cannot possibly exist, since most transformers cannot be stripped so easily from a type signature (how do you turn a MaybeT ma
into an ma
for all a
?). 这样的功能,不可能存在,因为大多数变压器不能如此轻易地从一个类型签名剥离(你怎么把一个
MaybeT ma
为ma
所有a
?)。 Hence, the most general way to turn (a -> mb) -> mb
to (a -> tmb) -> tmb
is undefined
. 因此,将
(a -> mb) -> mb
为(a -> tmb) -> tmb
的最常用方法是undefined
。
In the case of StateT sm
, there is a loophole that allows you to define it anyway. 在
StateT sm
的情况下,有一个漏洞允许您无论如何定义它。 Since StateT sma === s -> m (s,a)
, we can rewrite the type equation to : 由于
StateT sma === 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)
Solving 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)
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. 这是我们在代码重用方面可以做的最好的事情,而不是为所有表现出相同行为的monad定义MonadTrans的新子类。
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 :-) 祝你有个美好的一天:-)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.