有谁知道纯功能编程而不是强制性编程(即允许副作用)发生时,最糟糕的渐近减慢可能是什么?
从itowlson的评论中澄清:是否存在最知名的无损算法比最知名的破坏性算法渐近恶化的问题?
有谁知道纯功能编程而不是强制性编程(即允许副作用)发生时,最糟糕的渐近减慢可能是什么?
从itowlson的评论中澄清:是否存在最知名的无损算法比最知名的破坏性算法渐近恶化的问题?
Answers:
根据Pippenger [1996]的研究,当将纯功能的Lisp系统(具有严格的评估语义,而不是惰性的)与可以变异数据的Lisp系统进行比较时,可以翻译为在O(n)中运行的不纯Lisp编写的算法。转换为纯Lisp中运行时间为O(n log n)的算法(基于Ben-Amram和Galil [1992]关于仅使用指针模拟随机存取存储器的工作)。皮蓬格(Pippenger)还确定,有些算法是您可以做的最好的事情。在不纯系统中存在O(n)在纯系统中存在Ω(n log n)的问题。
关于本文有一些警告。最重要的是,它不解决惰性函数语言,例如Haskell。Bird,Jones和De Moor [1997]证明由Pippenger构造的问题可以在O(n)时间内以一种惰性函数语言解决,但它们并没有确定(据我所知,没有人知道)是否没有一种懒惰的功能语言可以像变种语言一样在相同的渐近运行时间内解决所有问题。
由皮蓬格(Pippenger)构造的问题需要专门构造Ω(n log n)才能获得此结果,并且不一定代表实际的实际问题。对这个问题有一些限制,这是出乎意料的,但是对于证明工作是必不可少的。特别地,该问题要求结果是在线计算的,而无法访问将来的输入,并且该输入由来自无限可能原子集合的原子序列组成,而不是固定大小的集合。并且本文仅针对线性运行时间的不纯算法建立(下界)结果。对于需要更长运行时间的问题,可能会额外增加O(log n线性问题中出现的因素可能在具有更长运行时间的算法所需的额外操作过程中被“吸收”。Ben-Amram [1996]简要地探讨了这些澄清和开放性问题。
实际上,可以用纯功能语言以与具有可变数据结构的语言相同的效率来实现许多算法。有关有效用于实现纯功能数据结构的技术的良好参考,请参见Chris Okasaki的“纯功能数据结构” [Okasaki 1998](这是其论文的扩展版本[Okasaki 1996])。
任何需要在纯功能数据结构上实现算法的人都应该阅读Okasaki。通过用平衡的二叉树模拟可变内存,您每次操作总是最糟糕的情况是O(log n)变慢,但在许多情况下,您可以做得更好,而且Okasaki描述了许多有用的技术,从摊销技术到实物化,逐步进行摊销的时间。纯功能数据结构可能很难使用和分析,但是它们提供了很多好处,例如引用透明性,有助于编译器优化,并行和分布式计算以及实现诸如版本控制,撤消和回滚之类的功能。
还要注意,所有这些仅讨论渐近的运行时间。许多实现纯功能数据结构的技术会给您一定程度的恒定因子减慢,这是因为它们需要额外的簿记工作以及所涉及语言的实现细节。纯功能数据结构的好处可能胜过这些恒定因素的减速,因此您通常需要根据相关问题进行权衡。
确实有几种算法和数据结构,即使是惰性的,也没有已知的渐近有效的纯函数解(在纯lambda演算中可以实现)。
但是,我们假设在“命令式”语言中,对内存的访问为O(1),而在理论上不能如此渐近(即对于无限制的问题大小),并且对庞大数据集中的内存的访问始终为O(log n) ,可以使用功能语言进行仿真。
同样,我们必须记住,实际上所有现代功能语言都提供可变数据,Haskell甚至在不牺牲纯度的情况下提供了数据(ST monad)。
本文声称,联合查找算法的已知纯功能实现都比它们发布的算法具有更差的渐进复杂度,后者具有纯功能接口,但内部使用可变数据。
其他答案声称永远没有任何区别,例如,纯函数代码的唯一“缺点”是它可以并行化,这一事实使您对函数式编程社区在这些问题上的了解程度/客观性有所了解。
编辑:
下面的评论指出,对纯函数式编程的优缺点的偏颇的讨论可能不会来自“函数式编程社区”。好点子。也许我看到的倡导者只是在评论中说是“文盲”。
例如,我认为此博客文章是由可以说是功能编程社区代表的人撰写的,并且由于它是“懒惰评估的要点”列表,因此它是提及任何缺点的好地方懒惰和纯函数式编程可能具有。解雇以下人员是一个不错的选择(从技术上讲是正确的,但偏向于不搞笑):
如果严格函数在严格语言中具有O(f(n))复杂度,那么在懒惰语言中函数也具有O(f(n))复杂度。为什么要担心?:)
在固定的内存使用上限上,应该没有区别。
证明草图:给定固定的内存使用上限,一个人应该能够编写一台虚拟机,该虚拟机以与您实际在该计算机上执行相同的渐进复杂度执行命令式指令集。之所以这样,是因为您可以将可变内存作为持久性数据结构进行管理,从而实现O(log(n))的读写,但是由于内存使用量的上限是固定的,因此您可以拥有固定的内存量,从而导致这些衰减到O(1)。因此,功能实现可以是在VM的功能实现中运行的命令式版本,因此它们都应具有相同的渐近复杂度。
我建议阅读Haskell的性能,然后看一下功能语言与程序/ OO语言的基准游戏性能。