为什么函数式编程还没有被接管?


197

我读过一些有关声明式/函数式编程(语言)的文章,尝试过Haskell并自己编写了一篇。从我所看到的,函数式编程比经典的命令式样式有几个优点:

  • 无状态程序;没有副作用
  • 并发 在新兴的多核技术中发挥出色
  • 程序通常较短,在某些情况下更易于阅读
  • 生产率提高(例如:Erlang)

  • 命令式编程是一个非常古老的范例(据我所知),可能不适合21世纪

为什么使用功能语言编写的公司或程序仍然如此“稀有”?

为什么在查看函数式编程的优势时,我们仍然使用命令式编程语言?

也许在1990年还为时过早,但是今天呢?


1
注意:此问题已在meta上讨论了至少两次,共识是保留它,尽管已通过上述“历史意义”锁进行了归档。我强烈建议任何人着眼于解锁它,而不必停下来,并感谢不必参加有关此问题的又一次乏味的讨论,享受原样的答案,并继续开展业务。
Shog9 2016年

Answers:


530

因为所有这些优点也是缺点。

无状态程序;没有副作用

现实世界中的程序都是关于副作用和突变的。用户按下按钮时是因为他们希望发生某些事情。当他们键入内容时,他们希望该状态替换以前存在的任何状态。当会计领域的简·史密斯结婚并更名为简·琼斯时,支持打印她的工资单的业务流程的数据库最好是处理这种突变。当您向外星人发射机枪时,大多数人并没有将其建模为建造生命值更少的新外星人。他们将其建模为现有外星人财产的变异。

当编程语言概念从根本上针对要建模的领域起作用时,很难证明使用该语言是合理的。

并发 在新兴的多核技术中发挥出色

问题刚刚解决。使用不可变的数据结构,您可以廉价使用线程安全,但要以处理过时的数据为代价。使用可变的数据结构,您将始终可以处理新数据,而不必编写复杂的逻辑来保持数据的一致性。并非其中一个明显优于另一个。

程序通常较短,在某些情况下更易于阅读

除非它们更长且更难阅读。学习如何阅读以功能风格编写的程序是一项困难的技能。人们似乎在构思程序方面要好得多,因为要遵循的一系列步骤(例如配方),而不是要进行的一系列计算。

生产率提高(例如:Erlang)

为了证明聘请知道如何以功能风格进行编程的程序员的巨额开销,生产力必须提高很多。

记住,您不想丢掉一个工作系统。大多数程序员不是从头开始构建新系统,而是维护现有系统,其中大多数是用非功能语言构建的。想象一下试图向股东证明这一点。您为什么要废弃现有的工资表系统以花费数百万美元构建一个新的工资表系统?“因为函数式编程太棒了”不可能使股东满意。

命令式编程是一个非常古老的范例(据我所知),可能不适合21世纪

函数式编程也很老。我看不出这个概念的年代如何相关。

不要误会我的意思。我喜欢函数式编程,我之所以加入这个团队是因为我想帮助将函数式编程的概念引入C#,并且我认为以不变的方式进行编程是未来的方式。但是以功能风格进行编程会产生巨大的成本,而这是不能简单地期望的。向功能性风格转变的过程将在数十年内逐步发生。就是这样:向更加实用的样式的转变,而不是全面拥抱Haskell的纯净和美丽以及放弃C ++。

我以编译器为生,我们肯定会为下一代编译器工具拥抱一种功能样式。这是因为函数式编程从根本上来说是我们面临的各种问题的良好匹配。我们的问题全都在于获取原始信息(字符串和元数据)并将其转换为不同的字符串和元数据。在发生突变的情况下,例如有人在IDE中键入内容,问题空间固有地适用于一些功能性技术,例如仅增量地重建树的部分。许多域没有这些使它们显然适合于功能样式的良好属性


41
“当会计界的简·史密斯结婚并更名为简·琼斯时,支持打印她的工资单的业务流程的数据库最好是处理这种突变。” 将会有简史密斯以前的名字的记录,我们不会将简以前的名字的所有实例追溯更新为她的新名字;)
朱丽叶

40
@朱丽叶:当然。我的观点是,如果您有一个代表员工的对象,则可以将“更改员工的姓名”操作视为代表该员工的对象的一种变体,而该对象不会更改对象标识。当简·史密斯(Jane Smith)更改她的名字时,您不会再创建另一个名叫简·琼斯(Jane Jones)的员工。没有两个名字不同的员工。很自然地将此过程建模为对象的突变,而不是新对象的构造。
埃里克·利珀特

24
这是一个很好的答案,但我认为您有时会夸大您的案子。就像朱丽叶(Juliet)所说的那样,尽管人们可能会认为它是名称更改,但实际上它确实是更深层次的名称替换。而且尽管功能性程序对于人们而言可能更难阅读(因为这一种博学的技能),但这并不是通常,因为它们更长。Haskell程序几乎总是比Java程序更简洁-即使在具有很多固有状态的“不适合”域中也是如此。
查克(Chuck)2010年

29
+1阅读此答案真是新鲜空气。很高兴听到您所处位置的人的这种实用主义(对函数式编程有潜在的热情)。
Dhaust

11
“因为所有这些优点也是缺点。无状态程序;没有副作用”:据我了解(我对FP不够了解,无法写出权威的答案),这是不正确的。函数编程与引用透明性有关,而不是与避免状态有关(即使必须适当处理状态以确保引用透明性)。Haskell 确实允许状态和突变。它只是提供了不同的(可能会争辩,更好)的工具进行推理。
Giorgio

38

编程大师:与主要编程语言创作者的对话

[哈斯克尔]

您为什么认为没有函数式编程语言进入主流?

约翰·休斯:行销不佳!我不是在宣传 我们有很多。我的意思是要仔细选择要占领目标市场的细分市场,然后做出坚定的努力,使功能编程成为迄今为止解决这一细分市场的最有效方法。在80年代的欢乐时光中,我们认为函数式编程对所有事物都有利,但是将新技术称为“对一切都有益”与将其称为“特别是一无是处”相同。品牌应该是什么?John Launchbury在ICFP的邀请演讲中非常清楚地描述了这个问题。Galois Connections的品牌是“功能语言的软件”时,差点破产,但是自从专注于“高安全性软件”以来,他们的实力越来越强。

许多人不知道技术创新是如何发生的,他们期望更好的技术将完全独自成为主导(“更好的捕鼠器”效应),但世界却并非如此。


36
Haskell:20年后,一夜之间取得成功!
朱丽叶

按照链接阅读有关Grady Booch的有趣文章的评论。不知道Booch是谁,但是无论如何,这让我笑了。
fearofawhackplanet

4
Grady Booch最终与Jacobson和Rumbaugh一起负责UML的可憎性。
只是我的正确观点,2010年

27

根本的答案是不会替代,也不会替代-它们是具有不同优点和缺点的不同工具,哪种方法具有优势取决于项目和其他“软性”问题,例如可用的人才库。

我认为您是对的,当选择功能编程而不是其他样式时,由于多核而导致的并发性增长将提高(全球开发项目集)的百分比。

我认为今天很少见,因为当今的大多数专业人才最喜欢命令式和面向对象的技术。例如,我不止一次选择Java作为商业项目的语言,因为它足够好,没有争议,而且我知道我永远不会耗尽可以在其中进行编程(足够好)的人。


这是非常有见地和令人愉快的实用主义。绝对是我对各种形式问题的最佳答案。
本森

也许这两种都是头等公民的语言将会流行起来。
凯文·科斯特兰

1
同意110%。有几次,我尝试加入FP,但几周后我失去了继续前进的意愿。我已经在程序上进行了30多年的编程,而且我已经习惯了命令式编程。整个IT行业都是如此。改变既不会轻易也不会很快。
Richard Eng 2015年

26

尽管功能编程有很多优势,但命令式和面向对象的编程永远不会完全消失。

命令式和面向对象的编程是对该问题及其解决方案的分步说明。这样,可以更容易理解。函数式编程可能有些晦涩。

最终,有用的程序将始终具有副作用(例如,向用户提供实际输出以供消费),因此,最纯粹的功能语言仍将需要一种不时进入命令式世界的方法。

当前的最新技术是命令式语言(例如C#)从功能世界(例如lambda语句)中借用功能,反之亦然。


3
OOP是否不是命令式编程的某种子集?
pankrax 2010年

9
OOP是一个超集。OOP是势在必行如C ++是C.
罗伯特哈维

10
我认为OOP不一定要依赖命令式编程。看起来像Clojure或CLOS-两者都是功能性的,但还是面向对象的。
加布

9
OO语言往往势在必行,但并非必须如此。OCaml是一种功能强大的语言(尽管不是纯粹的功能语言),其整个存在意义是面向对象的。
Chuck 2010年

7
我不明白为什么OOP是命令式编程的超集。并非所有命令式代码都是OOP,也不是所有功能代码都是非OOP。我想说的是,OOP是命令式编程和函数式编程,就像空气动力学机翼是赛车,飞机,火箭,冷却风扇或风车一样,或者……也就是说,这些概念是相关的,但并不紧密相关。 1对1连接。
塞巴斯蒂安·马赫

21

不是吗

过去,Smalltalk是一个很棒的面向对象的系统。为什么没有采用面向对象的编程?好吧,它有。看起来不像Smalltalk。主流语言在C ++,Java,C#等语言中的应用越来越类似于Smalltalk。时尚和样式的变化比任何事物都慢,因此,当OO成为主流时,我们通过将OO的一部分粘合到旧语言中而获得了它,因此看起来就像C一样可以吞噬。

功能是相同的方式。Haskell是一种很棒的函数式语言。但是,与20年前相比,如今使用类C语法的主流程序员人数更多。因此它必须看起来像C。完成:查看任何LINQ表达式,并告诉我它不起作用。


2
有趣的一点是,主流语言如何变得越来越像Smalltalk?例如,C ++,Java和C#都不基于消息发送,而我认为这是Smalltalk范例中最重要的部分。
乔纳森·斯特林

4
乔纳森(Jonathan):选择任何Smalltalk功能,并观察它在C ++(最旧的)中最弱,在Java中可以,在C#中更好。例如,GC(仅Java / C#),自动装箱(仅Java / C#之后),闭包(仅C#)和反射(在Java中存在,在C#中更好)。如果要传递消息,请查看C#4 dynamic。这是这些功能中最多的Smalltalk-y,所以仅在这三种语言中最现代的最新版本中提供它就不足为奇了。:-)

Javascript,python和ruby很好地说明了它的确具有的功能
2014年

15

我相信命令式语言之所以流行,仅仅是因为这是更多人习惯的。函数式编程和命令式编程模型都不比其他方法更晦涩难懂。实际上,它们是互补的。

一位发帖人说,命令性代码比功能性编程代码更容易理解。只有在读者已经看过命令性代码的情况下,这才是正确的,尤其是如果先前的示例是同一“系列”的一部分(例如C / C ++,Perl,PHP和Java)。我不会声称这对任何命令式语言都是正确的;比较Java和Forth,举一个极端的例子。

对于一个外行来说,所有编程语言都是难以理解的胡言乱语,也许是诸如Hypertalk和SQL之类的冗长语言除外。(值得注意的是,SQL是一种声明性和/或功能性语言,并且非常受欢迎。)

如果从一开始就接受过Lisp-y或Haskell-y语言的培训,我们都会认为函数式编程语言是完全正常的。


“有一位发帖人说,命令式代码比函数式编程代码更容易理解。只有在读者已经看过命令式代码的情况下,尤其是在先前的示例属于同一“系列”的一部分(例如C / C ++, Perl,PHP和Java)。”:非常正确(+1):我记得当我开始编程时,我在学习Pascal和C方面付出了很多努力。既然我对这些语言有一定的经验,那么阅读Scala,Haskell或Scheme是多么容易。
乔治

2
我有时仍然认为可变状态有用的唯一原因是,它提供了一种编写快速代码(不进行复制)的简便方法。但是这样做的原因可能是我不了解足够的函数编程,而且在90%的情况下,您可以在不使用可变状态的情况下编写快速的函数代码。
乔治

3
电子表格是用于组织计算的最广泛使用且寿命长的环境之一。本质上是具有单元而不是命名变量的函数式编程环境。我认为人们通常不会将程序固有地视为一系列步骤。也许程序员沉迷于广泛的命令式语言。
jpnp 2014年

14

您已经获得了足够的答案,我仅会提及一些我尚未提及的事情。

首先(在我看来),程序语言从其通用性中受益匪浅。举个例子,几乎所有几乎在任何程度上都了解主流程序语言(或OO)的人都可以相当好地阅读其他大多数语言。我积极避免使用Java,C#,Cobol,Fortran或Basic(仅举几个例子),但可以很好地阅读它们中的任何一个-实际上,与每天使用它们的人一样,几乎可以阅读。

在功能方面,这是真少。仅举例来说,我也可以相当合理地编写Scheme,但这在阅读Ocaml或Haskell时几乎没有用(仅用于几个示例)。即使在一个家庭中(例如,Scheme与Common Lisp),对一个人的熟悉程度似乎也不会很好地转化为另一个。

功能代码更具可读性的主张往往仅在狭窄的条件范围内才是正确的。对于非常熟悉该语言的人来说,可读性确实非常出色-但对于其他所有人而言,可读性通常几乎不存在。更糟糕的是,尽管过程语言的差异主要是语法,因此相对容易学习,但功能语言的差异通常更为根本,因此需要进行大量研究才能真正理解(例如,了解Lisp对理解Monad毫无帮助)。

另一个要点是,功能程序比程序程序短的想法通常更多地基于语法而不是语义。用Haskell编写的程序(例如)通常很短,但是它的功能只是其中很小的一部分。如果仅Haskell具有相对简洁的语法,这是非常重要的。

很少有纯粹的功能语言可以与APL争夺简洁的源代码(尽管公平地说,APL还支持创建更高级别的功能,因此与其他情况相比并没有太大的区别)。反之,阿达和C ++(仅几个例子)可以在必要的完成给定任务操作的数量方面相当有竞争力,但语法(至少通常)基本上更详细。


极好的评论!我全心全意。我发现大多数程序语言都相当容易阅读和理解,即使我只是其中的真诚专家。关于FP语言不能说相同的话。
Richard Eng 2015年

1
另一个重要的一点是,在任何给定的范例中,您都可以找到从新手到专家的各种专业知识。FP代码对于专家来说可能很容易阅读和理解,但是中级程序员可能仍然会遇到困难。专家通常只占FP社区的一小部分。
Richard Eng 2015年

11

无需感知的需求

我回想起我老老板里克·克莱恩(Boss Rick Cline)的回答,当时我向他展示了约翰·巴库斯(John Backus)的图灵奖演讲的副本,该演讲的标题是《编程可以从冯·诺伊曼风格中解放出来吗?

他的回答是:“也许我们中的某些人不想从冯·诺依曼风格中解放出来!”


10

为什么函数式编程还没有被接管?

功能在某些方面更好,而在其他方面则更糟,因此它永远不会“接管”。它在现实世界中已经无处不在。

无状态程序;没有副作用

无状态程序更易于测试。现在,这已广为人知,并且在工业中经常被利用。

并发 在不断发展的多核技术中发挥出色的作用程序通常更短,在某些情况下更易于阅读生产率提高了(例如:Erlang)

您正在混合并发和并行性。

使用通信顺序过程(CSP)可以有效地完成并发。CSP中的代码可以更改其本地状态,但是在它们之间发送的消息应始终是不变的。

纯函数式编程在多核中的表现极差,因为它对缓存不友好。内核最终争夺共享内存,并且并行程序无法扩展。

为什么使用功能语言编写的公司或程序仍然如此“稀有”?

Scala通常被认为是一种功能性语言,但它的功能并不比C#(当今世界上最受欢迎的语言之一)更强大。

为什么在查看函数式编程的优势时,我们仍然使用命令式编程语言?

纯函数式编程有许多严重的缺点,因此我们使用不纯函数式语言,例如Lisp,Scheme,SML,OCaml,Scala和C#。


7

当我想到函数式编程可能给我的项目带来什么时,我总是沿着相同的思路走下去:

  1. 为了获得函数式编程的全部优势,您需要懒惰。是的,有严格的函数式语言,但是函数式编程的真正好处在严格的代码中也无法体现出来。例如,在Haskell中,很容易在列表上创建一系列惰性操作并将它们连接起来并将其应用到列表中。例如。op1 $ op2 $ op3 $ op4 $ someList。我知道这不会建立整个列表,而在内部,我只会得到一个很好的循环,一次遍历所有元素。这使您可以编写真正的模块化代码。两个模块之间的接口可能涉及移交潜在的庞大数据结构,但是您不必一定要驻留该结构。

  2. 但是,当您懒惰时,就很难推理出内存使用情况了。更改Haskell编译器标志通常会将算法使用的内存量从O(N)更改为O(1),但有时不这样做。当您有需要最大程度地利用所有可用内存的应用程序时,这实际上是不可接受的,即使对于不需要所有内存的应用程序也不是很好。


懒惰与调试之间的交互作用也较不理想。
布莱恩(Brian)2010年

3
当我发现我用其他语言追逐的许多错误都与参照透明性不足有关时,我就不必担心调试问题,尽管有时它们会很痛苦。
sigfpe 2010年

6

两件事情:

  1. 无论技术多么出色,都需要时间。FP背后的想法已有70多年的历史了。但是它在软件工程中的主流应用(在行业中,在战trench中)可能不到10年。要求开发人员采用种族上新的思维方式是可能的,但是这需要时间(很多年)。例如,OOP实际上在1980年代初期就已成为主流用法。但是,直到1990年代后期,它才获得优势。
  2. 您需要迫使人们在技术发展壮大之前面对它的力量。当前,人们正在使用不使用并行性的工具,并且一切正常。当不使用并行性的应用程序变得异常缓慢时;那么许多人将被迫使用并行工具,而FP可能会迅速普及。这也可能适用于FP的其他优势。

3
FP 非常擅长代码重用。可能比OO好。我不得不在工作中处理过几次,迁移到不同的类型,以及新的系统,这很轻松。
nlucaroni

@Freddy Rios和@nlucaroni。我改写了评论,以消除误解。
Phil
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.