我想知道Haskell中是否存在IORef的合法用法?更具体地说,如果有人能解决以下问题或指出一个合适的地方来了解更多信息,我将不胜感激:
- 使用IORef是否被认为是一种糟糕的Haskell做法?如果是,为什么?更具体地说,它如何比IO monad更好或更糟
-
如果一个人想把状态添加到程序中,那么状态monad不是更好(更纯粹)的方法吗?如果一个人觉得更有必要,他就不能继续使用STM和MVar吗?
-
是否存在使用IORef而不是STM、MVar或纯IO可以轻松处理的编程场景?
我正在读一篇使用IORef作为代码片段的论文,由于我对IORef的负面看法,这篇论文对我来说很难阅读。与其沉溺于我的无知,我想向我的哈斯克尔同伴寻求帮助可能是个更好的主意。
首先,我认为对于论文中的代码片段,使用IORef
是非常明智的,特别是如果论文不是关于可变引用或并发的最佳实践的。IORef
易于理解,具有直接的语法和语义(尤其是在非并发环境中),如果你想让读者专注于你的例子中除了IORef
s之外的方面,这是一个自然的选择。不幸的是,作者的方法对你产生了反效果——只需忽略IORef
s,并关注论文的其余部分。
(如果这篇论文是关于可变引用或并发的最佳实践的,那么它可能是在有更好的替代方案之前写的。)
无论如何,对于您更大的问题,使用IORef
的主要反对意见是:
- 就像任何其他将可变状态引入程序的机制一样,它会使代码更难推理、维护、测试等——函数式编程支持者会说,所有这些通常都会让FP比突变密集型命令式算法"有优势">
- 它只是一个围绕专用
STRef RealWorld
的新型包装器,它在STRef
上添加的唯一内容是一些原子操作。在非并发代码中,没有充分的理由不在ST s
monad中使用STRef s
值,因为它们更灵活——您可以在带有runST
的纯代码中运行它们,如果需要,也可以在带stToIO
的IO monad中运行它们 - 在并发代码中,有更强大的抽象,如
MVar
和STM
,它们比IORef
更容易使用
因此,在可变状态"糟糕"的程度上——如果你真的需要它——根据你是否需要并发性,可以提供更好的替代方案,IORef
没有太多值得推荐的地方。
另一方面,如果您已经在IO
monad中处理一些非并发代码,因为您需要执行实际的IO操作,并且您确实需要一些不容易从IO中分离出来的普遍可变状态,那么使用IORef
似乎是合法的。
关于您更具体的问题:
-
我想可以肯定地说,使用
IORef
被认为是"糟糕的做法",而较弱的工具可以完成这项工作。这个较弱的工具可能是STRef s
,或者更好的是State
monad,或者更好地是一个完全不需要任何状态的重写的高阶算法。因为IORef
将IO与可变引用结合在一起,所以它有点像一把命令式的大锤,很可能会导致最独特的Haskell代码,所以最好避免它,除非它"显然"是解决特定问题的正确方案。 -
State
monad通常是向程序添加状态的首选惯用方法,但它通过将一系列不可变的状态值贯穿计算过程来提供可变状态的"幻觉",并且并非所有算法都能以这种方式有效实现。在需要真正的可变状态的情况下,STRef
通常是非并发设置中的自然选择。请注意,您可能不会在非并发设置中使用MVar
或STM
——在这种情况下没有理由使用它们,而且它们会迫使您使用IO
monad,即使您不需要它。 -
是的,在某些编程场景中,
IORef
或STRef
比State
、STM
、MVar
或纯IO
更可取(见下文)。在少数情况下,IORef
显然比STRef
更可取,但如上所述,如果您已经处于IO
monad中,并且需要与IO操作纠缠在一起的真正可变状态,那么IORef
在稍微干净的语法方面可能比STRef
更具优势。
IORef
或STRef
是一种好方法的一些例子:
base
包中的Data.Unique
使用IORef
作为全局计数器来生成唯一对象
base
库中,文件句柄内部广泛使用IORef
将缓冲区附加到句柄。这是一个很好的例子,说明"已经在IO monad中进行纠缠IO操作">vector
包中的可变向量,那么从技术上讲,您使用的是可变字节数组,而不是STRef
或IORef
,但在道德上仍然是等效的equivalence
包使用STRef
s来有效地实现联合联接算法IORef
或STRef
值通常是最有效的