将 ST monad 重新打扮成类似于 State monad 的东西
Re-dress a ST monad as something similar to the State monad
场景如下:给定一个 C 库,其核心是一些结构,并由丰富的 C 函数提供对其的操作。
步骤 1: 使用 Haskell 的 FFI a wrapper is created. It has functions like myCLibInit :: IO MyCLibObj
, myCLibOp1 :: MyCLibObj -> ... -> IO ()
, and so on. MyCLibObj
is an opaque type that carries (and hides) a Ptr
or ForeignPtr
to the actual C struct, for example as shown in this wiki or in RWH ch. 17。
第 2 步: 使用 Control.Monad.ST.Unsafe
convert all the IO
actions into ST
操作中的 unsafeIOToST
。这是通过引入类似
的东西来完成的
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 块中混合对多个 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
将使用 runST
函数新的 monad,我们称它为 Q
(对于“问题”),应该是
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 结构。
您可以使用 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
s
应该作为 MyCLibST
的参数出现,如果您想继续访问其他 ST
功能,如可变引用和数组。
场景如下:给定一个 C 库,其核心是一些结构,并由丰富的 C 函数提供对其的操作。
步骤 1: 使用 Haskell 的 FFI a wrapper is created. It has functions like myCLibInit :: IO MyCLibObj
, myCLibOp1 :: MyCLibObj -> ... -> IO ()
, and so on. MyCLibObj
is an opaque type that carries (and hides) a Ptr
or ForeignPtr
to the actual C struct, for example as shown in this wiki or in RWH ch. 17。
第 2 步: 使用 Control.Monad.ST.Unsafe
convert all the IO
actions into ST
操作中的 unsafeIOToST
。这是通过引入类似
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 块中混合对多个 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
将使用 runST
函数新的 monad,我们称它为 Q
(对于“问题”),应该是
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 结构。
您可以使用 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
s
应该作为 MyCLibST
的参数出现,如果您想继续访问其他 ST
功能,如可变引用和数组。