功能编程的缺陷/缺点

时间:2009-11-24 00:08:03

标签: functional-programming paradigms

您何时不想使用函数式编程?什么不太擅长?

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

相关:

9 个答案:

答案 0 :(得分:44)

我很难想到函数式编程的许多缺点。再说一遍,我是国际功能编程大会的前任主席,所以你可以放心地认为我有偏见。

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

  • 新人写的功能程序很可能会不必要地慢 - 比起C的新人编写的C程序更可能。另一方面,它几乎同样可能是由新手编写的C ++程序将不必要地慢。 (所有这些闪亮的功能......)

    一般来说,专家写快速功能程序没有困难;事实上,8核和16核处理器上的一些性能最佳的并行程序现在用Haskell编写。

  • 开始函数式编程的人更有可能在实现承诺的生产力提升之前放弃,而不是像Python或Visual Basic那样开始。书籍和开发工具的形式并没有那么多的支持。

  • 与人交谈的人数较少。 Stackoverflow就是一个很好的例子;相对较少的Haskell程序员经常访问该站点(尽管部分原因是Haskell程序员拥有自己的生动论坛,这些论坛比Stackoverflow更老,更成熟)。

    你也不能很容易地与你的邻居交谈,因为功能编程概念比Smalltalk,Ruby和C ++等语言背后的面向对象概念更难教,更难学。而且,面向对象的社区花了数年时间为他们所做的事情开发了很好的解释,而功能编程社区似乎认为他们的东西显然是伟大的,并且不需要任何特殊的隐喻或词汇来解释。 (他们错了。我还在等着第一本伟大的书功能设计模式。)

  • 众所周知的 lazy 函数编程的缺点(适用于Haskell或Clean但不适用于ML或Scheme或Clojure)是很难预测时间评估 lazy 功能程序的空间成本 - 即使是专家也无法做到。这个问题是范式的基础,并没有消失。有很好的工具可以发现时间和空间行为 post facto ,但要有效地使用它们,你必须已经是专家。

答案 1 :(得分:30)

我认为围绕函数式语言的废话是函数式编程的最大问题。当我开始在愤怒中使用函数式编程时,对我来说一个很大的障碍是理解为什么Lisp社区提出的许多高度进化的论点(例如关于宏和同音语法)是错误的。今天,我看到很多人在并行编程方面受到Haskell社区的欺骗。

事实上,除了这个线程之外,你不必再看一些了:

  

“通常专家编写快速功能程序没有困难;事实上,8核和16核处理器上的一些性能最佳的并行程序现在都是用Haskell编写的。”

这样的陈述可能会给你一种专家选择Haskell的印象,因为它对于并行性来说非常好,但事实是Haskell的性能糟透,并且Haskell对多核并行性有好处的神话由Haskell研究人员延续,几乎没有关于并行性的知识,他们只能在自己集团控制下的期刊和会议的舒适区内发布,以避免真正的同行评审。 Haskell在现实世界的并行/多核/ HPC中是不可见的,因为它很难用于并行编程。

具体而言,多核编程中的真正挑战是利用CPU缓存来确保内核不会缺少数据,这一问题在Haskell环境中从未得到解决。麻省理工学院的Charles Leiserson小组在使用他们自己的Cilk语言解释和解决这个问题方面做得非常出色,Cilk语言继续成为英特尔TBB和微软TPL in .NET 4中多核实际并行编程的支柱。在2008年论文The cache complexity of multithreaded cache oblivious algorithms中,如何使用这种技术编写优雅的高级命令式代码,这些代码可编译为可扩展的高性能代码。我在一些最先进的Parallel Haskell研究的my review中对此进行了解释。

这给纯函数式编程范式留下了很大的疑问。这是你为抽象时间和空间所付出的代价,这一直是这种陈述范式背后的主要动机。

编辑:Texas Multicore Technologies have also recently found Haskell to be underwhelming in the context of multicore parallelism

答案 2 :(得分:29)

函数式编程的一大缺点是,在理论层面上,它与硬件以及大多数命令式语言都不匹配。 (这是其明显优势之一的另一面,能够表达你想要做什么而不是你希望计算机如何。)

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

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

答案 3 :(得分:23)

如果您的语言没有提供通过程序检测状态/异常行为的良好机制(例如,monadic绑定的语法糖),那么任何涉及状态/异常的任务都会成为一件苦差事。 (即使有这些糖,有些人可能会发现在FP中处理状态/异常更难。)

功能习语通常会进行大量的控制反转或懒惰,这通常会对调试产生负面影响(使用调试器)。 (由于不变性/引用透明性,FP在某种程度上抵消了由于不变性/引用透明度导致的错误更少的错误,这意味着您需要更少地进行调试。)

答案 4 :(得分:13)

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

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

答案 5 :(得分: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!";

但请注意,面向对象语言恰恰相反。向抽象类添加一个新的子类非常容易,但是如果你想为抽象类/接口添加一个新的抽象方法来实现所有子类,那就很乏味了。

答案 6 :(得分:3)

我只是想用轶事嗡嗡作响,因为我正在学习Haskell。我正在学习Haskell,因为将函数与动作分开的想法对我很有吸引力,并且由于纯函数与非纯函数的隔离,隐式并行化背后有一些非常性感的理论。

我已经学习了三天的折叠功能。折叠似乎有一个非常简单的应用程序:获取列表并将其减少到单个值。 Haskell实现了foldlfoldr。这两个函数具有大量不同的实现。 foldl的替代实现称为foldl'。除此之外,版本的语法略有不同,称为foldr1foldl1,其初始值不同。其中foldl1'的{​​{1}}对应实现。好像所有这些都不是令人兴奋的,foldl1需要作为参数并在缩减中内部使用的函数有两个单独的签名,只有一个变体在无限列表(r)上工作,其中只有一个是在常量内存中执行(据我所知(L),因为它只需要fold[lr].*)。理解为什么redex可以在无限列表上工作,至少需要对语言懒惰行为以及不是所有函数都会强制评估第二个参数的次要细节有一个正确的理解。这些功能的在线图表令人困惑,因为对于那些从未在大学里见过他们的人而言。没有foldr等价物。我找不到Haskell前奏中任何函数的单一描述。前奏是一种预装的分布式核心。我最好的资源实际上是一个我从未见过的人(Cale),他在自己的时间里以巨大的代价帮助我。

哦,并且fold不必将列表缩减为非列表类型标量,列表的标识函数可以写成perldoc(可以累积到列表的高亮显示)。

/我回去读书。

答案 7 :(得分:2)

以下是我遇到的一些问题:

  1. 大多数人发现函数式编程难以理解。这意味着你编写功能代码可能会更难,而且其他人几乎肯定会更难找到它。
  2. 函数式编程语言通常比c语言慢。随着时间的推移,这变得越来越不成问题(因为计算机变得越来越快,编译器越来越聪明)。
  3. 没有像命令式对手那样广泛传播,很难找到常见编程问题的库和示例。 (例如,它几乎总是更容易找到Python的东西,然后是Haskell)
  4. 缺少工具,尤其是调试工具。它绝对不像打开Visual Studio for C#或eclipse for Java那么容易。

答案 8 :(得分:0)

远离功能编程的具体实现细节,我看到两个关键问题:

  1. 选择一个现实世界问题的功能模型而不是命令式问题似乎相对罕见。当问题域必不可少时,使用具有该特征的语言是一种自然而合理的选择(因为通常建议最小化规范与实现之间的距离,以减少细微错误的数量)。是的,这可以通过一个足够聪明的编码器来克服,但是如果你需要Rock Star Coders来完成任务,那是因为它太血腥了。

  2. 由于某些我从未真正理解的原因,函数式编程语言(或者他们的实现或社区?)更有可能想要用他们的语言拥有一切。使用其他语言编写的库的用处要少得多。如果其他人对某些复杂的操作有特别好的实现,那么使用它而不是制作自己的更有意义。我怀疑这部分是由于使用复杂的运行时而导致处理外部代码(尤其是有效地执行它)相当困难。在这一点上,我很想被证明是错的。

  3. 我认为这些都回归到普遍缺乏实用主义,因为编程研究人员比普通编码人员更强烈地使用函数式编程。一个好的工具可以让专家做出伟大的事情,但是一个伟大的工具可以使普通人接近专家可以正常做的事情,因为这是迄今为止更困难的任务。