下面是一个场景:给定是一个 C 库,其核心具有一些结构,并由大量 C 函数提供其操作。
第 1 步:使用 Haskell 的 FFI 创建一个包装器。它具有myCLibInit :: IO MyCLibObj
、myCLibOp1 :: MyCLibObj -> ... -> IO ()
等功能。MyCLibObj
是一种不透明的类型,它携带(和隐藏(实际 C 结构的Ptr
或ForeignPtr
,例如本 wiki 或 RWH 第 17 章中所示。
步骤2:使用Control.Monad.ST.Unsafe
unsafeIOToST
将所有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 步:通常,在一个 do-block 中混合多个MyCLibObj
的操作是没有意义的。例如,当 C 结构是(或应该被认为是(单例实例时。做上面doSomething
这样的事情要么是荒谬的,要么是完全禁止的(例如,当 C 结构是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
将使用新monad的runST
函数,因此我们将其称为Q
(对于"q'uestion"(,应该是
newtype Q a = Q (forall s. ST s a)
这对我来说看起来很奇怪。我已经在努力为这个Q
实现Functor
实例,更不用说Applicative
和Monad
了。ST s
实际上已经是一个monad,但是状态s
不能逃脱ST
monad,因此forall s. ST s a
。这是摆脱s
的唯一方法,因为runST :: (forall s. ST s a) -> a
,而withMyCLibInstance
只是一个myCLibInit'
后跟一个runST
。但不知何故,这不适合。
解决第 3 步的正确方法是什么?我应该执行第 2 步,还是在第 1 步之后立即滚动Q
?我的感觉是,这应该很简单。ST
monad 拥有我需要的一切,Q
只需要以正确的方式设置......
更新 1:步骤 3 中的单例和静态结构示例不是很好。如果并行执行两个这样的 do 块,可能会发生非常糟糕的事情,即两个 do 块将并行处理相同的 C 结构。
您可以使用读取器效果来访问单例,仅在run
函数中实例化它:
newtype MyCLibST s a = MyCLibST { unMyCLibST :: ReaderT (STMyCLib s) (ST s) a }
runMyCLibST :: (forall s. MyCLibST s a) -> a
runMyCLibST m = runST (myCLibInit >>= runReaderT (unMyCLibST m))
-- Wrap the API with this.
unsafeMkMyCLibST :: (MyCLibObj -> IO a) -> MyCLibST s a
s
应显示为参数,MyCLibST
如果您想保留对其他ST
功能(如可变引用和数组(的访问权限。