状态,ST,IORef和MVar之间的区别

我正在48小时内写作自己的计划 (我约85小时),我已经到了关于添加variables和作业的部分。 本章有一个很大的概念上的跳跃,我希望这个过程是分两步完成的,在这之间有一个很好的重构,而不是直接跳到最后的解决scheme。 无论如何…

我已经迷失了许多不同的课程,这些课程似乎有相同的目的: StateSTIORefMVar 。 正文中提到了前三个,而最后一个似乎是前三个StackOverflow问题的答案。 它们似乎都在连续调用之间进行状态。

这些都是什么,它们又有什么不同呢?


特别是这些句子没有意义:

相反,我们使用称为状态线程的function,让Haskell为我们pipe理聚合状态。 这让我们可以像处理任何其他编程语言一样使用函数来获取或设置variables。

IORef模块允许您使用IO monad内的有状态variables。

所有这些使得行type ENV = IORef [(String, IORef LispVal)]混淆 – 为什么第二个IORef ? 如果我写入type ENV = State [(String, LispVal)]会怎样呢?

国家Monad:一个可变状态的模型

国家monad是一个纯粹的function环境,为国家的程序,用一个简单的API:

  • 得到

mtl包中的文档。

状态monad是在单个控制线程中需要状态时常用的。 它在实现中实际上并不使用可变状态。 而是通过状态值对程序进行参数化(即状态是所有计算的附加参数)。 该状态似乎只在单个线程中发生变化(并且不能在线程之间共享)。

ST monad和STRefs

ST monad是IO monad的受限表亲。

它允许任意可变的状态 ,实现为机器上的实际可变内存。 在无副作用的程序中,API是安全的,因为rank-2types参数可防止依赖于可变状态的值从本地作用域转义。

因此,在其他纯粹的程序中允许受控的可变性。

通常用于可变数组和其他变异的数据结构,然后冻结。 这也是非常有效的,因为可变状态是“硬件加速”。

主要API:

  • Control.Monad.ST
  • runST – 开始一个新的记忆效应计算。
  • 和STRefs :指向(本地)可变单元格的指针。
  • 基于ST的arrays(如vector)也很常见。

把它想象成IO monad的危险性较低的兄弟。 或者IO,在那里你只能读写内存。

IORef:IO中的STRefs

这些是IO monad中的STRefs(参见上文)。 他们没有STREFS关于地方的安全保证。

MVars:带锁的IORefs

像STRefs或IORefs一样,但附有一个锁,用于从多个线程进行安全的并发访问。 在使用atomicModifyIORef (比较和交换primefaces操作)时,IORefs和STRefs仅在multithreading设置中是安全的。 MVars是安全共享可变状态的更一般的机制。

通常,在Haskell中,通过STRef或IORef使用MVars或TVars(基于STM的可变单元格)。

好的,我将从IORef开始。 IORef提供了一个在IO monad中可变的值。 这只是对某些数据的引用,就像任何引用一样,还有一些函数允许您更改所引用的数据。 在Haskell中,所有这些函数都在IO运行。 你可以把它想象成一个数据库,文件或其他外部数据存储 – 你可以获取和设置数据,但是这样做需要通过IO。 IO是完全必要的原因是因为Haskell是纯粹的 ; 编译器需要一种方法来知道在任何给定时间参考指向哪些数据(请阅读sigfpe的“你可能已经发明了单子” blogpost)。

MVar与IORef基本上是一样的,除了两个非常重要的区别。 MVar是一个并发原语,所以它被devise用于从多个线程访问。 第二个不同之处在于MVar是一个可以填满或空的框。 所以在IORef Int总是有一个Int (或者是底部)的情况下, MVar Int可能有一个Int或者它可能是空的。 如果一个线程试图从一个空的MVar读取一个值,它将会阻塞,直到MVar被填充(被另一个线程)。 基本上,一个MVar a相当于一个IORef (Maybe a) ,它具有对并发有用的额外语义。

State是提供可变状态的monad,不一定与IO相关。 事实上,它对纯粹的计算特别有用。 如果你有一个使用状态而不是IO的algorithm, State monad通常是一个优雅的解决scheme。

StateT还有一个monad变压器版本。 这经常被用来保存程序configuration数据,或者应用程序中的“游戏世界状态”状态types。

ST是稍有不同的东西。 ST的主要数据结构是STRef ,它就像一个IORef但具有不同的monad。 ST monad使用types系统欺骗(“状态线程”文档提到)来确保可变数据不能转义monad; 也就是说,当你运行一个ST计算,你会得到一个纯粹的结果。 ST很有趣的原因是它是一个像IO这样的原始monad,允许计算对bytearrays和指针执行低级操作。 这意味着ST可以在对可变数据使用低级操作时提供纯粹的接口,这意味着它非常快速。 从程序的angular度来看,就好像ST计算运行在一个单独的线程和线程本地存储一样。

其他人做了核心的事情,但回答直接的问题:

所有这些使行typesENV = IORef [(String, IORef LispVal)]混淆。 为什么第二个IORef? 如果我type ENV = State [(String, LispVal)]会怎样呢?

Lisp是一个具有可变状态和词法范围的函数式语言。 想象一下,你已经closures了一个可变的variables。 现在你已经有了这个variables的引用,这个variables在(printIt, setIt) = let x = 5 in (\ () -> print x, \y -> set xy)风格的伪代码里(printIt, setIt) = let x = 5 in (\ () -> print x, \y -> set xy) 。 你现在有两个函数 – 一个打印x,一个设置它的值。 当你评估printIt ,你想在printIt被定义的初始环境中查找x的名字 ,但是你想在printIt调用的环境中查找名字绑定的setIt之后可能被调用任何次数)。

有两种方法可以让这两个IORefs做到这一点,但是你肯定需要的不仅仅是你提出的后一种types,它不允许你以一个词法作用域的方式改变名字的值。 谷歌的“funargs问题”为一大堆有趣的史前史。