您什么时候不想使用函数式编程?那不是很擅长什么?
我更多地是在寻找整个范式的缺点,而不是诸如“未广泛使用”或“没有良好的调试器”之类的东西。到目前为止,这些答案可能是正确的,但是它们将FP视为一个新概念(不可避免的问题),而不是任何固有的品质。
有关:
您什么时候不想使用函数式编程?那不是很擅长什么?
我更多地是在寻找整个范式的缺点,而不是诸如“未广泛使用”或“没有良好的调试器”之类的东西。到目前为止,这些答案可能是正确的,但是它们将FP视为一个新概念(不可避免的问题),而不是任何固有的品质。
有关:
Answers:
对于我来说,很难想到函数式编程的许多缺点。再说一次,我是国际函数编程会议的前任主席,因此您可以放心地认为我有偏见。
我认为主要的缺点与隔离和进入壁垒有关。学习编写好的功能程序意味着学习以不同的方式思考,要想做到这一点,需要投入大量的时间和精力。没有老师很难学习。这些属性导致一些缺点:
新手编写的功能程序可能会不必要地变慢,比新手编写的C程序更可能会慢一些。另一方面,新手编写的C ++程序也同样可能会不必要地变慢。(所有这些闪亮的功能...)
通常,专家编写快速的功能程序并不困难。实际上,现在8核和16核处理器上性能最好的并行程序都是用Haskell编写的。
比起像Python或Visual Basic这样的人,开始执行函数式编程的人更有可能在实现预期的生产率增长之前就放弃了。书籍和开发工具的形式所提供的支持并不多。
与之交谈的人较少。Stackoverflow是一个很好的例子。相对而言,很少有Haskell程序员会定期访问该网站(尽管部分原因是Haskell程序员拥有自己的生动活泼的论坛,比Stackoverflow年代久远,而且建立得更好)。
确实,您不能很轻松地与邻居交谈,因为与诸如Smalltalk,Ruby和C ++之类的语言中的面向对象的概念相比,函数编程的概念更难于教导和学习。而且,面向对象的社区花了很多年的时间对其工作做很好的解释,而功能编程社区似乎认为他们的东西显然很棒,不需要任何特殊的隐喻或词汇来进行解释。(他们错了。我还在等第一本书《功能设计模式》。)
惰性函数编程的一个众所周知的缺点(适用于Haskell或Clean,但不适用于ML或Scheme或Clojure)是很难预测评估惰性函数程序的时间和空间成本的,即使专家也做不到它。这个问题是范式的基础,并且不会消失。有许多出色的工具可用于事后发现时间和空间行为,但是要有效地使用它们,您必须已经成为专家。
函数式编程的一个主要缺点是,从理论上讲,它与硬件以及大多数命令性语言都不匹配。(这是其明显的优势之一,能够表达的另一面是什么,你想要做的,而不是如何你想要的电脑做它。)
例如,函数式编程大量使用了递归。在纯lambda演算中这很好,因为数学的“堆栈”是无限的。当然,在实际硬件上,堆栈非常有限。天真的对大型数据集进行递归可以使您的程序蓬勃发展。大多数功能语言都优化了尾递归,因此不会发生这种情况,但是使算法进行尾递归可能会迫使您执行一些相当不美观的代码体操(例如,尾递归映射函数会创建向后列表或必须建立一个差异列表)列表,因此与非尾递归版本相比,它需要做更多的工作才能以正确的顺序返回到正常的映射列表。
(感谢Jared Updike提供的差异列表建议。)
map
和尾递归rev_map
)。
如果您的语言没有提供很好的机制来通过程序检测状态/异常行为(例如,单子绑定的语法糖),那么任何涉及状态/异常的任务都会变得很繁琐。(即使使用这些糖,有些人可能会发现在FP中处理状态/异常更加困难。)
功能性习语通常会进行很多控制反转或延迟,这通常会对调试(使用调试器)产生负面影响。(由于不可变性/参照透明性,FP不太容易出错,这在一定程度上弥补了这一点,这意味着您将需要减少调试的频率。)
菲利普·沃德勒(Philip Wadler)撰写了一篇有关此的论文(为什么没有人使用函数式编程语言),并解决了阻止人们使用FP语言的实际陷阱:
更新:具有ACM访问权限的用户无法访问的旧链接:
除了速度或采用方面的问题并解决一个更基本的问题外,我听说它通过函数式编程可以很容易地为现有数据类型添加新功能,但是“很难”添加新数据类型。考虑:
(用SMLnj编写。另外,请原谅一些虚构的示例。)
datatype Animal = Dog | Cat;
fun happyNoise(Dog) = "pant pant"
| happyNoise(Cat) = "purrrr";
fun excitedNoise(Dog) = "bark!"
| excitedNoise(Cat) = "meow!";
我可以很快添加以下内容:
fun angryNoise(Dog) = "grrrrrr"
| angryNoise(Cat) = "hisssss";
但是,如果我向Animal添加新类型,则必须遍历每个函数以添加对它的支持:
datatype Animal = Dog | Cat | Chicken;
fun happyNoise(Dog) = "pant pant"
| happyNoise(Cat) = "purrrr"
| happyNoise(Chicken) = "cluck cluck";
fun excitedNoise(Dog) = "bark!"
| excitedNoise(Cat) = "meow!"
| excitedNoise(Chicken) = "cock-a-doodle-doo!";
fun angryNoise(Dog) = "grrrrrr"
| angryNoise(Cat) = "hisssss"
| angryNoise(Chicken) = "squaaaawk!";
但是请注意,对于面向对象的语言,情况恰恰相反。向抽象类添加新的子类非常容易,但是如果您想为所有子类实现的抽象类/接口添加新的抽象方法,则可能很乏味。
我只是想讲些轶事,因为我在讲话时正在学习Haskell。我正在学习Haskell,是因为将功能与动作分离的想法吸引了我,并且由于纯功能与非纯功能的隔离,隐式并行化背后有一些非常性感的理论。
我已经学习折叠函数类三天了。Fold似乎有一个非常简单的应用程序:获取列表并将其减少为单个值。哈斯克尔实现foldl
,而foldr
这一点。这两个功能的实现方式截然不同。有一个替代实现foldl
,称为foldl'
。最重要的是,版本的语法稍有不同,foldr1
并且foldl1
具有不同的初始值。其中有一个foldl1'
for的对应实现foldl1
。似乎所有这些都不是让您烦恼的功能fold[lr].*
require作为参数并在内部使用于约简中有两个单独的签名,只有一个变体在无限列表(r)上起作用,并且其中只有一个在常量内存中执行(据我所知(L),因为它只需要一个redex
)。要理解为什么foldr
可以在无限列表上工作,至少需要对惰性语言的行为有一个体面的理解,并且需要了解次要的细节,即并非所有函数都会强制第二个参数的求值。对于从未上过大学的人来说,这些功能的在线图形令人困惑。没有perldoc
当量。对于Haskell前奏中的任何功能,我找不到任何描述。序言是一种随核心一起预加载的发行版。我最好的资源确实是一个我从未见过的人(Cale),他用大量的时间来帮助我。
哦,而且fold不必将列表缩小为非列表类型标量,因此可以编写列表的标识函数foldr (:) [] [1,2,3,4]
(可以累积到列表的突出显示)。
/ me回到阅读状态。
这是我遇到的一些问题:
抛开函数式编程的特定实现细节,我看到了两个关键问题:
相对于当务之急,选择一些实际问题的功能模型似乎并不现实。当问题领域势在必行时,使用具有该特征的语言是一种自然而合理的选择(因为通常建议将规范和实现之间的距离最小化,以减少精妙的bug数量)。是的,可以通过一个足够智能的编码器来解决,但是如果您需要Rock Star Coders来完成任务,那是因为它太血腥了。
由于我从未真正理解的某些原因,函数式编程语言(或者也许是其实现或社区?)更可能希望使用其语言来包含所有内容。用其他语言编写的库的使用要少得多。如果其他人对某个复杂的操作有一个特别好的实现,那么使用它而不是自己创建则更有意义。我怀疑这部分是由于使用复杂的运行时而造成的,这使得处理外来代码(尤其是有效地执行)变得相当困难。在这一点上,我很想证明自己是错误的。
我想这两者都回到了普遍缺乏实用主义的境地,这是由于编程研究人员比普通编码人员更广泛地使用函数式编程。好的工具可以使专家做伟大的事情,但是好的工具是使普通人能够执行专家通常可以做的事情,因为到目前为止,这是更加艰巨的任务。