函数式编程的缺陷/缺点


70

您什么时候不想使用函数式编程?那不是很擅长什么?

我更多地是在寻找整个范式的缺点,而不是诸如“未广泛使用”或“没有良好的调试器”之类的东西。到目前为止,这些答案可能是正确的,但是它们将FP视为一个新概念(不可避免的问题),而不是任何固有的品质。

有关:


6
1800次浏览后,“面向对象编程的陷阱”不是CW。(并不想成为粗鲁,只是比较的问题也许都应该是CW:d。)
戈登·古斯塔夫森

这具有主观标签,但是到目前为止,我所看到的答案相当客观。我可能会删除主观标签。
布莱恩(Brian)

那地方特质呢?
JD

Answers:


45

对于我来说,很难想到函数式编程的许多缺点。再说一次,我是国际函数编程会议的前任主席,因此您可以放心地认为我有偏见。

我认为主要的缺点与隔离和进入壁垒有关。学习编写好的功能程序意味着学习以不同的方式思考,要想做到这一点,需要投入大量的时间和精力。没有老师很难学习。这些属性导致一些缺点:

  • 新手编写的功能程序可能会不必要地变慢,比新手编写的C程序更可能会慢一些。另一方面,新手编写的C ++程序也同样可能会不必要地变慢。(所有这些闪亮的功能...)

    通常,专家编写快速的功能程序并不困难。实际上,现在8核和16核处理器上性能最好的并行程序都是用Haskell编写的。

  • 比起像Python或Visual Basic这样的人,开始执行函数式编程的人更有可能在实现预期的生产率增长之前就放弃了。书籍和开发工具的形式所提供的支持并不多。

  • 与之交谈的人较少。Stackoverflow是一个很好的例子。相对而言,很少有Haskell程序员会定期访问该网站(尽管部分原因是Haskell程序员拥有自己的生动活泼的论坛,比Stackoverflow年代久远,而且建立得更好)。

    确实,您不能很轻松地与邻居交谈,因为与诸如Smalltalk,Ruby和C ++之类的语言中的面向对象的概念相比,函数编程的概念更难于教导和学习。而且,面向对象的社区花了很多年的时间对其工作做很好的解释,而功能编程社区似乎认为他们的东西显然很棒,不需要任何特殊的隐喻或词汇来进行解释。(他们错了。我还在等第一本书《功能设计模式》。)

  • 惰性函数编程的一个众所周知的缺点(适用于Haskell或Clean,但不适用于ML或Scheme或Clojure)是很难预测评估惰性函数程序的时间和空间成本的,即使专家也做不到它。这个问题是范式的基础,并且不会消失。有许多出色的工具可用于事后发现时间和空间行为,但是要有效地使用它们,您必须已经成为专家。


8
“一般而言,专家编写快速功能程序并不困难;事实上,现在8核和16核处理器上性能最好的并行程序都是用Haskell编写的”。仅适用于最琐碎的问题。参见flyingfrogblog.blogspot.com/2010/06/…–
JD

5
@Jon:这完全取决于问题的确切性质以及您获得哪种缓存位置。测量(其中基准测试仅是一种类型)将显示最佳。在网页上没有意义。
Donal Fellows 2010年

1
@Donal:正如我在该网页上所说的那样,在简单的多核模型下计算算法的缓存复杂度也是最好的指标。
JD 2010年

2
我想不出更好的线索(也许一两个)来帮助感兴趣的读者就jdh意见的确切优点(或缺乏优点)得出自己的结论。
sclv 2010年

8
记录下来-我的评论是对jdh现在删除的评论的回复,jdh指出阅读reddit线程相当痛苦。我不会在这里做任何进一步的回应,因为我不喜欢jdh可以删除注释而不提供任何跟踪的事实,并且表明了他愿意这样做。
sclv 2010年

29

函数式编程的一个主要缺点是,从理论上讲,它与硬件以及大多数命令性语言都不匹配。(这是其明显的优势之一,能够表达的另一面是什么,你想要做的,而不是如何你想要的电脑做它。)

例如,函数式编程大量使用了递归。在纯lambda演算中这很好,因为数学的“堆栈”是无限的。当然,在实际硬件上,堆栈非常有限。天真的对大型数据集进行递归可以使您的程序蓬勃发展。大多数功能语言都优化了尾递归,因此不会发生这种情况,但是使算法进行尾递归可能会迫使您执行一些相当不美观的代码体操(例如,尾递归映射函数会创建向后列表或必须建立一个差异列表)列表,因此与非尾递归版本相比,它需要做更多的工作才能以正确的顺序返回到正常的映射列表。

(感谢Jared Updike提供的差异列表建议。)


9
这凸显了FP的一个有趣问题:在FP中有效编程需要您了解某些技巧-尤其是应对懒惰。在您的示例中,实际上很容易保持代码尾部递归(使用严格的左折)并避免使事情崩溃。您没有向后构建列表并反转返回列表。诀窍是使用差异列表:en.wikipedia.org/wiki/Difference_list。这些技巧很多都不容易自己弄清楚。幸运的是,Haskell社区非常友好(IRC频道,邮件列表)。
Jared Updike

4
谢谢,贾里德。好信息。不过,为了捍卫我的描述,OCaml标准库按照我所说的方式做到了这一点(堆栈受限map和尾递归rev_map)。
Chuck

23

如果您的语言没有提供很好的机制来通过程序检测状态/异常行为(例如,单子绑定的语法糖),那么任何涉及状态/异常的任务都会变得很繁琐。(即使使用这些糖,有些人可能会发现在FP中处理状态/异常更加困难。)

功能性习语通常会进行很多控制反转或延迟,这通常会对调试(使用调试器)产生负面影响。(由于不可变性/参照透明性,FP不太容易出错,这在一定程度上弥补了这一点,这意味着您将需要减少调试的频率。)


5
“不变性/参照透明性,这意味着您将需要减少调试的频率”……并且由于所有内容都是由很少的独立功能构建的,因此您可以直接对其进行测试;如果每个功能是(a)一个正确的小功能或(b)两个或更多个正确的小功能的正确组成,那该不该做!您的程序是正确的。
Jared Updike 2010年

13

菲利普·沃德勒(Philip Wadler)撰写了一篇有关此的论文(为什么没有人使用函数式编程语言),并解决了阻止人们使用FP语言的实际陷阱:

更新:具有ACM访问权限的用户无法访问的旧链接:


9
请发布文章的相关文本。:D
Gordon Gustafson

@CrazyJugglerDrummer:我认为整篇文章都与此有关;-)
Hynek -Pichi- Vychodil 09年

1
我知道,但是我宁愿能够以某种方式查看它,而无需下载和打开它。那可能吗?
Gordon Gustafson,2009年

2
抱歉,无法访问链接。我会发布HTML文本,但PS / PDF实际上是图像,并且手头没有OCR软件。我想我可以将它的PDF发布到某个地方。不确定为什么ACM隐藏了一些较旧的文章;他们不是要传播这些信息吗?
Jared Updike,

2
后记文件的在线转换器:-Dview.samurajdata.se/…
帕维尔·萨瓦拉

8

除了速度或采用方面的问题并解决一个更基本的问题外,我听说它通过函数式编程可以很容易地为现有数据类型添加新功能,但是“很难”添加新数据类型。考虑:

(用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!";

但是请注意,对于面向对象的语言,情况恰恰相反。向抽象类添加新的子类非常容易,但是如果您想为所有子类实现的抽象类/接口添加新的抽象方法,则可能很乏味。


9
如果您将这些实现为OO中抽象类的子类,则还必须编写所有这些新函数。唯一的区别是组织功能的方式(按类型或行为)。
Chuck

10
菲利普·沃德勒(Philip Wadler)将此人称为表达问题
约尔格W¯¯米塔格

3
Wadler将此称为表达问题:en.wikipedia.org/wiki/Expression_Problem
Jared Updike

1
您拥有的是代数数据类型-它们被认为是封闭的,但是可以扩展!如果要扩展性,则需要继承或类型类/存在。
Dario

1
这与函数式编程无关。标准ML,F#和Haskell受此问题困扰。Mathematica,OCaml和Clojure不是。
JD

3

我只是想讲些轶事,因为我在讲话时正在学习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回到阅读状态。


2
这些问题是由非严格评估造成的,因此它们是Haskell特有的。
JD

2

这是我遇到的一些问题:

  1. 大多数人发现函数式编程很难理解。这意味着您编写功能代码可能会更困难,而其他人几乎肯定会很难接受它。
  2. 函数式编程语言通常比像c这样的语言要慢。随着时间的流逝,这已不再是一个问题(因为计算机变得越来越快,并且编译器变得越来越聪明)
  3. 由于没有它们当之无愧的广泛传播,可能很难找到常见编程问题的库和示例。(例如,为Python找到东西几乎总是比较容易,然后为Haskell找到它)
  4. 缺少工具,尤其是用于调试的工具。它绝对不像为C#打开V​​isual Studio或为Java打开eclipse那样简单。

6
您是否有任何数字或参考资料来支持2号?同样对于数字4,F#将成为Visual Studio 2010中一类完全受支持的语言
Russ Cam

8
我认为项目符号2-4不是函数式编程所固有的,而是历史/文化/等等的更多产物。(也就是说,尽管它们可能是正确的,但我认为它们不是真实的,因为FP。)
Brian Brian

4
回复1:我认为那不是真的。Excel是一种功能性编程语言,而且我还没有发现它比C,BASIC,Pascal或Python更难理解。实际上,这可能是另一回事。
约尔格W¯¯米塔格

6
关于2:语言不能比另一种语言慢(或快)。语言只是抽象规则,您无法执行它们。只有实现可以比其他实现慢或快,但是您不再在谈论语言。最后,要解决相同的问题,您需要采取相同的步骤,因此性能将是相同的。例如,Supero Haskell编译器生成的代码比由GCC编译的手动优化的C代码运行速度快10%。实施良好的Scheme编译器所生成的代码速度是GCC的一半至两倍。
约尔格W¯¯米塔格

3
关于4:我敢肯定,任何在1990年代使用过Lisp Machine IDE的人都会对20年后的Eclipse和Visual Studio仍然如此糟糕感到惊讶。无论如何,这与函数式编程无关。Visual Studio的出色程度是Visual Studio的功能,而不是命令式编程。实际上,F#Visual Studio插件具有与C#和VB.NET插件几乎完全相同的功能。在缺少功能的情况下,它与功能编程无关,并且与Microsoft为F#v C#分配的资金数​​量无关。
约尔格W¯¯米塔格

0

抛开函数式编程的特定实现细节,我看到了两个关键问题:

  1. 相对于当务之急,选择一些实际问题的功能模型似乎并不现实。当问题领域势在必行时,使用具有该特征的语言是一种自然而合理的选择(因为通常建议将规范和实现之间的距离最小化,以减少精妙的bug数量)。是的,可以通过一个足够智能的编码器来解决,但是如果您需要Rock Star Coders来完成任务,那是因为它太血腥了。

  2. 由于我从未真正理解的某些原因,函数式编程语言(或者也许是其实现或社区?)更可能希望使用其语言来包含所有内容。用其他语言编写的库的使用要少得多。如果其他人对某个复杂的操作有一个特别好的实现,那么使用它而不是自己创建则更有意义。我怀疑这部分是由于使用复杂的运行时而造成的,这使得处理外来代码(尤其是有效地执行)变得相当困难。在这一点上,我很想证明自己是错误的。

我想这两者都回到了普遍缺乏实用主义的境地,这是由于编程研究人员比普通编码人员更广泛地使用函数式编程。好的工具可以使专家做伟大的事情,但是好的工具是使普通人能够执行专家通常可以做的事情,因为到目前为止,这是更加艰巨的任务。


2
还要注意的是,无论常规地如何教授,许多语言都不是纯命令式的或纯函数式的。有相当大的交叉受精。
多纳研究员2010年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.