函数式编程的陷阱/缺点

你什么时候不想使用函数式编程? 有什么不好?

我更加关注整体范式的弊端,而不是“不被广泛使用”或“没有好的可用debugging器”之类的东西。 现在这些答案可能是正确的,但是他们处理FP是一个新概念(一个不可避免的问题),而不是任何固有的特性。

有关:

  • 函数式编程的优点
  • 为什么没有function性编程呢?

对于函数式编程来说,我觉得很多缺点是很难的。 再一次,我是function规划国际会议的前任主席,所以你可以放心地认为我是有偏见的。

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

  • 新手编写的函数程序很可能会不必要地慢,比新来的C编程的C程序更有可能。另一方面,新手编写的C ++程序几乎同样可能会不必要地慢。 (所有这些shiny的function…)

    一般来说,专家在编写快速的function程序方面没有困 事实上,一些在8核和16核处理器上的最好的并行程序现在都是用Haskell编写的。

  • 开始函数式编程的人更可能会放弃在实现承诺的生产力增长之前,而不是开始Python或Visual Basic的人。 书籍和开发工具的forms并不多。

  • 与人交谈的人越来越less。 Stackoverflow就是一个很好的例子。 Haskell程序员相对较less的定期访问网站(尽pipe其中的一部分是Haskell程序员有自己的生动的论坛,比Stackoverflow更老更好)。

    同样,你也不能轻易地和你的邻居说话,因为函数式编程的概念比Smalltalk,Ruby和C ++等语言背后的面向对象概念更难学,更难学。 另外,面向对象社区花费了好几年的时间,为他们所做的工作提供了很好的解释,而function编程社区似乎认为他们的东西显然是伟大的,不需要任何特殊的隐喻或词汇来解释。 (他们错了,我还在等第一本好书“ functiondevise模式” 。)

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

函数式编程的一大缺点是,在理论层面上,硬件和命令式语言并不匹配。 (这是它的一个明显的优势的另一面,能够expression你想要完成的事情,而不是你希望计算机做什么。)

例如,函数式编程大量使用recursion。 这在纯lambda演算中很好,因为math的“栈”是无限的。 当然,在真正的硬件上,堆栈非常有限。 天真地recursion在一个大的数据集可以使你的程序繁荣。 大多数函数式语言都会优化尾recursion,所以这种情况不会发生,但是使algorithm尾recursion可以迫使您做一些相当不美观的代码体操(例如,尾recursion映射函数创build向后列表或必须build立差异列表,所以它必须做额外的工作才能返回到一个正常的映射列表与正确的顺序相比,非尾recursion版本)。

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

我认为围绕函数式语言的胡说是function编程的最大问题。 当我开始愤怒地使用函数式编程时,对我来说,一个很大的障碍就是理解为什么Lisp社区提出的许多高度发展的观点(例如关于macros和同态语法)是错误的。 今天,我看到很多人被Haskell社区所欺骗,并行编程。

事实上,你不必再看看这个线程来看看其中的一些:

“一般来说,专家们在编写快速的函数式程序方面并没有什么困难;实际上,一些8核和16核处理器上最好的并行程序现在都是用Haskell编写的。”

像这样的语句可能给你的印象是专家selectHaskell,因为它可以是非常好的并行性,但事实是,Haskell的performance糟糕,Haskell有利于多核并行的神话是Haskell研究人员永远不知道平行谁避免真正的同行审查只发布在自己的集团控制下的期刊和会议的舒适区。 Haskell在真正的并行/多核/ HPC中是看不见的,因为它在并行编程方面很糟糕。

具体而言,多核编程中的真正挑战是利用CPU高速caching来确保核心不会缺乏数据,这是Haskell从未解决的问题。 Charles Leiserson在麻省理工学院的小组做了很好的解释和解决这个问题的工作,他们自己的Cilk语言成为了.NET 4中英特尔TBB和微软TPL多核实际并行编程的中坚力量。精湛的描述如何使用这种技术来编写优雅的高级命令式代码编译为可扩展的高性能代码在2008年的论文中multithreadingcaching遗忘algorithm的caching复杂性 。 我在对一些最先进的Parallel Haskell研究的回顾中解释了这一点。

这在纯function编程范例上留下了一个大问号。 这是你花费时间和空间抽象的代价,这一直是这个陈述范式背后的主要动机。

编辑: 德克萨斯州多核技术最近也发现Haskell在多核并行性的背景下是压倒性的 。

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

函数式习惯用法经常会做大量的反转控制或懒惰,这往往会对debugging(使用debugging器)产生负面影响。 (由于不可变性/参考透明性,FP的偏差要小得多,这意味着你不需要经常debugging)。

Philip Wadler写了一篇关于这个的文章(称为为什么没有人使用函数式编程语言),并解决了阻止人们使用FP语言的实际缺陷:

更新:ACM访问权限不可访问的旧链接:

除了速度或采用问题以及解决一个更基本的问题之外,我已经听说了通过函数式编程,为现有的数据types添加新的函数是非常容易的,但是添加新的数据types是很困难的。 考虑:

(写在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添加一个新types,则必须通过每个函数来添加对它的支持:

 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,因为从行为中分离函数的想法吸引了我,而且由于纯函数与非纯函数的隔离,在隐式并行化背后有一些非常性感的理论。

我已经学习了三天的折叠类function。 折叠似乎有一个非常简单的应用程序:采取一个列表,并减less到一个单一的价值。 Haskell为此实现了一个foldlfoldr 。 这两个函数具有大量不同的实现。 foldl有一个替代的实现,称为foldl' 。 最重要的是,有一个稍微不同的语法版本叫foldr1foldl1具有不同的初始值。 其中有foldl1'的相应实现。 好像所有这些都不是令人兴奋的, fold[lr].*作为参数需要的函数和在内部使用的函数有两个单独的签名,只有一个变体在无限列表(r)上工作,并且只有其中一个在常量内存中执行(据我所知(L),因为只有它需要redex )。 理解foldr可以在无限列表上工作的原因至less需要对语言lazy-behavoir的正确理解,以及不是所有函数都会强制对第二个参数进行评估的小细节。 networking上的这些function的图表混淆为一个谁在大学里从来没有见过他们的地狱。 没有perldoc相当于。 我找不到Haskell前奏中的任何function的单一描述。 这个前奏是一种预装的configuration,与核心配合。 我最好的资源真的是一个我从来没有遇到过的人(卡莱),他正在以一笔巨大的开销帮助我。

呵呵,fold并不一定要把列表减less到非列表types的标量,列表的标识函数可以写成foldr (:) [] [1,2,3,4] (突出显示你可以累加到一个列表)。

/我回到阅读。

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

  1. 大多数人觉得函数式编程很难理解。 这意味着编写function代码可能会比较困难,而且对于其他人来说,这几乎肯定会更难。
  2. 函数式编程语言通常比像c这样的语言要慢。 随着时间的推移,这个问题变得越来越小(因为电脑越来越快,编译器变得越来越聪明)
  3. 没有像他们的命令对手那样广泛传播,可能很难find常见编程问题的库和例子。 (例如,它几乎总是比较容易findPython的东西,然后是Haskell)
  4. 缺less工具,特别是对于debugging。 它绝对不像开放Visual Studio for C#或eclipse for Java那么简单。

从function编程的具体实现细节来看,我看到两个关键问题:

  1. select一个真正的问题的function模型是必要的,这似乎是相当罕见的。 当问题领域势在必行时,使用具有这种特性的语言是一种自然而合理的select(因为通常build议尽量减less规范与实现之间的距离,作为减less微妙错误数量的一部分)。 是的,这可以通过一个足够聪明的编码器来克服,但是如果你需要使用Rock Star编码器来完成这个任务,那是因为这太难了。

  2. 由于某种原因,我从来没有真正理解,函数式编程语言(或者也许是他们的实现或社区?)更希望在他们的语言中拥有所有的东西。 用其他语言编写的库的使用less得多。 如果其他人对某些复杂操作有特别好的实现,那么使用这个操作会更有意义,而不是自己创build。 我怀疑这部分是由于使用复杂的运行时间来处理外部代码(尤其是高效地执行代码)相当困难。 我想在这一点上被certificate是错误的。

我想这些都是由于程序devise研究人员比普通编程人员更强烈地使用函数式编程而导致的普遍缺乏实用主义。 一个好的工具可以让专家做出伟大的事情,但是一个很好的工具是让普通人能够接近专家可以正常工作的工具,因为这是一项比较艰巨的任务。