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


91

我正在48小时内完成“ 编写自己的计划”(我大约需要85小时),并且已经完成了有关添加变量和赋值的部分。本章在概念上有很大的跳跃,我希望它分两步完成,并且在两者之间进行了很好的重构,而不是直接跳转到最终解决方案。无论如何…

我迷路了一些不同类的,似乎达到相同的目的:StateSTIORef,和MVar。文本中提到了前三个,而后三个似乎是很多有关前三个的StackOverflow问题的首选答案。它们似乎在连续调用之间都带有状态。

这些分别是什么,它们又有何不同?


这些句子尤其没有意义:

相反,我们使用一种称为状态线程的功能,让Haskell为我们管理聚合状态。这使我们能够像使用任何其他编程语言一样对待可变变量,使用函数来获取或设置变量。

IORef模块使您可以在IO monad中使用状态变量。

所有这些使这条线变得type ENV = IORef [(String, IORef LispVal)]混乱-为什么第二条IORef呢?如果我改写会type ENV = State [(String, LispVal)]怎样?

Answers:


119

国家莫纳德:可变国家的典范

State monad是具有状态的程序的纯功能环境,它具有简单的API:

  • 得到

mtl软件包中的文档。

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

ST monad和STRefs

ST monad是IO monad的受限表亲。

它允许任意可变状态,实现为机器上的实际可变内存。该API在无副作用程序中非常安全,因为rank-2类型参数可防止依赖于可变状态的值转义局部范围。

因此,它允许在其他情况下纯程序中受控的可变性。

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

主要API:

  • Control.Monad.ST
  • runST-启动新的内存效果计算。
  • STRefs:指向(本地)可变单元格的指针。
  • 基于ST的数组(例如向量)也是常见的。

可以将其视为IO monad的不太危险的兄弟。或IO,您只能在其中读写内存。

IORef:IO中的STRef

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

MVars:带锁的IORef

与STRef或IORef相似,但带有一个锁,用于从多个线程进行安全的并发访问。IORef和STRef仅在使用atomicModifyIORef(比较交换原子操作)时在多线程设置中安全。MVar是安全共享可变状态的更通用机制。

通常,在Haskell中,应通过STRef或IORef使用MVar或TVar(基于STM的可变单元)。


3
MVars中的M和TVars中的T是什么?我猜是“可变”,“交易”。有趣的是,ST表示状态线程。
CMCDragonkai 2015年

10
为什么说MVar应该优先于此STRefSTRef保证只有一个线程可以对其进行更改(并且不会发生其他类型的IO)-如果不需要并发访问可变状态,肯定会更好吗?
本杰明·霍奇森

@CMCDragonkai我一直认为M代表互斥体,但是我找不到它在任何地方都有记载。
Andrew Thaddeus Martin,

37

好的,我将从开始IORefIORef提供在IO monad中可变的值。它只是对某些数据的引用,并且像任何引用一样,有一些函数可让您更改其引用的数据。在Haskell中,所有这些功能都在中运行IO。您可以将其视为数据库,文件或其他外部数据存储,可以在其中获取和设置数据,但是这样做需要经过IO。IO绝对必要的原因是因为Haskell是纯洁的;编译器需要一种方法来知道参考在任何给定时间指向的数据(请阅读sigfpe的“您可能已经发明了monads”博客文章)。

MVar除了两个非常重要的区别外,s与IORef基本相同。 MVar是一个并发原语,因此它是为从多个线程访问而设计的。第二个区别是an MVar是一个可以填充或填充的框。因此,在IORef Int始终有一个Int(或位于底部)的情况下,一个MVar Int可能具有Int或可能为空。如果一个线程试图从empty读取一个值MVar,它将阻塞直到MVar被另一个线程填充为止。基本上,an MVar a等于IORef (Maybe a)具有对并发有用的额外语义的an 。

State是提供可变状态的单子,不一定与IO一起提供。实际上,它对于纯计算特别有用。如果你有一个使用状态,但不是一个算法IO,一个State单子往往是一种优雅的解决方案。

还有一个State的monad变压器版本StateT。这经常用于保存程序配置数据或应用程序中的“游戏世界状态”状态类型。

ST有点不同。中的主要数据结构STSTRef,类似于,IORef但具有不同的monad。该ST单子用途类型系统欺骗(即“状态的线程”的文档提),以确保可变数据无法逃避的单子; 也就是说,当您执行ST计算时,您将获得纯结果。ST有趣的原因是它是类似于IO的原始monad,它允许计算对字节数组和指针执行低级操作。这意味着ST可以在对可变数据进行低级操作时提供纯接口,这意味着它非常快。从程序的角度看,就好像ST计算在具有线程本地存储的单独线程中运行一样。


17

其他人已经完成了核心工作,但要回答直接的问题:

所有这些使线型ENV = IORef [(String, IORef LispVal)] 令人困惑。为什么要使用第二个IORef?如果我type ENV = State [(String, LispVal)]改为这样做会怎样?

Lisp是一种具有可变状态和词法范围的功能语言。想象您已经关闭了一个可变变量。现在,您已经获得了对该变量的引用,这些变量还散布在其他函数中-例如(用haskell样式的伪代码)(printIt, setIt) = let x = 5 in (\ () -> print x, \y -> set x y)。现在,您有两个功能-一个打印x,另一个设置它的值。当您求值时printIt,您想要在定义了它的初始环境中查找x 的名称printIt,但是您想要在被调用的环境中查找该名称所绑定的(可能被调用了多次) )。printItsetIt

有两种方法可以使这两个IORef陷入困境,但是您肯定比您提出的后者要多,这不允许您以词法范围更改名称绑定的值。Google提出了很多有趣的史前“真菌问题”。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.