"状态#"的规范



但是,STT的文档说:

此 monad 转换器不应与可以包含多个答案的 monad 一起使用,例如列表 monad。原因是状态令牌将在不同的答案中重复,这会导致坏事发生(例如失去引用透明度(。安全单子包括单子状态、读取器、写入器、可能及其相应单子转换器的组合。

我希望能够自己判断STTmonad的某种使用是否安全。特别是,我想了解与列表monad的相互作用。我知道STT sloc (ListT (ST sglob)) a不安全,但STT sloc []呢?

我发现(至少在GHC中(,STT最终是使用MuteVar#State#realWorld#等神奇的结构来实现的。是否有关于这些对象行为方式的准确文档?

这与我前面的一个问题密切相关。

你真的不需要了解State#是如何实现的。 您只需要将其视为通过计算线程传递的令牌,以确保ST操作的特定执行顺序,否则这些操作可能会被优化掉。

STT s []monad 中,您可以将列表操作视为生成一个可能的计算树,最终答案位于叶子处。 在每个分支点,拆分State#令牌。 所以,粗略地说:

  • 在从根到叶的特定路径中,单个State#令牌贯穿整个路径,因此当需要答案时,所有 ST 操作将按顺序执行
  • 对于两条路径
  • ,它们共同的树部分中的 ST 操作(拆分之前(是安全的,并且以您期望的方式在两条路径之间正确"共享">
  • 拆分两条路径后,未指定两个独立分支中操作的相对顺序

我相信还有进一步的保证,尽管有点难以推理:

如果在最终的答案列表(即runSTT生成的列表(中,你强制索引k处的单个答案 - 或者,实际上,我认为如果你只是强制列表构造函数证明在索引k有一个答案 - 那么树的深度优先遍历中的所有操作都将被执行,直到该答案。 问题是树中的其他操作也可能已被执行。

例如,以下程序:

{-# OPTIONS_GHC -Wall #-}
import Control.Monad.Trans
import Control.Monad.ST.Trans
type M s = STT s []
foo :: STRef s Int -> M s Int
foo r = do
_ <- lift [1::Int,2,3]
writeSTRef r 1
n1 <- readSTRef r
n2 <- readSTRef r
let f = n1 + n2*2
writeSTRef r f
return f
main :: IO ()
main = print $ runSTT $ foo =<< newSTRef 9999

使用-O0编译时,GHC 8.4.3 下产生不同的答案(答案为[3,3,3](与-O2(答案为[3,7,15](。

在其(简单(计算树中:

root
/  |  
1   2   3          _ <- lift [1,2,3]
/    |    
wr    wr    wr       writeSTRef r 1
|     |     |
rd    rd    rd       n1 <- readSTRef r
|     |     |
rd    rd    rd       n2 <- readSTRef r
|     |     |
wr    wr    wr       writeSTRef r (n1 + n2*2)
|     |     |
f     f     f        return (n1 + n2*2)

我们可以推断,当需要第一个值时,左分支中的写入/读取/读取/写入操作已被执行。 (在这种情况下,我认为中间分支上的写入和读取也已执行,如下所述,但我有点不确定。

当需要第二个值时,我们知道左分支中的所有操作都已按顺序执行,中间分支中的所有操作已按顺序执行,但我们不知道这些分支之间的相对顺序。 它们可能是完全按顺序执行的(给出答案3(,或者它们可能是交错的,以便左分支上的最终写入落在右分支上的两个读取之间(给出答案1 + 2*3 = 7(。

最新更新