是什么触发了lambda函数在现代主流编程语言中的流行?


112

在过去的几年中,匿名函数(又名lambda函数)已成为一种非常流行的语言结构,几乎每种主流/主流编程语言都已经引入了它们,或计划在即将发布的标准修订版中引入它们。

但是,匿名函数是数学和计算机科学中一个非常古老且非常著名的概念(由数学家Alonzo Church于1936年左右发明,并自1958年以来被Lisp编程语言使用,请参见此处)。

那么,为什么今天的主流编程语言(其中许多起源于15至20年前)从一开始就不支持lambda函数,而仅在后来才引入它们呢?

在过去的几年中,是什么导致了匿名功能的广泛采用?是否有某种特定的事件,新的要求或编程技术引发了这种现象?

重要的提示

这个问题的重点是在现代主流(因此,除了少数例外,非功能性)语言中引入匿名功能。另外,请注意,匿名函数(块)存在于Smalltalk中,这不是一种功能语言,并且标准命名函数甚至在程序语言(如C和Pascal)中已经存在很长时间了。

请不要通过谈论“采用功能范式及其好处”来概括您的答案,因为这不是问题的主题。


7
15到20年前,人们对OO提出了同样的问题……这不是一个新概念,但是它的普及程度很高。
MattDavey 2012年

7
@MattDavey大多数人当然会不同意,但是我不得不提醒他们,“大多数Smalltalk开发人员”并不是很多人; P
yannis 2012年

30
我认为更有趣的问题是什么导致了他们的死亡!毕竟,曾经有一段时间,当大多数现代语言的确有lambda表达式,然后像Java和C ++语言开始流行。(尽管我不会完全将Java称为“现代”语言。Java中最现代的概念是泛型,它可以追溯到60年代末70年代初。甚至Java提供的功能组合,指针安全性,内存安全性,类型安全性,GC,静态类型的OO,泛型都在1985年在Eiffel中存在……更好的是,恕我直言。)
JörgW Mittag 2012年

31
甚至在Java 1.0出现之前,尽管它仍处于早期设计阶段,但几乎所有人都指出Java需要lambda。使用Java的一些设计师包括Guy Steele(Lisp的支持者,Scheme的共同设计师,Common Lisp的共同作者,Fortress的设计师),James Gosling(编写了第一台PC的Emacs Lisp解释器),Gilad Bracha(Smalltalk)支持者,Ammorphic Smalltalk的联合设计师,Newspeak的设计师,Phil Wadler(Haskell的联合设计师),Martin Odersky(Scala的设计师)。Java最终没有lambdas的结果真的超出了我。
约尔格W¯¯米塔格

8
“一点点”通常意味着50%的功能和50%的噪声。
凯文·克莱恩

Answers:


86

函数式编程或至少在某些方面肯定存在明显的趋势。在某些时候采用了匿名函数的一些流行语言是C ++(C ++ 11),PHP(PHP 5.3.0),C#(C#v2.0),Delphi(自2009年起),Objective C()而Java 8将为该语言带来对lambda的支持。而且有些流行的语言通常不被认为是功能性的,但是从一开始,或者至少在早期,受支持的匿名功能就是JavaScript。

与所有趋势一样,试图寻找一个引发它们的事件可能是浪费时间,通常是多种因素的组合,其中大多数是无法量化的。《实用通用Lisp》(于2005年出版)在将Lisp作为一种实用语言引起新的关注方面可能发挥了重要作用,因为一段时间以来,Lisp 主要是您在学术环境或非常特定的细分市场中会遇到的一种语言。正如敏锐的在回答中所解释的,JavaScript的流行在引起对匿名函数的新关注方面也起了重要作用。

除了从多用途语言中采用功能性概念之外,还有向功能(或主要是功能性)语言的明显转变。诸如Erlang(1986),Haskell(1990),OCaml(1996),Scala(2003),F#(2005),Clojure(2007)之类的语言,甚至像R(1993)之类的领域特定语言似乎也获得了强烈的关注在介绍它们之后。总的趋势引起了对较旧的函数式语言的新关注,例如Scheme(1975)和显然的Common Lisp。

我认为最重要的事件是在业界采用功能编程。我完全不知道为什么这种情况一直没有发生,但是在我看来,在90年代初和中期某个时候,函数式编程开始在行业中占有一席之地(也许)始于Erlang的激增。电信Haskell在航空航天和硬件设计中的采用

乔尔·斯波尔斯基(Joel Spolsky)写了一篇非常有趣的博客文章,《 JavaSchools的危险》(Perils of JavaSchools),在那篇文章中,他反对当时(然后)大学倾向于Java而不是其他可能更难学习的语言的趋势。尽管该博客文章与函数式编程无关,但它确定了一个关键问题:

辩论就在这里。像我这样的懒惰的CS本科生多年抱怨,再加上业界对美国大学的CS专业毕业人数很少的抱怨,在过去十年中,许多本来很好的学校都100%地使用了Java。很时髦,使用“ grep”评估简历的招聘人员似乎很喜欢,而且最重要的是,Java没有什么困难可以真正淘汰程序员,而无需动脑筋做指针或递归,所以辍学率较低,计算机科学系的学生人数更多,预算也更多,而且一切都很好。

我仍然记得我上大学时第一次见到Lisp时多么讨厌她。这绝对是一个苛刻的情妇,而且不是可以立即提高工作效率的语言(嗯,至少我做不到)。与Lisp相比,Haskell(例如)要友好得多,您无需付出太多努力就可以提高工作效率,而又不会觉得自己像个白痴,这可能也是向函数式编程过渡的重要因素。

总而言之,这是一件好事。几种多用途语言正在采用范式的概念,这些概念以前对于大多数用户而言似乎是不可思议的,并且主要范式之间的差距正在缩小。

相关问题:

进一步阅读:


感谢您的回答(以及很多有趣的想法)。+1但是,我认为将(仅)lambda引入编程语言是向FP迈出的很小的一步,甚至可能使许多人感到困惑(lambda在命令式语言中独自干什么?)。在学习了一些Haskell,Scala和SML之后,我不觉得我可以使用仅支持lambda的命令式语言(真正的FP)(关于currying和模式匹配,不变性?)。
乔治


2
@YannisRizos:自从5(1994年)发布以来,Perl就具有匿名功能,但是直到5.004(1997年)才完全“正确”。
Blrfl 2012年

1
@penartur这也是我的想法,但是一位友好的编辑通过在此处指向我来纠正了我:msdn.microsoft.com/en-us/library/0yw3tz5k%28v=vs.80%29.aspx
yannis 2012年

我认为,导致功能语言流行的主要“事件”是网络。更具体地说,是从桌面程序到服务器端的转变。这使开发人员可以自由选择任何编程语言。保罗·格雷厄姆(Paul Graham)和利普(Lisp)在90年代就是一个著名的例子。
吉拉德·纳尔

32

我认为有趣的是,函数式编程的普及与Javascript的增长和扩散并驾齐驱。在功能性编程方面,JavaScript具有许多根本特性,在其诞生之初(1995年)在主流编程语言(C ++ / Java)中并不十分流行。它突然被作为唯一的客户端Web编程语言注入了主流。突然,许多程序员只需要了解Javascript,因此您必须了解一些功能性编程语言功能。

我想知道,如果不是因为Javascript的突然兴起,那么流行的功能语言/功能会是多么流行。


5
Java语言当然是一种重要的语言,但是我不确定Java语言的引入是否可以单独说明函数式编程的普及:正如Yannis在他的回答中所说明的那样,近年来出现了许多其他函数式编程语言。 。
乔治

8
@ Giorgio-可能还有很多其他功能性编程语言,但是(相对)没有人使用它们。JS的使用以及越来越多的观点认为C ++ / Java编写函子的方法既痛苦又烦人,这实际上是推动主流发展的动力,即使更多的学术语言坚定了应如何实现它们。
Telastyn 2012年

1
在一般的动态语言的流行在被暗示为哈斯克尔的普及一个解释:book.realworldhaskell.org/read/...
雅尼斯

同样,问题的重点不是总体上FP的普及,而是关于后来在通用,非功能性语言中引入匿名功能的问题。即使大型公众(大多数程序员)不了解它们,语言设计师也非常了解它们。一开始一定有理由将其排除在外。也许对于90年代初期的开发者来说,它们被认为是不直观的。
乔治

@giorgio-与Java样式函子相比,实现起来要麻烦得多。结合缺乏知识/采用的知识,这是一个非常明确的设计选择。
Telastyn 2012年

27

JavaScript和DOM事件处理程序意味着成千上万的程序员必须至少学习一些关于一流功能的知识,才能进行网络上的任何交互操作。

从那里开始,到匿名函数的步伐相对较短。因为JavaScript不会结束this,所以它也强烈建议您也了解闭包。然后,您变得很聪明:您了解匿名的一流函数,这些函数在封闭的词法范围内关闭。

一旦适应了它,便会以您使用的每种语言来使用它。


7
+1不仅涉及匿名功能。闭包是一个比定义内联临时函数更广泛的概念。
phkahler 2012年

@phkahler:您是对的,并且从这个意义上讲,Java已经有了闭包(甚至比使用函数文字获得的闭包还要强大),但是对于单方法匿名类的常见情况它缺乏简洁的表示法。
Giorgio

17

当然,这不是唯一的因素,但是我要指出Ruby的流行。并不是说这比董事会已有的六个答案中的任何一个都重要,但我认为许多事情是同时发生的,列举所有这些都是很有用的。

Ruby不是一种功能语言,当您使用过ML之类的东西时,它的lambda,prods和block似乎很笨拙,但事实是,它普及了映射的概念,并减少了一代年轻程序员的兴趣,他们逃离了Java和PHP。牧场。几种语言的Lambda似乎比其他任何东西都更具防御性(“坚持!我们也有这些!!)

但是,块语法及其与.each,.map,.reduce等的集成方式,使匿名函数的想法广为流行,即使它实际上是一个像协程一样的句法构造。通过&轻松转换为proc,使其成为函数式编程的门户药物。

我认为编写JavaScript的Ruby on Rails程序员已经开始以轻量级的功能风格进行工作。再加上程序员博客,Reddit的发明,黑客新闻和Stack Overflow的同时出现,其思想在互联网上的传播速度比新闻组时代要快。

TL; DR:Ruby,Rails,JavaScript,博客和Reddit / Hacker News / Stack Overflow将功能性想法推向了大众市场,因此每个人都希望使用现有的语言来防止进一步的缺陷。


2
+1是一个很好的答案,并且(如果可能的话,因为我只有一个投票)+1指出“几种语言中的Lambda似乎比其他任何东西都更具防御性(”,坚持!我们也有这些!!)”。我认为这也是一个因素。对于某些语言来说,lambdas是一项不错的功能,即使它对整个语言几乎没有表现力,但它仍使该语言颇受欢迎(a程序员的数量似乎认为对匿名函数的支持等同于完全支持函数式编程。)
Giorgio

2
我真的认为这是近年来大多数语言实现块语法的原因。但是唯一可以确保的方法是询问语言开发人员的动机。我们只能推测imo。
SpoBo

对我来说,Ruby是最先成为块摇滚的语言,非常吸引人,因此+1。Haskell可能也产生了作用。
rogerdpack 2012年

13

正如Yannis所指出的,有许多因素影响了以前没有的语言对高阶函数的采用。他仅轻描淡写的重要项目之一就是多核处理器的激增,以及对更多并行和并发处理的渴望。

函数式编程的map / filter / reduce样式对并行化非常友好,从而使程序员可以轻松使用多个内核,而无需编写任何显式的线程代码。

正如Giorgio所指出的,函数式编程不仅仅是高级函数。函数,加上映射/过滤/减少编程模式,以及不变性是函数编程的核心。这些东西共同构成了强大的并行和并发编程工具。值得庆幸的是,许多语言已经支持某种不变性的概念,即使不支持,程序员也可以将它们视为不变性,从而允许库和编译器创建和管理异步或并行操作。

向语言添加高阶函数是简化并发编程的重要步骤。

更新资料

我将添加一些更详细的示例,以解决Loki指出的问题。

考虑下面的C#代码,该代码遍历小部件的集合,从而创建新的小部件价格列表。

List<float> widgetPrices;
    float salesTax = RetrieveLocalSalesTax();
foreach( Widget w in widgets ) {
    widgetPrices.Add( CalculateWidgetPrice( w, salesTax ) );
}

对于大量的小部件或计算量大的CalculateWidgetPrice(Widget)方法,此循环将无法充分利用任何可用的内核。要在不同内核上进行价格计算,将需要程序员显式创建和管理线程,传递工作并一起收集结果。

一旦将高阶函数添加到C#中,请考虑一个解决方案:

var widgetPrices = widgets.Select( w=> CalculateWidgetPrice( w, salesTax ) );

foreach循环已移到Select方法中,隐藏其实现详细信息。程序员剩下的就是告诉选择要应用于每个元素的功能。这将允许Select实现以并行方式运行计算,无需程序员参与即可处理所有同步和线程管理问题。

但是,当然,Select不能并行运行。这就是不可变性的地方。Select的实现不知道所提供的函数(上面的CalculateWidgets)没有副作用。该功能可能会在Select及其同步范围之外更改程序的状态,从而破坏所有功能。例如,在这种情况下,salesTax的值可能会错误地更改。纯功能语言提供了不变性,因此Select(映射)功能可以确定没有状态改变。

C#通过提供PLINQ替代Linq来解决此问题。看起来像:

var widgetPrices = widgets.AsParallel().Select(w => CalculateWidgetPrice( w, salesTax) );

这将充分利用系统的所有核心,而无需明确管理这些核心。


我确实指出了对更多并行和并发处理的需求,我在第四段中链接到的“ Aerlang的历史” ACM文章中对此进行了讨论。但这是一个很好的观点,我应该对此进行更多的扩展。+1,因为现在我不必; P
yannis 2012年

您说得对,我的视线不够仔细。我编辑了我的话。
2012年

哦,您并不是真的必须这样做,我不是在抱怨;)
yannis 2012年

4
您上面描述的内容均不需要lambda。使用命名函数可以轻松实现相同的功能。在这里,您只是在记录a cause和a perceived affect而没有解释correlation。IMO的最后一行是问题所在。但是您没有回答。为什么它简化了并发编程。
马丁·约克

@Ben:请注意,您的示例涉及不需要使用匿名函数的高阶函数。您的答案包含有趣的想法(针对另一个问题),但现在不在您的讨论范围内。
乔治

9

我同意这里的许多答案,但是有趣的是,当我了解了lambda并跳入它们时,并不是出于其他人提到的任何原因。

在许多情况下,lambda函数只会提高代码的可读性。在lambda之前,当您调用接受函数指针(或函数或委托)的方法时,必须在其他位置定义该函数的主体,因此,当您具有“ foreach”构造时,您的读者将不得不跳到另一个地方。代码的一部分,以查看您打算对每个元素执行什么操作。

如果处理元素的函数的主体只有几行,我将使用一个匿名函数,因为现在当您阅读代码时,功能保持不变,但是读者不必来回跳动,整个实现就是就在他面前

没有匿名函数就可以实现许多功能编程技术和并行化。只需声明一个常规变量,并在需要时传递引用即可。但是使用lambda可以简化代码编写和阅读代码的难度。


1
很好的解释(+1)。Lisp程序员自1958
Giorgio

4
@ Giorgio:可以,但是Lisp程序员还必须购买带有增强的打开/关闭括号键的特殊键盘:)
DXM 2012年

@DXM:不是键盘,它们获得了附加的输入设备,类似于用于打开和关闭括号的钢琴踏板;-)
vartec

@ DXM,vartec:最近正在做一些Scheme,我发现括号可以。某些C ++代码可能更含糊不清(我对C ++的经验比对Scheme的经验还多)。:-)
乔治

9

在这里涉及到最近的历史时,我相信一个因素是在Java和.NET中增加了泛型。这自然会导致Func < >和其他强类型的计算抽象(Task < >,Async < >等)。

在.NET世界中,我们精确地添加了这些功能以支持FP。这触发了一系列与函数式编程有关的语言工作,尤其是C#3.0,LINQ,Rx和F#。这种进步也影响了其他生态系统,并且直到今天仍在C#,F#和TypeScript中继续。

当然也可以让Haskell在MSR工作:)

当然也有许多其他影响(当然是JS),而这些步骤又受很多其他因素的影响-但是将泛型添加到这些语言中有助于在软件世界的大部分地区打破90年代后期僵化的OO正统观念,并有助于开放FP的门。

唐·塞姆

ps F#是2003年,而不是2005年-尽管我们会说直到2005年它才达到1.0。我们还在2001-02年做了一个Haskell.NET原型。


欢迎!我将2005用于F#,因为那是F#的Wikipedia文章中报告的第一个稳定版本发布的年份。您想要我将其更改为2003吗?
扬尼斯2012年


4

从我看来,大多数答案都集中在解释为什么函数式编程通常会卷土重来并使其成为主流。我觉得这并没有真正回答关于匿名函数的问题,以及为什么它们突然变得如此流行。

真正流行的是封闭。因为在大多数情况下,闭包是通过变量传递的一次性函数,所以显然对它们使用匿名函数语法是有意义的。实际上,在某些语言中,这是创建闭包的唯一方法。

为什么封盖越来越流行?因为它们在事件驱动的编程很有用,所以在创建回调函数时。当前,这是编写JavaScript客户端代码的方式(实际上,这是编写任何GUI代码的方式)。当前,它也是编写高效后端代码和系统代码的方式,因为以事件驱动范例编写的代码通常是异步且非阻塞的。对于后端,这已成为解决C10K问题的方法


感谢您强调这个问题不是关于函数式编程(+1)的,因为(1)非参数语言(例如Smalltalk)也使用作为参数传递的代码块的思想,以及(2)改变状态从闭包的词法上下文中捕获(在许多lambda实现中可能这样)绝对是不起作用的。是的,有了闭包,匿名闭包的步骤很短。有趣的是,闭包也早已众所周知,并且自八十年代以来就使用了事件驱动的编程(据我所知)。
Giorgio

但是也许直到最近几年才清楚可以比以前想象的更多地使用闭包。
Giorgio

@Giorgio:是的,当前使用的大多数概念已经存在了很长时间。但是它们还没有被使用,现在已经被使用了。
vartec

1

我认为原因是并发和分布式编程的普及率越来越高,面向对象编程的核心(用对象封装变化的状态)不再适用。在分布式系统的情况下,因为有没有共享状态(即概念和软件抽象是泄漏)以及在并发系统的情况下,由于适当地同步访问共享状态已被证明麻烦且容易出错。就是说,面向对象编程的主要好处之一不再适用于许多程序,这使得面向对象范例的帮助远不如以前。

相反,功能范式不使用可变状态。因此,我们从功能范例和模式中获得的任何经验都可以立即转移到并发和分布式计算中。现在,该行业没有重新发明轮子,而是借用了这些模式和语言功能来满足其需求。


4
一些主流语言(例如C ++ 11)中的匿名函数确实允许可变状态(它们甚至可以从定义的环境中捕获变量,并在执行过程中对其进行更改)。因此,我认为谈论一般的功能范式,特别是关于不变性,这超出了所要提出的问题的范围。
乔治

刚阅读Java 8的一些功能说明时,项目lambda的主要目标之一就是支持并发。这立即将我们带入了所有这些出色的javabean都会遇到的可变性clusterbomb。一旦Java获得了lambda(假设它确实在版本8的最终发行版中确实实现了),那么他们就需要解决默认的不可变问题(以某种方式破坏语言,以Lisp的方式考虑-副作用免费功能)在COBOL中-重击DATA DIVISION / COPYBOOK)
Roboprog 2013年

说得好。摆脱可变状态使并发变得更容易,而诸如cascalog和spark之类的技术可以轻松地在计算机集群之间分配功能性编程。有关如何以及为什么的更多详细信息,请参见glennengstrand.info/analytics/distributed/functional/…
Glenn

1

如果可以增加我的0.02欧元,尽管我会同意JavaScript引入该概念的重要性,但我认为除了并发编程之外,我还应将责任归咎于异步编程的当前方式。当进行异步调用(网页需要)时,简单的匿名功能非常有用,以至于每个Web程序员(即每个程序员)都必须非常熟悉该概念。


1

的一个类似于匿名函数的另一个很老的例子/ lambda表达式是调用-名在大陵60.然而要注意的call-by-name是接近传递宏作为参数不是通过真正的功能,并且更脆弱/更难结果明白了。


0

据我所知,这里的祖先。

  • 2005年:Javascript最近使带有lambda的高阶编程重新成为主流。特别是像underscore.jsjquery这样的库。这些库中的第一个是prototype.js,它比jquery早一年。原型基于Ruby的Enumerable模块,这使我们能够…
  • 1996年:Ruby的Enumerable模块很明显地从Smalltalk的收集框架中得到启发。正如Matz在许多采访中提到的那样,这导致我们…
  • 1980:Smalltalk使用了大量的高阶编程,并提供了一个集合API,该API大量使用了高阶编程(例如,GNU Smalltalk的Iterable类)。在惯用的Smalltalk代码中,您不会找到任何for循环,而只会找到高阶枚举。不幸的是,当Java在1998年将Smalltalk的集合框架移植到Java时,省略了高阶枚举。这就是在未来十年内逐步淘汰主流编程的方式!Smalltalk有很多祖先,但是与OP问题相关的是LISP,这使我们能够…
  • 1958年:LISP显然以高阶编程为核心。

当然,Amiss是整个ML祖先。ML,SML,OCaml,Haskell,F#。那必须有所作为..
穆罕默德·阿尔卡鲁里

-1

匿名函数很不错,因为很难命名,而且如果只在一个地方使用一个函数,则不需要名称。

Lambda函数直到最近才成为主流,因为直到最近,大多数语言还不支持闭包。

我建议Javascript推动了这一主流。这是一种通用语言,无法表达并行性,匿名函数简化了回调模型的使用。此外,Ruby和Haskell等流行语言也做出了贡献。


1
“ Lambda函数直到最近才成为主流,因为直到最近,大多数语言还不支持闭包。”:这种推理对我来说有点循环:成为主流意味着大多数语言都支持闭包。人们可能会立即问到“是什么导致了现代编程语言中闭包的流行”。
乔治

我知道Python并不是lambda的最佳实现。但就受欢迎程度而言,它的贡献可能超过Haskell。
Muhammad Alkarouri
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.