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

Joh*_*ler 87 variables monads haskell state-monad ioref

我正在通过48小时为自己编写一个方案(我大约85小时)而且我已经完成了关于添加变量和赋值的部分.本章中有一个很大的概念性跳跃,我希望它分两步完成,两者之间有很好的重构,而不是直接跳到最终的解决方案.无论如何…

我已经得到了一些不同类的,似乎达到相同的目的失去了:State,ST,IORef,和MVar.文本中提到了前三个,而最后三个似乎是许多关于前三个问题的StackOverflow问题的最佳答案.它们似乎在连续调用之间都处于状态.

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


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

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

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

所有这一切都使这条线路type ENV = IORef [(String, IORef LispVal)]混乱 - 为什么第二个IORef呢?如果我写的话会破坏什么type ENV = State [(String, LispVal)]

Don*_*art 116

国家Monad:可变状态的模型

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

  • 得到

mtl包中的文档.

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

ST monad和STRefs

ST monad是IO monad的受限表兄弟.

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

因此,它允许在其他纯程序中进行受控的可变性.

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

主API:

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

可以把它想象成IO monad中不那么危险的兄弟.或IO,您只能读取和写入内存.

IORef:IO中的STRefs

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

MVars:带锁的IORefs

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

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

  • 为什么你说`MVar`应该比'STRef`更受欢迎?`STRef`保证只有一个线程可以改变它(并且不会发生其他类型的IO) - 如果我不需要并发访问可变状态,那肯定会更好吗? (10认同)
  • TV中的M和电视中的T是什么?我猜"Mutable","Transactional".有趣的是ST如何表示State Thread. (3认同)

Joh*_*n L 36

好的,我会先说IORef. IORef提供IO monad中可变的值.它只是对某些数据的引用,与任何引用一样,有些功能允许您更改它引用的数据.在Haskell中,所有这些功能都在运行IO.您可以将其视为数据库,文件或其他外部数据存储 - 您可以在其中获取和设置数据,但这样做需要通过IO.IO是必要的原因是因为Haskell是纯粹的 ; 编译器需要一种方法来知道在任何给定时间引用指向哪些数据(阅读sigfpe的"你可能已经发明了monads"博客帖子).

MVar除了两个非常重要的差异之外,它与IORef基本相同. MVar是一个并发原语,因此它设计用于从多个线程进行访问.第二个区别是a MVar是一个可以满或空的盒子.因此,如果IORef Int总是有Int(或者是底部),则MVar Int可能有一个Int或者它可能是空的.如果一个线程试图从空读取一个值MVar,它将阻塞直到MVar被填充(由另一个线程).基本上,a MVar a等同于具有IORef (Maybe a)对并发有用的额外语义.

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

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

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


scl*_*clv 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,你要查找的名称在其初始环境x的printIt定义,但你要查找的该名称被绑定到所处的环境printIt称为(后setIt可能已被调用的任何次数).

有两种方法可以让两个IORef执行此操作,但您确实需要的不仅仅是您提出的后一种类型,它不允许您以词法范围的方式更改名称绑定的值.谷歌的"funargs问题"对于很多有趣的史前史.