功能数据结构中的简单“撤消”
Posted
技术标签:
【中文标题】功能数据结构中的简单“撤消”【英文标题】:easy "undo" in functional data structures 【发布时间】:2011-03-27 03:44:26 【问题描述】:我听说纯函数式数据结构的好处之一是您可以免费获得撤消/重做操作。有人可以解释为什么吗?我不明白为什么在函数式语言中添加撤消/重做更容易。
例如,假设我有以下队列实现:
data Queue a = Queue [a] [a]
newQueue :: Queue a
newQueue = Queue [] []
empty :: Queue a -> Bool
empty (Queue [] []) = True
empty _ = False
enqueue :: Queue a -> a -> Queue a
enqueue (Queue xs ys) y = Queue xs (y:ys)
dequeue :: Queue a -> (a, Queue a)
dequeue (Queue [] []) = error "Queue is empty!"
dequeue (Queue [] ys) = dequeue (Queue (reverse ys) [])
dequeue (Queue (x:xs) ys) = (x, Queue xs ys)
如何修改它以获取撤消和重做操作? (我可以想象 enqueue 和 dequeue 函数也返回两个列表,一个列表是队列的所有先前版本,另一个列表是队列的所有未来版本,这些列表充当我们的撤消/重做操作,但我猜这不是人们通常想到的。)
【问题讨论】:
【参考方案1】:例子:
q1 = newQueue
q2 = enque q1 3
那么q1
是q2
的撤消,因为所有值都是不可变的。只需保留对先前值的引用。
【讨论】:
正是我要指出的,以及与函数式编程相比,这是纯粹性和不变性的一个特征。 是的,我可以用多种语言做同样的事情,包括 Java。【参考方案2】:撤消/重做在纯函数代码中更容易实现的原因在于两个保证:
操作没有副作用 数据结构是不可变的只要您保持对旧数据结构的引用,您就可以随时恢复到旧数据结构。如果要存储整个历史记录,可以将其保存在一个列表中:
trackHistory :: Queue a -> [Queue a] -> [Queue a]
trackHistory currentQueue history = currentQueue : history
显然,在实际应用程序中,您希望限制历史记录大小,但您明白了。这是有效的,因为您可以保证列表中的每个队列都没有更改。
【讨论】:
另一个使撤消/重做变得便宜的功能是共享。例如多个版本的列表可以共享内存中的公共部分。 @maxtaldykin 你知道如何以纯粹的函数方式重新创建 javascript 的原型继承数据结构吗? Alessandro Warth 有一篇博士论文,它使用 JS 中的原型继承来实现作用域副作用。它本质上创建了一个不可变的树,其中每个修改都是一个新的 JS 对象,它继承自前一个对象。您可以读取所有历史记录,但任何修改都会掩盖以前的键/值属性。您最终还会共享现有数据,并仅将增量存储在新对象版本中。 @CMCDragonkai,对不起,我不知道如何回答你的问题。谢谢你指点Warth的论文,我会努力看的。 好吧,这叫世界概念。以上是关于功能数据结构中的简单“撤消”的主要内容,如果未能解决你的问题,请参考以下文章