有没有人知道在纯函数编程时可能发生的最坏的渐近减速是什么,而不是强制性的(即允许副作用)?
来自itowlson评论的澄清:是否有任何问题,其中最着名的非破坏性算法渐渐比最着名的破坏性算法更糟糕,如果是这样的话多少?
答案 0 :(得分:525)
根据Pippenger [1996],将纯函数的Lisp系统(并且具有严格的评估语义,而不是懒惰)与可以改变数据的系统进行比较时,为在O中运行的不纯Lisp编写的算法(< em> n )可以转换为在O( n log n )时间运行的纯Lisp中的算法(基于{{3的工作)关于仅使用指针模拟随机存取存储器的方法。 Pippenger还确定有一些算法可以做到最好;不纯系统中存在O( n )的问题,纯系统中的Ω( n log n )。
关于这篇论文,有几点需要注意。最重要的是它没有解决惰性函数语言,例如Haskell。 Ben-Amram and Galil [1992]证明Pippenger构造的问题可以在O( n )时间内以惰性函数语言解决,但它们没有建立(据我所知,没有人有)一个懒惰的函数式语言是否可以解决与具有变异的语言相同的渐近运行时间内的所有问题。
Pippenger构造的问题要求Ω( n log n )是专门为实现这一结果而构建的,并不一定代表实际的现实问题。这个问题有一些限制意外,但证明工作是必要的;特别是,问题要求结果是在线计算的,不能访问未来的输入,并且输入由来自无限可能原子集的原子序列组成,而不是固定大小集。并且本文仅针对线性运行时间的不纯算法建立(下界)结果;对于需要更长运行时间的问题,在线性问题中看到的额外O(log n )因子可能能够在算法所需的额外操作过程中被“吸收”运行时间更长。 Bird, Jones and De Moor [1997]简要探讨了这些澄清和开放式问题。
在实践中,许多算法可以用纯函数语言实现,效率与具有可变数据结构的语言相同。有关用于有效实现纯功能数据结构的技术的良好参考,请参阅Ben-Amram [1996](这是他的论文Chris Okasaki's "Purely Functional Data Structures" [Okasaki 1998]的扩展版本)。
任何需要在纯功能数据结构上实现算法的人都应该阅读Okasaki。通过使用平衡二叉树模拟可变内存,每次操作总是可以减少O(log n )减速,但在许多情况下,你可以做得更好,Okasaki描述了许多有用的技术,从摊销技术到逐步进行摊销工作的实时技术。纯功能数据结构可能有点难以使用和分析,但它们提供了许多好处,如参考透明性,有助于编译器优化,并行和分布式计算,以及版本控制,撤消和回滚等功能的实现。 / p>
另请注意,所有这些都只讨论了渐近运行时间。许多用于实现纯功能数据结构的技术会为您提供一定量的常数因子减速,这是因为它们需要额外的簿记工作,以及相关语言的实现细节。纯功能数据结构的好处可能超过这些常数因素减速,因此您通常需要根据相关问题进行权衡。
答案 1 :(得分:44)
确实存在几种算法和数据结构,其中没有渐近有效的纯函数解(t.i.可在纯lambda演算中实现),即使是懒惰也是如此。
然而,我们假设在“命令式”语言中,对内存的访问是O(1),而理论上不能如此渐近(即对于无界问题大小)和对大型数据集中的内存的访问总是O( log n),可以用函数式语言模拟。
另外,我们必须记住,实际上所有现代函数式语言都提供了可变数据,而Haskell甚至提供了它而不牺牲纯度(ST monad)。
答案 2 :(得分:34)
This article声称the union-find algorithm的已知纯功能实现都比他们发布的实现具有更低的渐近复杂度,它具有纯粹的功能接口,但在内部使用可变数据。
事实上,其他答案声称永远不会有任何差异,例如,纯功能代码的唯一“缺点”是它可以并行化,让您了解功能编程社区的知情/客观性在这些问题上。
编辑:
下面的评论指出,对纯函数式编程的利弊的偏见讨论可能不是来自“函数式编程社区”。好点子。也许我认为的倡导者只是引用一个评论,“文盲”。
例如,我认为这个blog post是由可以说是功能编程社区代表的人编写的,因为它是“懒惰评估点”的列表,所以它会很好提及懒惰和纯功能编程可能具有的任何缺点。一个好地方本来可以代替以下(技术上是正确的,但偏向于不好笑)解雇:
如果严格的函数在严格语言中具有O(f(n))复杂度,那么它在惰性语言中也具有复杂度O(f(n))。为什么要担心? :)
答案 3 :(得分:4)
对内存使用情况有一个固定的上限,应该没有区别。
证明草图: 给定内存使用的固定上限,应该能够编写一个虚拟机,该虚拟机执行具有相同渐近复杂度的命令式指令集,就像您实际在该机器上执行一样。这是因为您可以将可变内存作为持久数据结构进行管理,给予O(log(n))读取和写入,但是对于内存使用量具有固定的上限,您可以拥有固定数量的内存,从而导致衰变为O(1)。因此,功能实现可以是在VM的功能实现中运行的命令性版本,因此它们应该具有相同的渐近复杂度。
答案 4 :(得分:1)
我建议您阅读performance of Haskell,然后查看功能语言与程序/ OO表演的benchmarks game表演。