这是一个场景:给定的是一个C库,其核心是一些结构,其中的操作由丰富的C函数提供 .
Step 1: 使用Haskell的FFI创建了一个包装器 . 它具有 myCLibInit :: IO MyCLibObj
, myCLibOp1 :: MyCLibObj -> ... -> IO ()
等功能 . MyCLibObj
是一个opaque类型,它将 Ptr
或 ForeignPtr
携带(并隐藏)到实际的C结构中,例如wiki或RWH ch. 17中所示 .
Step 2: 使用 unsafeIOToST
从Control.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
...
Step 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
这导致 question :如何将 ST s a
monad重新打扮成类似于 State
monad的东西 . 由于 withMyCLibInstance
将使用 runST
函数新的monad,我们称之为 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步的正确方法是什么?我应该在第1步之后立即执行第2步,还是直接滚动_219061?我的感觉是,这应该很简单 . 我只需要 ST
monad, Q
只需要以正确的方式设置......
Update 1: 步骤3中的单例和静态结构示例不是很好 . 如果并行执行两个这样的do块,则可能发生非常糟糕的事情,即两个块都可以并行地在同一个C结构上工作 .
1 回答
您可以使用reader效果来访问单例,只在
run
函数中实例化它:如果要保持对可变引用和数组等其他
ST
功能的访问,s
应作为MyCLibST
的参数出现 .