Jones和Launchbury在他们的paper“懒惰的功能状态线程”中描述了ST monad。至 确保不能在创建它们的上下文(或“线程”)之外使用可变变量 在,他们使用特殊类型,包括更高级别的类型。这里有四个重要的例子:
newVar :: ∀ s a. a -> ST s (MutVar s a)
readVar :: ∀ s a. MutVar s a -> ST s a
writeVar :: ∀ s a. MutVar s a -> a -> ST s ()
runST :: ∀ a. (∀ s. ST s a) -> a
为了理解这种结构背后的想法,我阅读了论文的前两部分。该 以下解释似乎是核心:
现在,我们真正想说的是
newST
应该只应用于使用newVar
来创建该线程中使用的任何引用的状态转换器。换句话说,runST
的论证不应该对初始状态中已经分配的内容做出任何假设。也就是说,runST
应该有效,无论它给出的初始状态如何。因此runST
的类型应为:runST :: ∀ a. (∀ s. ST s a) -> a
解释很好,但我想知道它是如何映射到所需类型的最终定义的。我的
问题是我不知道如何解释类型变量s
。如第1节所述,一个州
本质上是从变量到值的映射。但是什么是州类型s
?在一个什么州可以
类型s
与另一个t
不同?对我来说有两种可能性:
(1)类型s
可以看作是变量映射的具体类型,例如列表,数组
和序列可以看作是顺序集合的具体类型。 (我故意避免在这里
单词“class”和“implementation”,因为s
的类型没有类限制。)
(2)类型s
可以看作具体的价值,即具体的状态,即具体的映射
变量
解释(2)很好地映射到Jones'和Launchbury对runST
类型的解释。该
全量化表示实际状态的独立性(由...完全描述)
国家类型)。然而,这种解释中的缺陷是writeVar
的类型。很清楚
修改状态,它应该是∀ s a. MutVar s a -> a -> ST t ()
类型。所以这
解释必须是假的。
∀ s a. ST s a -> a
的{{1}}类型可以。重要的是
关于给定的状态(类型runST
)的具体值,一定不能有任何假设
到功能。因此,全量化不应该超过状态类型,而是(相反)超过
国家的具体价值。类型系统必须表示有状态的结果
赋予s
的动作独立于状态(但不独立于其类型)。
因此,我的两种解释都是错误的。我真的试着理解这种类型的一切
施工。有人可以向我解释一下吗?
PS。我已经读过这个帖子了 [How does the ST monad work?但它对我没有帮助。什么时候 我用手看我的统一算法,机制是有效的,它是如何工作的(通过 使用范围的限制)但我真的想了解它 - 不仅仅是看它是否有效。
答案 0 :(得分:10)
解释很好,但我想知道它是如何映射到所需类型的最终定义的。
好的,让我们回到的方式直到我们尝试做的事情,以及它与我们已经做的事情有何不同。
我们已经可以功能状态了。这是通过State s
monad完成的,其中State s x :: *
与函数类型s -> (x, s)
同构:将s
作为输入并返回包含两个值的对的函数为monad键入x
,并为s
类型添加新状态。通过一些弱类型,我们甚至可以使用Data.Map.Strict(Map)
来处理这些值。例如,基本的JavaScript弱类型系统在历史上足以支持大多数通用计算,它看起来像(省略函数,结果也是对象,并且有类似词汇闭包的东西):
data JSVariable = Undefined | Number Double | Str String | Boolean Bool | Null |
Object (Map String JSVariable)
通过这些,我们可以使用State (Map String JSVariable)
monad来存储一个函数名称空间,通过它可以引用变量,将它们初始化为值,并在状态线程中对它们进行操作。然后我们有一个类型
runState :: State s x -> s -> (x, s)
我们可以填写并截断以获取:
fst . flip runState Map.empty :: State (Map a b) x -> x
更一般地说,我们可能会立即对此进行概括,使Map
成为Monoid
或其他内容。
ST
如何与众不同?首先,ST
希望 mutate 一个基础数据结构 - 上面没有 mutate Map,但是创建了一个 new < / em>传递给下一个线程的地图。因此,例如,如果我们编写相应的论文let v = runST (newVar True) in runST (readVar v)
,我们上面没有歧义,因为无论您如何对其进行分割,您从等效newVar True
获得的数据结构仅是状态的冻结快照,而不是完整的可变状态本身! State s
monad中没有可变状态。
为了维护一个独特的命名空间状态(而不是某种全局状态)的外观,我们保留了ST s
monad的概念,但现在我们不能直接访问强>到s
。这意味着s
被称为幻像类型:它不代表您可以保留的任何具体内容,这是您将获得的唯一值类型s
是undefined :: s
,即便如此,只有在您有意识地选择制作时才会{。}}。
因为我们永远不会给你一个类型为s
的值,所以你没有任何关于它的功能。对于你来说,总是是一个由底层管道填充的类型变量。 (让我们不要深入了解管道的作用。)
其次,ST
因此允许比上述弱类型系统更大范围的类型安全!我们现在可以让每个newVar :: x -> ST s (MutVar s x)
返回此类型MutVar s x
的封装引用。这个引用包含一个指向它所处的状态的指针,因此它在任何其他上下文中都没有意义 - 它也有自己的不同类型x
,所以它可以保存任何类型,它可以严格的类型检查。
所以我们从我们想要实现的一般想法开始:我们希望能够定义ST s x
类型的线程,其中s
通常保留为类型变量,但x
是我们对计算感兴趣的值,然后最终我们应该能够将它们插入到看起来像这样的函数中:
runST' :: ∀ s a. (ST s a) -> a
这个想法&#34;气味正确&#34;因为它(a)类似于State s
monad中类似的东西,并且(b)你应该能够取ST s a
,为它做一个空白状态,然后运行计算到返回纯函数a
。
但是......有一个问题。问题是,在本文的这一点上,我们有几个函数,如newVar :: x -> ST s (MutVar s x)
,它们将内部线程状态类型s
复制到&#34;输出&#34;这个单子的空间。即使我们没有得到类型为 s
的值,我们仍然会得到类型变量 s
,正如您所记得的那样某些可变状态块的某种通用命名空间。其他函数如readVar :: MutVar s x -> ST s x
将允许您创建依赖于此特定可变状态的其他状态线程。
这是关键词:具体。 newVar :: x -> ST s (MutVar s x)
的类型包含非特定或一般 s
,它适用于任何s
。但是,如果您runST (newVar True)
为某些特定的 MutVar s Bool
获得了s
,那么就是计算中使用的那个。我们希望确保runST
仅使用一般 s
而非特定的。
换句话说,newVar
的类型为x -> forall s. ST s (MutVar s x)
(请参阅forall
强制变量仍为免费?),而readVar
的类型为MutVar s x -> ST s x
(不是forall
- 本身,或者来自其他计算的特定MutVar
,它具有特定的s
。如果每s
我们还包含生成传递给readVar
的对象的newVar
,readVar
只会从特定转换为一般转换为forall s
保持这个术语一般!
因此,本文的基本问题是:我们如何重新定义ST
以确保状态不具体?必须出现这样的情况readVar
不能提供给runST
,但是来自newVar True
的内容,甚至来自newVar True >>= readVar
的内容都可以在送到runST
时进行类型检查{1}}。
他们的回答是,我们添加到Haskell的语法,说&#34;这个类型变量必须仍然是一个自由类型变量!&#34;这是通过编写 这会阻止类型检查的并行线程废话,从而使整个系统正确。runST :: forall x. (forall s. ST s x) -> x
来完成的。请注意,forall s.
现在在函数参数中包含来表示&#34;嘿,此参数必须是此表达式中的常规自由类型参数。&#34; < / p>
答案 1 :(得分:7)
我会避免将s
解释为任何具体的东西。它基本上只是一个“唯一标识符”,可确保两个不同ST
计算中的状态不会混淆。特别是,因为runST
的参数是一个(rank-2)多态行为,所以你可以以某种方式走私这个monadic动作的任何状态引用都必然是一个存在类型,而且基本上什么也没有你可以做一个无拘无束的存在主义。因此,例如,它是不可能的。指的是monad状态中不再存在的值。
如果您希望将s
视为具体内容,请将其视为 world 的类型,其中可以分配,修改和删除有状态值。
该设置中的类型说明:
newVar :: ∀ s a. a -> ST s (MutVar s a)
:在任何世界s
上,我能够将a
类型的值抛向地面,然后可以通过{{1引用(当然,只在同一个世界中 - 虽然这个世界的状态因此发生了变化,但它的类型/身份并没有)。MutVar
:如果可变引用和我一样生活在同一个世界中,我可以查看该对象并给你一份副本作为monadic结果。readVar :: ∀ s a. MutVar s a -> ST s a
:如果可变参考与我一样生活在同一个世界中,我就能够摧毁坐在那里的物体并用其他东西替换它。writeVar :: ∀ s a. MutVar s a -> a -> ST s ()
:如果行动适用于任何世界,我可以选择一些没有人知道的隐藏星球。然后,该行动可以将各种变异的暴行应用于景观并仅将结果带回来。因为没有人知道这个星球,我们仍然拥有一个宇宙,对于所有人都能看到的宇宙,它没有受到伤害,而且功能完全正常,但你确实得到了破坏性计算的结果。