场景如下:Given 是一个 C 库,其核心是一些结构,其上的操作由大量 C 函数提供。
第 1 步:使用 Haskell 的FFI创建一个包装器。它具有myCLibInit :: IO MyCLibObj
、myCLibOp1 :: MyCLibObj -> ... -> IO ()
等功能。MyCLibObj
是一个不透明类型,它携带(并隐藏) aPtr
或ForeignPtr
实际的 C 结构,例如在本wiki或RWH ch. 17 .
第 2 步:使用unsafeIOToST
fromControl.Monad.ST.Unsafe
将所有IO
动作转换为ST
动作。这是通过引入类似的东西来完成的
data STMyCLib s = STMyCLib MyCLibObj
然后将所有IO
函数包装在ST
函数中,例如:
myCLibInit' :: ST s (STMyCLib s)
myCLibInit' = unsafeIOToST $ STMyCLib <$> myCLibInit
这允许编写反映使用类似 OO 的 C 库的命令式程序,例如:
doSomething :: ST s Bool
doSomething = do
obj1 <- myCLibInit'
success1 <- myCLibOp1' obj1 "some-other-input"
...
obj2 <- myCLibInit'
result <- myCLibOp2' obj2 42
...
return True -- or False
main :: IO ()
main = do
...
let success = runST doSomething
...
第 3 步:MyCLibObj
通常将多个操作混合在一个 do-block上是没有意义的。例如,当 C 结构是(或应该被认为是)单例实例时。做类似doSomething
上面的事情要么是荒谬的,要么是完全禁止的(例如,当 C 结构是 a 时static
)。在这种情况下,类似于State
monad 的语言是必要的:
doSomething :: ResultType
doSomething = withMyCLibInstance $ do
success <- myCLibOp1'' "some-other-input"
result <- myCLibOp2'' 42
...
return result
在哪里
withMyCLibInstance :: Q a -> a
这就引出了一个问题:如何将ST s a
monad 重新打扮成更像State
monad 的东西。由于withMyCLibInstance
将使用runST
新的 monad 函数,我们称之为Q
(对于 'q'uestion),应该是
newtype Q a = Q (forall s. ST s a)
这对我来说看起来很奇怪。我已经在Functor
为此实现实例而苦苦挣扎Q
,更不用说Applicative
和Monad
. ST s
实际上已经是一个 monad,但是 states
不能逃脱ST
monad,因此forall s. ST s a
. 这是摆脱的唯一方法,s
因为runST :: (forall s. ST s a) -> a
,并且withMyCLibInstance
只是 amyCLibInit'
后跟 a runST
。但不知何故,这不合适。
处理第 3 步的正确方法是什么?我应该做第 2 步,还是Q
在第 1 步之后滚动我的权利?我的感觉是这应该很简单。ST
单子有我需要的一切,只是Q
需要以正确的方式设置......
更新 1:步骤 3 中的单例和静态结构示例不是很好。如果两个这样的 do 块并行执行,可能会发生非常糟糕的事情,即两个 do 块将并行处理同一个 C 结构。