Haskell 中的 IORef

IORef in Haskell

我想知道 IORef 在 Haskell 中是否有合法的用法?更具体地说,如果有人可以解决以下问题或指向适当的地方以了解更多信息,我将不胜感激:

  1. 使用 IORef 被认为是一种不好的 Haskell 做法吗?如果是,为什么?更具体地说,它比 IO monad 好还是坏?
  2. 如果想向程序中添加状态,state monad 不是更好(更纯粹)的方法吗?如果一个人感觉更迫切,他不能仍然使用 STM 和 MVar,并且仍然更好吗?

  3. 是否存在可以使用 IORef 而不是 STM、MVar 或纯 IO 轻松处理的编程场景?

我正在阅读一篇使用 IORef 作为代码片段的论文,由于我对 IORef 的负面看法,我很难阅读它。与其沉迷于自己的无知,我认为向我的 Haskell 同事寻求帮助可能是一个更好的主意。

首先,我认为对于论文中的代码片段,使用 IORef 是非常明智的,特别是如果论文不是 about 可变的最佳实践引用或并发。 IORef 易于理解,具有直截了当的语法和语义(尤其是在非并发设置中),如果您希望 reader 专注于示例的其他方面而不是 IORef秒。不幸的是,作者的方法适得其反——忽略 IORef 并注意论文的其余部分。

(如果论文 关于可变引用或并发的最佳实践,那么它可能是在更好的替代方案可用之前编写的。)

无论如何,对于更大的问题,反对使用 IORef 的主要理由是:

  • 与将可变状态引入程序的任何其他机制一样,它使您的代码更难以推理、维护、测试等——函数式编程支持者会说的所有常见事情都会给 FP "edge" 超过突变密集型命令式算法。
  • 它只是围绕专用 STRef RealWorld 的新型包装器,它在 STRef 上添加的唯一内容是一些原子操作。在非并发代码中,没有充分的理由不在 ST s monad 中使用 STRef s 值,因为它们更灵活——您可以 运行 在纯代码中使用 runST 或者,如果需要,在 IO monad 中使用 stToIO.
  • 在并发代码中,有更强大的抽象,例如 MVarSTM,它们比 IORef 更容易使用。

因此,在某种程度上可变状态是 "bad" 并且——如果你真的需要它——根据你是否需要并发,可以使用更好的替代方案,没有什么可推荐的IORef.

另一方面, 如果您已经在 IO monad 中处理一些非并发代码,因为您需要执行实际的 IO 操作,并且您真正需要一些不容易从 IO 中分离出来的普遍可变状态,那么使用 IORefs 似乎是合法的。

关于您更具体的问题:

  1. 我想可以肯定地说,当 weaker tool would do the job 时,使用 IORef 被视为 "bad practice"。那个较弱的工具可能是 STRef s,或者更好的是 State monad,或者更好的是完全不需要任何状态的重写的高阶算法。因为 IORef 将 IO 与可变引用结合在一起,所以它是一种命令式的大锤,可能会导致可能出现最单一的 Haskell 代码,因此最好避免使用它,除非它是 "obviously" 正确的解决方案具体问题。

  2. State monad 通常是向程序添加状态的首选惯用方式,但它通过线程化一系列不可变状态来提供可变状态的 "illusion"通过计算得出的值,并不是所有的算法都可以通过这种方式有效地实现。在需要真正可变状态的地方,STRef 通常是非并发设置中的自然选择。请注意,您可能不会在非并发设置中使用 MVarSTM —— 在这种情况下没有理由使用它们,它们会迫使您进入 IO monad即使您不需要它。

  3. 是的,在某些编程场景中,IORefSTRef 优于 StateSTMMVar,或纯 IO(见下文)。很少有 IORef 明显优于 STRef 的情况,但是——如上所述——如果你已经在 IO monad 中并且需要真正的可变状态,那就是与 IO 操作纠缠在一起,那么 IORef 可能在更简洁的语法方面比 STRef 有优势。

一些 IORefSTRef 是好的方法的例子:

    base 包中的
  • Data.Unique 使用 IORef 作为生成唯一对象的全局计数器。
  • base 库中,文件句柄内部广泛使用 IORef 将缓冲区附加到句柄。这是 "already being in the IO monad with entangled IO operations".
  • 的一个很好的例子
  • 使用可变向量可以最有效地实现许多向量算法(例如,甚至像计算数据块中的字节频率这样简单的事情)。如果您使用 vector 包中的可变向量,那么从技术上讲,您使用的是可变字节数组而不是 STRefIORef,但它在道德上仍然是等价的。
  • equivalence 包使用 STRefs 来有效地实现联合连接算法。
  • 作为一个有点悬而未决的例子,如果您正在为一种命令式语言实现解释器,那么使用 IORefSTRef 可变变量的值通常是最有效率。