【问题标题】:Re-dress a ST monad as something similar to the State monad将 ST monad 重新打扮成类似于 State monad 的东西
【发布时间】:2018-06-03 05:43:36
【问题描述】:

以下是场景:Given 是一个 C 库,其核心是一些结构,其上的操作由大量 C 函数提供。

第 1 步:使用 Haskell 的 FFI 创建一个包装器。它具有myCLibInit :: IO MyCLibObjmyCLibOp1 :: MyCLibObj -> ... -> IO ()等功能。 MyCLibObj 是一种不透明类型,它将PtrForeignPtr 携带(并隐藏)到实际的C 结构中,例如在wikiRWH ch. 17 中所示。

第 2 步: 使用 unsafeIOToST from 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
    ...

第 3 步: 通常将多个 MyCLibObj 的操作混合在一个 do-block 中是没有意义的。例如,当 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(用于'q'uestion),应该是

newtype Q a = Q (forall s. ST s a)

这对我来说看起来很奇怪。我已经在为这个Q 实现Functor 实例而苦苦挣扎,更不用说ApplicativeMonadST s 实际上已经是一个 monad,但是状态 s 不能逃脱 ST monad,因此是 forall s. ST s a。这是摆脱s 的唯一方法,因为runST :: (forall s. ST s a) -&gt; awithMyCLibInstance 只是myCLibInit' 后跟runST。但不知何故,这不合适。

解决第 3 步的正确方法是什么?我应该执行第 2 步,还是在第 1 步之后立即滚动我的Q?我的感觉是这应该很简单。 ST monad 拥有我所需要的一切,Q 只需要以正确的方式设置...

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

【问题讨论】:

  • 也许使用withMyCLibInstance :: (forall s. Q s a) -&gt; a 会更容易,并相应地修改Q?您似乎正在尝试隐藏 s 参数,这是 ST 工作所必需的。
  • 我不太能够根据您的建议制定解决方案。最后,一定有一些monad M 这样withMyCLibInstance :: M a -&gt; a,所以我们不是以newtype M a = forall s. ST s a 结尾吗?
  • 为什么需要一个单独的 monad?你能改写withMyCLibInstance :: (forall s. STMyClib s -&gt; ST s a) -&gt; a而不暴露myCLibInit吗?
  • 如果我理解正确,那将导致类似withMyCLibInstance $ \obj -&gt; do ... 的内容。好的,这是向前迈出的一步。不过,我仍然必须一直在do 中使用obj

标签: haskell ffi state-monad st-monad


【解决方案1】:

您可以使用阅读器效果访问单例,仅在 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

如果您想继续访问其他ST 功能(如可变引用和数组),s 应该作为MyCLibST 的参数出现。

【讨论】:

  • ReaderT... 这是一个很好的观点。为什么不使用更简单的type MyCLibST a = forall s. ReaderT (STMyCLib s) (ST s) a
  • 带量词的类型同义词有各种问题:它们使类型推断变得困难,Haskell 的类型系统不是含蓄的([MyCLibST a] 不是有效类型)和MyCLibST 不是Monad(因为类型同义词必须始终完全应用)。至于typenewtype 的问题,主要是口味问题,但type 使代码重用更容易,newtype 可以更好地定义抽象边界。我认为newtype 是更安全的默认值。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-03-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多