功能数据结构中的简单“撤消”

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

那么q1q2 的撤消,因为所有值都是不可变的。只需保留对先前值的引用。

【讨论】:

正是我要指出的,以及与函数式编程相比,这是纯粹性和不变性的一个特征。 是的,我可以用多种语言做同样的事情,包括 Java。【参考方案2】:

撤消/重做在纯函数代码中更容易实现的原因在于两个保证:

操作没有副作用 数据结构是不可变的

只要您保持对旧数据结构的引用,您就可以随时恢复到旧数据结构。如果要存储整个历史记录,可以将其保存在一个列表中:

trackHistory :: Queue a -> [Queue a] -> [Queue a]
trackHistory currentQueue history = currentQueue : history

显然,在实际应用程序中,您希望限制历史记录大小,但您明白了。这是有效的,因为您可以保证列表中的每个队列都没有更改。

【讨论】:

另一个使撤消/重做变得便宜的功能是共享。例如多个版本的列表可以共享内存中的公共部分。 @maxtaldykin 你知道如何以纯粹的函数方式重新创建 javascript 的原型继承数据结构吗? Alessandro Warth 有一篇博士论文,它使用 JS 中的原型继承来实现作用域副作用。它本质上创建了一个不可变的树,其中每个修改都是一个新的 JS 对象,它继承自前一个对象。您可以读取所有历史记录,但任何修改都会掩盖以前的键/值属性。您最终还会共享现有数据,并仅将增量存储在新对象版本中。 @CMCDragonkai,对不起,我不知道如何回答你的问题。谢谢你指点Warth的论文,我会努力看的。 好吧,这叫世界概念。

以上是关于功能数据结构中的简单“撤消”的主要内容,如果未能解决你的问题,请参考以下文章

如何撤消emacs中的填充段落?

如何撤消 phpmyadmin 中的查询? [复制]

如何撤消 git 中的最后一次提交,但保持我的更改未暂存?

需要帮助为文本编辑器程序编写撤消/重做功能

从公共架构中撤消特权“创建表”,但不是从定制架构中撤消

我想在android应用程序中提供撤消功能[关闭]