State,ST,IORef和MVar之间的区别

时间:2011-04-04 23:33:42

标签: variables haskell monads state-monad ioref

我正在通过Write Yourself a Scheme in 48 Hours(我大约85小时),我已经完成了关于Adding Variables and Assignments的部分。本章中有一个很大的概念性跳跃,我希望它分两步完成,两者之间有很好的重构,而不是直接跳到最终的解决方案。总之...

我已经迷失了许多看似用于同一目的的不同类:StateSTIORefMVar。文本中提到了前三个,而最后三个似乎是许多关于前三个问题的StackOverflow问题的最佳答案。它们似乎在连续调用之间都处于状态。

这些是什么以及它们如何彼此不同?


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

  

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

  

IORef模块允许您在IO monad 中使用有状态变量

所有这些都会让行type ENV = IORef [(String, IORef LispVal)]感到困惑 - 为什么第二个IORef?如果我改写type ENV = State [(String, LispVal)]会怎样?

3 个答案:

答案 0 :(得分:111)

State Monad:一个可变状态的模型

状态monad是一个纯函数的环境,用于具有状态的程序,具有简单的API:

  • 获得

the mtl package中的文档。

状态monad通常在单个控制线程中需要状态时使用。它实际上并没有使用可变状态。相反,程序由状态值参数化(即状态是所有计算的附加参数)。状态似乎只在一个线程中发生变异(并且不能在线程之间共享)。

ST monad和STRefs

ST monad是IO monad的受限表兄。

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

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

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

主要API:

  • Control.Monad.ST
  • runST - 启动新的记忆效应计算。
  • STRefs:指向(本地)可变细胞的指针。
  • 基于ST的数组(例如vector)也很常见。

将其视为IO monad中较不危险的兄弟姐妹。或者IO,你只能读写内存。

IORef:IO中的STRefs

这些是IO monad中的STRef(见上文)。它们没有STRefs关于地点的安全保障。

MVars:带锁的IORef

与STRefs或IORefs类似,但附加了锁,以便从多个线程进行安全的并发访问。使用atomicModifyIORef(比较和交换原子操作)时,IORef和STRef在多线程设置中是安全的。 MVars是一种更安全地共享可变状态的更通用的机制。

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

答案 1 :(得分:36)

好的,我将从IORef开始。 IORef提供IO monad中可变的值。它只是对某些数据的引用,与任何引用一样,有些功能允许您更改它引用的数据。在Haskell中,所有这些函数都在IO中运行。您可以将其视为数据库,文件或其他外部数据存储 - 您可以在其中获取和设置数据,但这样做需要通过IO。 IO是必要的原因是因为Haskell ;编译器需要一种方法来知道参考在任何给定时间指向哪些数据(阅读sigfpe's "You could have invented monads"博文)。

除了两个非常重要的差异之外,

MVar与IORef基本相同。 MVar是一个并发原语,因此它设计用于从多个线程进行访问。第二个区别是MVar是一个可以满或空的框。因此,IORef Int始终具有Int(或底部),MVar Int可能有Int或者可能为空。如果一个线程试图从空MVar读取一个值,它将一直阻塞,直到MVar被另一个线程填充。基本上,MVar a等同于IORef (Maybe a),其具有对并发有用的额外语义。

State是一个提供可变状态的monad,不一定是IO。实际上,它对纯计算特别有用。如果您的算法使用状态但不使用IO,则State monad通常是一种优雅的解决方案。

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

ST略有不同。 ST中的主要数据结构是STRef,它类似于IORef但具有不同的monad。 ST monad使用类型系统技巧(文档中提到的“状态线程”)来确保可变数据不能逃脱monad;也就是说,当你运行ST计算时,你会得到一个纯粹的结果。 ST很有趣的原因是它是一个像IO一样的原始monad,允许计算对字节数组和指针执行低级操作。这意味着ST可以在对可变数据使用低级操作时提供纯接口,这意味着它非常快。从程序的角度来看,就好像ST计算在一个带有线程本地存储的单独线程中运行。

答案 2 :(得分:18)

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

  

所有这些都使得线型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时,您希望在定义printIt的初始环境中查找x的名称,但是您要查找该名称在printIt 被称为的环境中被绑定(setIt可能被多次调用之后)。

有两种方法可以让两个IORef这样做,但你确实需要的不仅仅是你提出的后一种类型,它不允许你以词法范围的方式改变名称绑定的值。谷歌是一个有趣的史前史的“funargs问题”。