状态,ST,IORef和MVar之间的区别
我正在48小时内写作自己的计划 (我约85小时),我已经到了关于添加variables和作业的部分。 本章有一个很大的概念上的跳跃,我希望这个过程是分两步完成的,在这之间有一个很好的重构,而不是直接跳到最后的解决scheme。 无论如何…
我已经迷失了许多不同的课程,这些课程似乎有相同的目的: State
, ST
, IORef
和MVar
。 正文中提到了前三个,而最后一个似乎是前三个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
计算运行在一个单独的线程和线程本地存储一样。
其他人做了核心的事情,但回答直接的问题:
所有这些使行types
ENV = 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问题”为一大堆有趣的史前史。