在Racket或SML等函数式语言中,我们通常在递归调用中执行列表操作(模式匹配,列表追加,列表连接......)。但是,我不确定这些操作在函数式语言中的一般实现。创建,更新或删除列表中的元素等操作是否会返回列表的全新副本?我曾在一本书中读到一个关于函数式编程缺点的例子;也就是说,每次更新数据库时,都会返回一个全新的数据库副本。
我质疑这个例子,因为FP中的数据本质上是不可变的,因此现有列表中的创建列表不应该创建一个全新的副本。相反,根据过滤条件,新列表只是对其他列表中现有对象的不同引用集合。
例如,列出A = [a,b,c]
和列表B=[1,2,3]
,我创建了一个新列表,其中包含现有列表中的前两个元素,即C=[a,b,1,2]
。此新列表仅包含来自a,b,
的{{1}}和来自A
的{{1}}的引用。它不应该是新副本,因为数据是不可变的。
因此,要更新列表中的元素,它应该只花费线性时间在列表中查找元素,创建新值并创建一个新列表,其中包含与旧列表中相同的元素,但更新后的元素除外。要创建新列表,运行环境仅更新前一个元素的下一个指针。如果列表中包含非原子元素(即list,tree ...),并且其中一个非原子元素中只有一个原子元素被更新,则该过程将递归地应用于非原子元素,直到原子元素为止。如上所述更新。 应该如何实施?
如果有人在每次从现有列表创建列表/添加/更新/删除/使用元素时创建列表的完整深层副本,那么他们做错了,不是吗?
另一件事是,当程序环境更新时(即为新变量添加新的键/值条目,所以我们稍后可以引用它),它不违反函数式编程的不可变属性是吗?
答案 0 :(得分:2)
你是完全正确的!具有不可变数据的FP语言将从不执行深层复制(除非它们实现得很差)。由于数据 不可变,因此重用它绝不会有任何问题。它与所有其他结构的工作方式完全相同。因此,例如,如果您正在使用树结构,那么最多只会复制实际的树,而不会复制其中包含的数据。
因此,虽然复制听起来非常昂贵,但是如果你来自命令式/ OO背景(你真的必须复制,因为你有可变数据),它比你想象的要少得多。拥有不可变数据有很多好处。