我遇到过RunST的Rank 2类型的许多解释,以及它如何阻止引用转义RunST。但我无法找出为什么这也会阻止以下代码进行类型检查(这是正确的,但我仍然想知道它是如何做的):
test = do v ← newSTRef True
let a = runST $ readSTRef v
return True
与:相比:
test = do v ← newSTRef True
let a = runST $ newSTRef True >>= λv → readSTRef v
return True
在我看来,newSTRef
的类型对于两种情况都是相同的:Λs. Bool → ST s (STRef s Bool)
。如果我理解的话,两种情况下v
的类型都是∃s. STRef s Bool
。但后来我很困惑接下来要做什么,以显示为什么第一个例子没有进行类型检查,我没有看到两个例子之间的差异,只是在第一个例子v
被绑定在runST
之外{1}}和第二个内部,我怀疑这是关键。如果我在我的推理中出错,请帮我展示这个和/或纠正我。
答案 0 :(得分:13)
函数runST :: (forall s . ST s a) -> a
是魔术发生的地方。要问的正确问题是如何生成类型为forall s . ST s a
的计算。
将其读作英语,这是一个对s
的所有选项有效的计算,这是一个幻像变量,表示ST
计算的特定“线程”。
当您使用newSTRef :: a -> ST s (STRef s a)
时,您正在生成一个STRef
,它与生成它的ST
线程共享其线程变量。这意味着值s
的{{1}}类型固定为与较大线程的v
类型相同。这种固定性意味着涉及s
的操作不再对线程v
的所有选择有效。因此,s
将拒绝涉及runST
的操作。
相反,像v
这样的计算在任何newSTRef True >>= \v -> readSTRef v
线程中都有意义。整个计算的类型对所有“ST
个线程ST
都有效”。这意味着它可以使用s
运行。
通常,runST
必须包围所有runST
个线程的创建。这意味着ST
的参数中s
的所有选择都是任意的。如果runST
周围的某些内容与runST
的参数相互作用,则可能会修复runST
的某些选项以匹配周围的上下文,因此不再可以自由应用所有“s
的选择。