如何理解Haskell的ST monad中的状态类型

时间:2015-12-28 13:57:15

标签: haskell types monads states

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 ()类型。所以这 解释必须是假的。

然而,解释(1)并不是更好,因为它没有映射到Jones'和Launchbury的 说明。 ∀ s a. ST s a -> a的{​​{1}}类型可以。重要的是 关于给定的状态(类型runST)的具体值,一定不能有任何假设 到功能。因此,全量化不应该超过状态类型,而是(相反)超过 国家的具体价值。类型系统必须表示有状态的结果 赋予s的动作独立于状态(但不独立于其类型)。

因此,我的两种解释都是错误的。我真的试着理解这种类型的一切 施工。有人可以向我解释一下吗?

PS。我已经读过这个帖子了 [How does the ST monad work?但它对我没有帮助。什么时候 我用手看我的统一算法,机制是有效的,它是如何工作的(通过 使用范围的限制)但我真的想了解它 - 不仅仅是看它是否有效。

2 个答案:

答案 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被称为幻像类型:它不代表您可以保留的任何具体内容,这是您将获得的唯一值类型sundefined :: 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的对象的newVarreadVar只会从特定转换为一般转换为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 ():如果行动适用于任何世界,我可以选择一些没有人知道的隐藏星球。然后,该行动可以将各种变异的暴行应用于景观并仅将结果带回来。因为没有人知道这个星球,我们仍然拥有一个宇宙,对于所有人都能看到的宇宙,它没有受到伤害,而且功能完全正常,但你确实得到了破坏性计算的结果。