此monad转换器不应与可能包含多个答案的monad(例如列表monad)一起使用。原因是状态令牌将在不同的答案之间重复,这会导致发生不良情况(例如,丢失参照透明性)。安全的monad包括monad State,Reader,Writer,Maybe以及它们对应的monad转换器的组合。
我希望能够自行判断STT
单子的某种使用是否安全。我特别想了解与List monad的交互。我知道STT sloc (ListT (ST sglob)) a
是unsafe,但是STT sloc []
呢?
我发现(至少在GHC中)STT
最终是使用MuteVar#
,State#
,realWorld#
等魔术构造实现的。是否有任何准确的文档?这些对象的行为如何?
这与earlier question of mine密切相关。
答案 0 :(得分:4)
您真的不需要了解如何实现State#
。您只需要将其视为通过计算线程传递的令牌,即可确保ST
操作的特定执行顺序,否则可能会被优化。
在STT s []
monad中,您可以将列表动作想像为生成可能的计算树,最终答案在叶子上。在每个分支点,State#
令牌被拆分。因此,大致来说:
State#
令牌穿过整个路径,因此将在需要答案时按顺序执行所有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}) }。