“状态号”

时间:2018-08-31 13:42:46

标签: haskell ghc state-monad st-monad

但是,documentation for STT说:

  

此monad转换器不应与可能包含多个答案的monad(例如列表monad)一起使用。原因是状态令牌将在不同的答案之间重复,这会导致发生不良情况(例如,丢失参照透明性)。安全的monad包括monad State,Reader,Writer,Maybe以及它们对应的monad转换器的组合。

我希望能够自行判断STT单子的某种使用是否安全。我特别想了解与List monad的交互。我知道STT sloc (ListT (ST sglob)) aunsafe,但是STT sloc []呢?

我发现(至少在GHC中)STT最终是使用MuteVar#State#realWorld#等魔术构造实现的。是否有任何准确的文档?这些对象的行为如何?

这与earlier question of mine密切相关。

1 个答案:

答案 0 :(得分:4)

真的不需要了解如何实现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(答案为[3,3,3])和-O2(答案为[3,7,15])进行编译时,根据GHC 8.4.3产生了不同的答案。

在其(简单的)计算树中:

    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}) }。