将ST monad重新装扮成类似于州Monad的东西

mcm*_*yer 4 haskell ffi state-monad st-monad

这是一个场景:给定的是一个C库,其核心是一些结构,其中的操作由丰富的C函数提供.

第1步:使用Haskell的FFI创建一个包装器.它具有像myCLibInit :: IO MyCLibObj,myCLibOp1 :: MyCLibObj -> ... -> IO ()等等功能.MyCLibObj是一个opaque类型,它携带(和隐藏)一个PtrForeignPtr实际的C结构,例如,如本维基RWH ch中所示.17.

第2步:使用unsafeIOToSTControl.Monad.ST.Unsafe将所有IO操作转换为ST操作.这是通过引入类似的东西来完成的

 data STMyCLib s = STMyCLib MyCLibObj
Run Code Online (Sandbox Code Playgroud)

然后将所有IO函数包装在ST函数中,例如:

myCLibInit' :: ST s (STMyCLib s)
myCLibInit' = unsafeIOToST $ STMyCLib <$> myCLibInit
Run Code Online (Sandbox Code Playgroud)

这允许编写命令式程序,这些程序反映了类似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
    ...
Run Code Online (Sandbox Code Playgroud)

第3步:通常将操作混合MyCLibObj在一个do-block中是没有意义的.例如,当C结构是(或应该被认为是)单例实例时.做doSomething上面这样的事情要么是荒谬的,要么是纯粹的禁止(例如,当C结构是a时static).在这种情况下,类似于Statemonad的语言是必要的:

doSomething :: ResultType
doSomething =  withMyCLibInstance $ do
    success <- myCLibOp1'' "some-other-input"
    result <- myCLibOp2'' 42
    ...
    return result
Run Code Online (Sandbox Code Playgroud)

哪里

withMyCLibInstance :: Q a -> a
Run Code Online (Sandbox Code Playgroud)

这导致了一个问题:如何将ST s amonad重新打扮成类似Statemonad的东西.既然withMyCLibInstance会使用runST新monad 的功能,那就叫它Q(对于'q'uestion),应该是

newtype Q a = Q (forall s. ST s a)
Run Code Online (Sandbox Code Playgroud)

这看起来很奇怪.我已经与实施挣扎的Functor实例,该Q,更别说ApplicativeMonad.ST s实际上已经是一个单子,但是国家s不能逃避ST单子,因此forall s. ST s a.这是摆脱的唯一途径s,因为runST :: (forall s. ST s a) -> a,并且withMyCLibInstance仅仅是一个myCLibInit'由跟随runST.但不知怎的,这不合适.

解决第3步的正确方法是什么?我应该Q在第1步之后做第2步,还是向右转?我的感觉是,这应该很简单.该ST单子拥有所有我需要的Q只是需要建立正确的方式?

更新1:步骤3中的单例和静态结构示例不是很好.如果两个这样的do块并行执行,则可能会发生非常糟糕的事情,即两个块都可以并行处理同一个C结构.

Li-*_*Xia 5

您可以使用reader效果来访问单例,只在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
Run Code Online (Sandbox Code Playgroud)

sMyCLibST如果要保持对ST可变引用和数组等其他功能的访问,则应该作为参数出现.