数据结构具有变异和非变异操作。例如,字典插入可以更改其基础数据结构的状态,但查找通常不会。
一些数据结构改变了它们的内部结构 - 即使在逻辑上非变异操作 - 但是以不改变可观察状态的方式。例如,splay tree在查找时将元素移向根,并且move-to-front list在查找时将元素移向头部。从逻辑上讲,这组密钥没有改变。
在C ++中,可以通过定义具有const
方法但mutable
数据成员的数据结构来实现。有没有办法在Haskell中这样做?我唯一能想到的是
setContains :: Set k -> k -> (Set k, Bool)
但这很难看,因为底层数据结构会改变界面。
答案 0 :(得分:6)
如果不使用不安全的原语,就无法实现这种低级优化,因此允许从纯代码中突变数据结构。
首先,请注意,在GHC运行时,纯代码 修改数据结构 - 通过评估它们。 E.g。
x = (3+2, 4+5)
main = print (fst x) >>> print (fst x)
在GHC中,第一个print
调用实际上重写 x
为(5, 4+5)
,用结果重写其第一个组件。这样,第二个print
不必再次执行添加。
当然,这种重写永远不会改变x
的语义,因此它是一种特殊的,#34;安全的#34;一种突变。
有时这还不足以实现一些低级优化,例如问题中描述的优化。然后,不安全的原语是唯一的选择。
我相信这种技术的典型例子是Data.Array.Diff
。这是一个不可变的数组数据结构,具有恒定的时间访问和更新(!)。在引擎盖下,有一个可变数组,其中必须执行更新。由于这会严重破坏对 immutable 数组的旧引用,因此这些引用也指向(可变)" changelog"在更新之前存储旧值。因此,我们得到了一种"版本控制" system:最后一个版本很快,旧版本变得越来越慢。
从外面看,只能观察到纯粹的行为;在里面,发生了很多变异。
该实现使用MVar
来阻止并发访问,以避免OP在上面的注释中描述的多线程问题。
让我再次强调,这种技巧在Haskell中绝对被认为是单一的。在日常编程中,这是不编写好的,可读的,可靠的Haskell代码的方式。