功能编程的缺陷/缺点

Gor*_*son 68 paradigms functional-programming

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

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

有关:

Nor*_*sey 46

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

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

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

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

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

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

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

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

  • "一般来说,专家编写快速功能程序没有任何困难;实际上,8核和16核处理器上的一些性能最佳的并行程序现在都是用Haskell编写的".只针对最琐碎的问题.见http://flyingfrogblog.blogspot.com/2010/06/regular-shape-polymorphic-parallel.html (8认同)
  • 为了记录 - 我的评论是回复jdh现在删除的评论指向一个相当痛苦的阅读reddit线程.我不会在这里做出任何回应,因为我不喜欢jdh可以在不提供任何痕迹的情况下删除注释的事实,并且已表明他愿意这样做. (7认同)
  • @Jon:这完全取决于问题的确切性质以及您获得的缓存区域.测量(其中基准测试只有一种类型)将显示哪种是最佳的; 在网页上进行辩护不会. (5认同)
  • 我想不出一个更好的线索(好吧,一两个可能)来帮助感兴趣的读者就jdh的意见的确切优点(或缺乏)得出他们自己的结论. (2认同)

Jon*_*rop 30

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

实际上,您不必再看这个线程就可以看到其中的一些:

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

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

具体而言,多核编程中的真正挑战是利用CPU缓存来确保内核不会缺乏数据,这一问题在Haskell环境中从未得到解决.麻省理工学院的Charles Leiserson团队使用他们自己的Cilk语言很好地解释和解决了这个问题,Cilk语言继续成为英特尔TBB和微软TPL in .NET 4中多核实际并行编程的支柱.关于如何使用这种技术编写优雅的高级命令式代码的精湛描述,这些代码编译成2008年论文中可扩展的高性能代码多线程缓存遗忘算法的缓存复杂性.我在审查中解释了这一点 一些最先进的并行Haskell研究.

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

编辑:德克萨斯多核技术公司最近也发现Haskell在多核并行的背景下没有给人留下深刻印象.

  • 这似乎不是对OP问题的回答,而是对Haskell的咆哮. (8认同)
  • Parallelism/= HPC (6认同)
  • 德克萨斯多核正在宣传他们自己的技术,反对使用列表写的matrixmult的蹩脚实现(我告诉你的列表!).一个快速的谷歌揭示了从基准套件中为并行haskell实现绘制的代码.将其视为惯用的或"快速的"或"好的"实现而不是编译器本身的可能低效的基准测试是类别错误. (4认同)
  • 当然,您可以将我提供的任何示例视为一个从未打算提高效率的特殊情况,但最终,Norman需要提供这些"8-和16-表现最佳的并行程序"的证据. "用Haskell编写的核心". (4认同)
  • 我们没有沟通.套件中的基准程序*不会被编写为高性能*.实际上,那些不具备高效性的程序有时比基于程序的程序更好,因为它们表现出更多类型的退化模式,这些模式提供了优化的机会. (2认同)

Chu*_*uck 29

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

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

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

  • 这突出了FP的一个有趣问题:在FP中有效编程需要你知道某些技巧 - 特别是处理懒惰.在您的示例中,实际上很容易使代码尾部递归(使用严格的左侧折叠)并避免让事情爆炸.您没有向后构建列表并反转返回列表.诀窍是使用差异列表:http://en.wikipedia.org/wiki/Difference_list.很多这些技巧都不是那么容易理解的.幸运的是,Haskell社区非常友好(IRC频道,邮件列表). (9认同)
  • 谢谢,贾里德.好消息.但是,为了保护我的描述,OCaml标准库就像我说的那样(stack-limited`map`和tail-recursive`rev_map`). (4认同)

Bri*_*ian 23

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

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

  • "不变性/参照透明度,这意味着你需要不那么频繁地调试"......并且因为一切都是由很少的独立功能构建的,你可以直接测试它们; 如果每个函数是(a)一个正确的小函数或(b)两个或多个正确的小函数的正确组合,那么!你的程序是正确的. (5认同)

Jar*_*ike 13

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

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

  • 请发表文章的相关文字.:d (8认同)
  • 对于无法访问的链接感到抱歉.我会发布文本的HTML,但PS/PDF实际上是一个图像,我手边没有OCR软件.我想我可以在某处发布PDF文件.不确定为什么ACM会隐藏其中一些旧文章; 他们不想传播这些信息. (2认同)
  • postscript文件的在线转换器:-D http://view.samurajdata.se/ps.php?url=http%3A%2F%2Fwww.cse.iitb.ac.in%2F~as%2Ffpcourse%2Fsigplan-why. ps.gz&提交=查看! (2认同)

Ben*_*ell 8

除了速度或采用问题以及解决更基本的问题之外,我听说它通过函数式编程实现,为现有数据类型添加新函数非常容易,但添加新数据类型"很难".考虑:

(写在SMLnj.另外,请原谅有些人为的例子.)

datatype Animal = Dog | Cat;

fun happyNoise(Dog) = "pant pant"
  | happyNoise(Cat) = "purrrr";

fun excitedNoise(Dog) = "bark!"
  | excitedNoise(Cat) = "meow!";
Run Code Online (Sandbox Code Playgroud)

我可以很快添加以下内容:

fun angryNoise(Dog) = "grrrrrr"
  | angryNoise(Cat) = "hisssss";
Run Code Online (Sandbox Code Playgroud)

但是,如果我向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!";
Run Code Online (Sandbox Code Playgroud)

但请注意,面向对象语言恰恰相反.向抽象类添加新的子类非常容易,但如果要将新的抽象方法添加到抽象类/接口以供所有子类实现,则可能会很繁琐.

  • 这被Philip Wadler称为*表达问题*. (10认同)
  • 如果您在OO中将它们实现为抽象类的子类,那么您还必须编写所有这些新函数.唯一的区别是你如何组织功能(按类型或按行为). (9认同)
  • Wadler将此称为表达式问题:http://en.wikipedia.org/wiki/Expression_Problem (3认同)