据我所知,ST monad就像是IO的小兄弟,后者又是添加RealWorld
魔法的状态monad。我可以想象状态,我可以想象RealWorld以某种方式放入IO,但每次我写ST
的类型签名时,ST monad的s
都会让我困惑。
以ST s (STArray s a b)
为例。 s
如何在那里工作?它是否仅用于在计算之间建立一些人为数据依赖性,而不能像状态monad中那样被引用(由于forall
)?
我只是抛出想法,真的很感谢比我更有知识的人向我解释。
答案 0 :(得分:71)
s
使ST
monad中的对象不会泄漏到ST
monad的外部。
-- This is an error... but let's pretend for a moment...
let a = runST $ newSTRef (15 :: Int)
b = runST $ writeSTRef a 20
c = runST $ readSTRef a
in b `seq` c
好的,这是一个类型错误(这是一件好事!我们不希望STRef
泄漏到原始计算之外!)。由于额外的s
,这是一个类型错误。请记住runST
有签名:
runST :: (forall s . ST s a) -> a
这意味着您正在运行的计算上的s
必须没有约束。因此,当您尝试评估a
:
a = runST (newSTRef (15 :: Int) :: forall s. ST s (STRef s Int))
结果的类型为STRef s Int
,这是错误的,因为s
已在forall
runST
之外“转义”。类型变量总是必须出现在forall
的内部,而Haskell允许在任何地方隐式forall
量词。根本没有规则允许您有意义地找出a
的返回类型。
forall
的另一个例子:为了清楚地说明为什么你不允许事情逃脱forall
,这里有一个更简单的例子:
f :: (forall a. [a] -> b) -> Bool -> b
f g flag =
if flag
then g "abcd"
else g [1,2]
> :t f length
f length :: Bool -> Int
> :t f id
-- error --
当然f id
是一个错误,因为它会返回Char
列表或Int
列表,具体取决于布尔值是true还是false。这是完全错误的,就像ST
的例子一样。
另一方面,如果您没有s
类型参数,那么所有内容都会检查就好了,即使代码显然很伪造。
ST实际如何工作:实现方面,ST
monad实际上与IO
monad相同,但接口略有不同。当您使用ST
monad时,您实际上会获得unsafePerformIO
或等效的幕后信息。您可以安全地执行此操作的原因是因为所有ST
相关函数的类型签名,尤其是具有forall
的部分。
答案 1 :(得分:26)
s
只是一种破坏,使类型系统阻止你做一些不安全的事情。它在运行时没有“做”任何事情;它只是使类型检查器拒绝执行可疑事情的程序。 (这是一个所谓的幻像类型,只存在于类型检查器的头部,并且在运行时不会影响任何内容。)
答案 2 :(得分:0)
以 ST s (STArray s a b)
为例:s
在那里是如何工作的?
GHCi 会话时间:
GHCi, version 8.4.3: http://www.haskell.org/ghc/ :? for help
Prelude> let _ = (\x -> x^4) 5 in x
<interactive>:2:27: error: Variable not in scope: x
Prelude>
范围(或参考框架)
局部变量 x
是 lambda/匿名函数 \x -> x^4
- 在该范围之外,x
否则未定义,因此出现错误。
runST
的类型:
runST :: (forall s . ST s a) -> a
以类似的方式工作:局部量化类型变量s
的范围是类型ST s a
(runST
的参数类型) - 如果 s
出现在结果的类型中,它也超出了它的范围并且也缺乏合理的定义,从而导致类型错误。
所以如果你:
有一个动作m :: ST s (STArray s a b)
而您无意中尝试提取
来自 m
的可变数组:
… (let arr = runST m in …) …
...s
的转义超出其范围:
forall s . ST s (STArray s a)
(及其由类型系统检测)停止 sharing of mutable state,这是一个众所周知的难以捉摸的错误来源,特别是在并行和并发的上下文中。