'closure'和'lambda'有什么区别?


811

有人可以解释吗?我了解它们背​​后的基本概念,但是我经常看到它们互换使用,并且感到困惑。

现在我们在这里,它们与常规函数有何不同?


85
Lambda是一种语言构造(匿名函数),闭包是一种用于实现一流函数(无论是否匿名)的实现技术。不幸的是,这经常被许多人所混淆。
安德里亚斯·罗斯伯格



有关PHP的关闭,请参见php.net/manual/en/class.closure.php。这不是JavaScript程序员所期望的。
PaulH

SasQ的答案非常好。恕我直言,如果这个问题引导观众回答这个问题,它将对SO用户更加有用。
AmigoNico

Answers:


703

一个拉姆达只是一个匿名函数-没有名字定义的函数。在某些语言(例如Scheme)中,它们等效于命名函数。实际上,函数定义被重写为在内部将lambda绑定到变量。在其他语言(例如Python)中,它们之间有一些(相当不必要的)区别,但是在其他方面它们的行为相同。

闭合是任何功能关闭以上环境中,它被定义。这意味着它可以访问不在其参数列表中的变量。例子:

def func(): return h
def anotherfunc(h):
   return func()

这将导致错误,因为func关闭环境anotherfunc- h未定义。func仅在全球环境中关闭。这将起作用:

def anotherfunc(h):
    def func(): return h
    return func()

因为在func中定义了anotherfunc,并且在python 2.3及更高版本(或类似的数字)中定义了几乎正确的闭包(突变仍然无效)时,这意味着它关闭 anotherfunc的环境并且可以访问内部的变量它。在Python 3.1+,当使用突变也工作nonlocal关键词

另一个要点- 即使不再评估该环境,也func将继续关闭其anotherfunc环境anotherfunc。此代码也将起作用:

def anotherfunc(h):
    def func(): return h
    return func

print anotherfunc(10)()

这将打印10。

正如您所注意到的,这与lambda无关-它们是两个不同的(尽管相关)概念。


1
Claudiu,据我所知,python从来没有完全正确地关闭过。当我不看时,他们是否解决了可变性问题?很有可能...
simon

simon-你是对的,他们仍然不完全正确。我认为python 3.0将添加一个hack来解决此问题。(类似于“非本地”标识符)。我将改变我的ansewr以反映这一点。
克劳迪(Claudiu)

2
@AlexanderOrlov:它们既是lambda,也是闭包。Java之前通过匿名内部类进行了关闭。现在,通过lambda表达式使该功能在语法上变得更加容易。因此,新功能最相关的方面可能是现在有lambda。称它们为lambda是正确的,它们的确是lambda。我不知道为什么Java 8作者可能选择不强调它们是闭包这一事实。
克劳迪(Claaudiu)

2
@AlexanderOrlov,因为Java 8 lambda不是真正的闭包,它们是闭包的模拟。它们更类似于Python 2.3闭包(没有可变性,因此要求引用的变量必须是“有效最终”),并在内部编译为非闭包函数,这些函数将在封闭范围内引用的所有变量都视为隐藏参数。
Logan Pickup

7
@Claudiu我认为对特定语言实现(Python)的引用可能会使答案过于复杂。这个问题是完全与语言无关的(并且没有特定于语言的标签)。
马修(马修)

318

即使在此StackOverflow问题的答案中,lambda和闭包也有很多困惑。与其去问那些从某些编程语言的实践中学到闭包的随机程序员或其他笨拙的程序员,不如去探寻源头(一切从此开始)。而且,由于lambda表达式和封锁来自演算发明了在20世纪30年代第一台电子计算机,甚至出现之前邱奇回来了,这是我谈论。

Lambda微积分是世界上最简单的编程语言。您只能在其中执行以下操作:►

  • 应用:将一个表达式应用于另一个,表示为f x
    (将其视为函数调用,其中f,函数x是它的唯一参数)
  • 摘要:绑定出现在表达式中的符号,以标记该符号只是一个“槽”,一个等待填充值的空白框,一个“变量”。可以通过在希腊字母λ(lambda)之前加上符号名称(例如x),再.在表达式之前加一个小圆点来完成。然后,将表达式转换为需要一个参数函数。例如:使用表达式,并告诉该表达式中的符号是一个绑定变量 –可以用您提供的值作为参数替换它。 请注意,以这种方式定义的函数是匿名的
    λx.x+2x+2x
    –它没有名称,因此您尚不能引用它,但是您可以通过为其提供等待参数来立即调用它(还记得应用程序吗?),如下所示:(λx.x+2) 7。然后,将表达式(在这种情况下为文字值)7替换为所应用的lambda x的子表达式x+2,因此您得到7+2,然后9通过通用算术规则将其简化为。

因此,我们已经解决了其中的奥秘:
拉姆达匿名函数从上面的例子,λx.x+2


在不同的编程语言中,功能抽象(lambda)的语法可能不同。例如,在JavaScript中,它看起来像这样:

function(x) { return x+2; }

并且您可以立即将其应用于如下所示的某些参数:

(function(x) { return x+2; })(7)

或者您可以将此匿名函数(lambda)存储到一些变量中:

var f = function(x) { return x+2; }

有效地为其命名f,允许您引用它并在以后多次调用它,例如:

alert(  f(7) + f(10)  );   // should print 21 in the message box

但是您不必命名。您可以立即调用它:

alert(  function(x) { return x+2; } (7)  );  // should print 9 in the message box

在LISP中,lambda是这样制作的:

(lambda (x) (+ x 2))

您可以通过将其立即应用于参数来调用这样的lambda:

(  (lambda (x) (+ x 2))  7  )


好了,现在该解决另一个谜团了:什么是闭包。为此,我们来讨论一下lambda表达式中的符号变量)。

就像我说的那样,lambda抽象所做的是在符号的子表达式中绑定符号,以便它成为可替换的参数。这样的符号称为绑定。但是,如果表达式中还有其他符号怎么办?例如:λx.x/y+2。在此表达式中,该符号x受其λx.前面的lambda抽象约束。但是另一个符号y没有限制-它是免费的。我们不知道它是什么以及它来自何处,所以我们也不知道它的含义以及它代表什么价值,因此,在弄清楚y含义之前,我们无法评估该表达式。

实际上,其他两个符号2和也是如此+。只是我们对这两个符号非常熟悉,以至于我们通常会忘记计算机不知道它们,而我们需要通过在某个地方定义它们(例如在库或语言本身中)来告诉它们的含义。

您可以在表达式外部的“周围环境”中将其定义为自由符号,这被称为环境。环境可能是该表达式的一部分(如Qui-Gon Jinn所说:“总是有一条更大的鱼”;)),或者在某个库中,或者在语言本身(作为原始语言)中。

这使我们可以将lambda表达式分为两类:

  • CLOSED表达式:出现在这些表达式中的每个符号都受到一些lambda抽象的约束。换句话说,它们是独立的。他们不需要评估任何周围的环境。它们也称为组合器
  • OPEN表达式:这些表达式中的某些符号未绑定 -也就是说,其中出现的某些符号是自由的,它们需要一些外部信息,因此,只有提供这些符号的定义后,才能对其进行求值。

您可以通过提供环境来关闭打开的 lambda表达式,该环境通过将所有这些自由符号绑定到某些值(可以是数字,字符串,匿名函数又称为lambda,等等)来定义它们。

和这里来的闭合部分:
封闭件lambda表达式是该特定组中的外上下文(环境),让值所定义的符号的分类符号在该表达式中,使得它们非自由了。它将一个开放的 lambda表达式(仍然包含一些“未定义”的自由符号)转换为一个封闭的表达式,该表达式不再具有任何自由符号。

例如,如果您有以下lambda表达式:λx.x/y+2,符号x的束缚,而符号y是免费的,因此表达的是open,除非你说不能评价什么y手段(与同+2,这也是免费的)。但是,假设您也有这样的环境

{  y: 3,
+: [built-in addition],
2: [built-in number],
q: 42,
w: 5  }

这种环境用品所有的“不确定”(免费),从我们的lambda表达式符号定义(y+2),和一些额外的符号(qw)。我们需要定义的符号是环境的以下子集:

{  y: 3,
+: [built-in addition],
2: [built-in number]  }

而这恰恰是我们的lambda表达式:>

换句话说,它关闭一个打开的lambda表达式。这是名称闭包最初来自的地方,这就是为什么这个线程中这么多人的答案不太正确的原因:P


那么为什么他们会误解呢?为什么这么多人说闭包是内存中的某些数据结构,或者它们使用的语言的某些功能,或者为什么将闭包与lambda混淆?:P

好吧,应该指责Sun / Oracle,Microsoft,Google等公司的企业市场,因为这就是他们在其语言(Java,C#,Go等)中所说的这些构造。他们通常称其为“闭包”(lambda)。或者,他们将“闭包”称为一种用于实现词法作用域的特定技术,也就是说,一个函数可以在定义时访问在其外部范围内定义的变量。他们经常说函数将这些变量“封闭”起来,也就是说,将它们捕获到某种数据结构中,以防止它们在外部函数完成执行后被破坏。但这只是事后 “民俗语源”和市场营销的虚构内容,只会使事情更加混乱,

而且,由于他们所说的话总是有些道理,这甚至更糟,这不允许您轻易将其视为假:P让我解释一下:

如果要实现使用lambda作为一等公民的语言,则需要允许它们使用在其周围上下文中定义的符号(即,在lambda中使用自由变量)。而且即使周围的函数返回,这些符号也必须存在。问题在于这些符号已绑定到函数的某些本地存储(通常在调用堆栈上),当函数返回时,它们将不再存在。因此,为了使lambda能够按您期望的方式工作,您需要以某种方式从其外部上下文中“捕获”所有这些自由变量,并将其保存以供以后使用,即使外部上下文将消失。也就是说,您需要找到闭包的lambda(它使用的所有这些外部变量)并将其存储在其他位置(通过复制或为它们准备空间,而不是在堆栈上的其他位置)。实现此目标的实际方法是语言的“实现细节”。这里重要的是闭包闭包是来自lambda 环境的一组自由变量,需要保存在某个地方。

人们花了很长时间才开始调用他们在语言实现中使用的实际数据结构,以将闭包实现为“闭包”本身。该结构通常如下所示:

Closure {
   [pointer to the lambda function's machine code],
   [pointer to the lambda function's environment]
}

这些数据结构作为参数传递给其他函数,从函数返回并存储在变量中以表示lambda,并允许它们访问其封闭环境以及在该上下文中运行的机器代码。但它只是一种方式(许多之一)来实现闭合,不能封闭自己。

如上文所述,lambda表达式的关闭是其环境中定义的子集,这些定义为该lambda表达式中包含的自由变量提供值,从而有效地关闭了该表达式(将尚无法评估的开放 lambda表达式转换为一个封闭的 lambda表达式,然后可以对其进行评估,因为现在已定义其中包含的所有符号)。

其他任何东西都只是程序员和语言供应商的“货运邪教”和“ voo-doo魔术”,他们并未意识到这些概念的真正根源。

我希望能回答您的问题。但是,如果您有任何后续问题,请随时在评论中提问,我会尽力解释。


50
最佳答案解释一般的东西,而不是特定语言
Shishir阿罗拉

39
在解释事物时,我喜欢这种方法。从一开始就解释了事情是如何工作的,然后解释了当前的误解是如何产生的。此答案需要转到顶部。
Sharky

1
@SasQ> lambda表达式的关闭是其环境中定义的子集,这些定义为该lambda表达式中包含的自由变量提供值<因此,在您的示例数据结构中,这是否意味着说“指向以下内容的指针”更合适lambda函数的环境”是关闭吗?
杰夫

3
尽管Lambda演算对我来说就像是机器语言,但我必须同意这是一种与“人造”语言相反的“发现”语言。因此,它不受制于任意约定,而更适合于捕获现实的基础结构。我们可以在Linq,JavaScript,F#中找到更容易接近的细节,但是Lambda演算可谓一事无成。
StevePoling,

1
我感谢您多次重申自己的观点,每次措辞略有不同。它有助于加强概念。我希望更多的人这样做。
johnklawlor

175

当大多数人想到函数时,他们想到的是命名函数

function foo() { return "This string is returned from the 'foo' function"; }

当然,这些被称为名称:

foo(); //returns the string above

使用lambda表达式,您可以具有匿名函数

 @foo = lambda() {return "This is returned from a function without a name";}

在上述示例中,您可以通过分配给它的变量调用lambda:

foo();

但是,与将匿名函数分配给变量相比,更有用的是将它们传递给高阶函数或从高阶函数传递(即,接受/返回其他函数的函数)。在许多情况下,命名函数是不必要的:

function filter(list, predicate) 
 { @filteredList = [];
   for-each (@x in list) if (predicate(x)) filteredList.add(x);
   return filteredList;
 }

//filter for even numbers
filter([0,1,2,3,4,5,6], lambda(x) {return (x mod 2 == 0)}); 

闭合可以命名或匿名的功能,但是是公知的,当它“关闭在...之上”的范围的变量,其中所述函数被定义,即,闭合将仍然参考环境与在所使用的任何外变量关闭本身。这是一个命名的闭包:

@x = 0;

function incrementX() { x = x + 1;}

incrementX(); // x now equals 1

看起来似乎不多,但是如果全部都在另一个函数中并且您将其传递incrementX给外部函数呢?

function foo()
 { @x = 0;

   function incrementX() 
    { x = x + 1;
      return x;
    }

   return incrementX;
 }

@y = foo(); // y = closure of incrementX over foo.x
y(); //returns 1 (y.x == 0 + 1)
y(); //returns 2 (y.x == 1 + 1)

这就是在函数式编程中如何获取有状态对象。由于不需要命名“ incrementX”,因此在这种情况下可以使用lambda:

function foo()
 { @x = 0;

   return lambda() 
           { x = x + 1;
             return x;
           };
 }

14
你这是什么语言
克劳迪(Claudiu)

7
它基本上是伪代码。其中包含一些Lisp和JavaScript,以及我正在设计的一种名为“ @”(“ at”)的语言,以变量声明操作符命名。
马克·西达德

3
@MarkCidade,那么这种语言@在哪里?有文档和下载吗?
Pacerier,2013年

5
为什么不采用Javascript并添加以前导@符号声明变量的约束?这样可以节省时间:)
Nemoden

5
@Pacerier:我开始实现该语言:github.com/marxidad/At2015
Mark Cidade

54

并非所有的闭包都是lambda,也不是所有的lambda都是闭包。两者都是函数,但不一定以我们习惯的方式知道。

Lambda本质上是内联定义的函数,而不是声明函数的标准方法。Lambda经常可以作为对象传递。

闭包是通过引用其主体外部的字段来封闭其周围状态的功能。封闭状态在关闭的所有调用中保持不变。

在面向对象的语言中,闭包通常是通过对象提供的。但是,某些OO语言(例如C#)实现的特殊功能更接近于由纯功能语言(例如lisp)提供的闭包的定义,而闭包没有对象来封装状态。

有趣的是,C#中Lambda和Closure的引入使函数式编程更接近于主流用法。


那么,我们可以说闭包是lambda的子集,而lambda是函数的子集吗?
sker

闭包是lambda的子集...但是lambda比普通函数更特殊。就像我说的那样,lambda是内联定义的。从本质上讲,除非将它们传递给另一个函数或作为返回值返回,否则无法引用它们。
迈克尔·布朗

19
Lambda和闭包分别是所有函数的子集,但是Lambda和闭包之间只有一个交集,闭包的不相交部分将被称为函数,即闭包,而不相交的lamda是具有完整功能的自包含函数。绑定变量。
马克·西达德

在我看来,lambda是比功能更基本的概念。它确实取决于编程语言。
魏秋

15

就是这么简单:lambda是一种语言构造,即匿名函数的简单语法;闭包是一种实现它的技术-或任何一流的函数,无论是命名函数还是匿名函数。

更确切地说,闭包是在运行时如何将一等函数表示为一对“代码”和一个环境,“闭包”该代码中使用的所有非局部变量。这样,即使已经退出了这些变量的外部作用域,也仍然可以访问这些变量。

不幸的是,有许多语言不支持将函数作为一等值,或仅以残缺的形式支持它们。因此,人们经常使用“封闭”一词来区分“真实的事物”。


12

从编程语言的角度来看,它们完全是两件事。

基本上,对于图灵完整的语言,我们只需要非常有限的元素,例如抽象,应用和简化。抽象和应用程序提供了构建lamdba表达的方式,而归约法则确定了lambda表达的含义。

Lambda提供了一种抽象计算过程的方法。例如,为了计算两个数字的和,可以抽象出一个过程,该过程采用两个参数x,y并返回x + y。在方案中,您可以将其写为

(lambda (x y) (+ x y))

您可以重命名参数,但是它完成的任务不会改变。在几乎所有的编程语言中,您都可以给lambda表达式命名,即函数。但是并没有太大的区别,它们在概念上可以看作只是语法糖。

好,现在想象一下如何实现。每当我们将lambda表达式应用于某些表达式时,例如

((lambda (x y) (+ x y)) 2 3)

我们可以简单地将参数替换为要评估的表达式。该模型已经非常强大。但是该模型无法使我们更改符号的值,例如,我们无法模仿状态的更改。因此,我们需要一个更复杂的模型。简而言之,每当我们要计算lambda表达式的含义时,我们都将一对符号和相应的值放入环境(或表)中。然后,通过查找表中的相应符号来评估其余(+ xy)。现在,如果我们提供一些直接在环境上运行的原语,就可以对状态的变化进行建模!

在此背景下,请检查以下功能:

(lambda (x y) (+ x y z))

我们知道,当我们评估lambda表达式时,xy将绑定到新表中。但是我们如何以及在哪里查找z?实际上,z称为自由变量。必须有一个包含z的外部环境。否则,仅通过绑定x和y不能确定表达式的含义。为了清楚起见,您可以在scheme中编写以下内容:

((lambda (z) (lambda (x y) (+ x y z))) 1)

因此,z将在外部表中绑定为1。我们仍然得到一个接受两个参数的函数,但是它的真正含义还取决于外部环境。换句话说,外部环境在自由变量上关闭。借助set !,我们可以使该函数成为有状态的,即,它不是数学意义上的函数。它返回的内容不仅取决于输入,还取决于z。

这是您已经非常了解的东西,对象的方法几乎总是依赖于对象的状态。这就是为什么有人说“闭包是穷人的对象。”但是我们也可以将对象视为穷人的对象,因为我们真的很喜欢一流的功能。

我使用方案来说明想法,因为该方案是最早的具有真正封闭性的语言之一。SICP第3章将更好地介绍此处的所有材料。

总而言之,lambda和闭包实际上是不同的概念。Lambda是一个函数。闭包是一对lambda,以及关闭lambda的相应环境。


因此,可以用嵌套的lambda替换所有闭包,直到不再存在任何自由变量为止?在这种情况下,我想说闭包可以看作是一种特殊的lambda。
Trilarion 2014年

8

概念与上面描述的相同,但是如果您来自PHP背景,这将进一步说明如何使用PHP代码。

$input = array(1, 2, 3, 4, 5);
$output = array_filter($input, function ($v) { return $v > 2; });

函数($ v){返回$ v> 2; }是lambda函数定义。我们甚至可以将其存储在变量中,因此可以重用:

$max = function ($v) { return $v > 2; };

$input = array(1, 2, 3, 4, 5);
$output = array_filter($input, $max);

现在,如果您想更改过滤后的数组中允许的最大数量,该怎么办?您将不得不编写另一个lambda函数或创建一个闭包(PHP 5.3):

$max_comp = function ($max) {
  return function ($v) use ($max) { return $v > $max; };
};

$input = array(1, 2, 3, 4, 5);
$output = array_filter($input, $max_comp(2));

闭包是一种在其自身环境中求值的函数,该函数具有一个或多个绑定变量,调用该函数时可以访问它们。它们来自函数式编程世界,其中包含许多概念。闭包就像lambda函数一样,但是在某种意义上说更聪明,因为它们具有与定义闭包的外部环境中的变量进行交互的能力。

这是一个PHP关闭的简单示例:

$string = "Hello World!";
$closure = function() use ($string) { echo $string; };

$closure();

这篇文章很好地解释了。


7

这个问题很老,有很多答案。
现在有了非官方的封闭项目Java 8和Official Lambda,它使这个问题复活了。

在Java上下文中的答案(通过Lambda和闭包-有什么区别?):

“闭包是一个lambda表达式,它与将其每个自由变量绑定到一个值的环境配对。在Java中,lambda表达式将通过闭包来实现,因此这两个术语在社区中已可以互换使用。”


Lamdas如何通过Java闭包实现?这是否意味着Lamdas表达式将转换为旧的匿名类?
hackjutsu

5

简单地说,闭包是关于范围的一个技巧,lambda是一个匿名函数。我们可以更优雅地使用lambda实现闭包,并且lambda通常用作传递给更高功能的参数


4

Lambda表达式只是一个匿名函数。例如,在纯Java中,您可以这样编写:

Function<Person, Job> mapPersonToJob = new Function<Person, Job>() {
    public Job apply(Person person) {
        Job job = new Job(person.getPersonId(), person.getJobDescription());
        return job;
    }
};

其中Function类是用Java代码构建的。现在您可以在mapPersonToJob.apply(person)某个地方打电话使用它。那只是一个例子。在有语法之前,这就是一个lambda。Lambdas为此的捷径。

关闭:

当Lambda可以访问此范围之外的变量时,它就成为闭包。我猜你可以说它的魔力,它可以神奇地环绕它在其中创建的环境,并使用其作用域(外部作用域)之外的变量。因此,很明显,闭包意味着lambda可以访问其外部作用域。

在Kotlin中,lambda始终可以访问其闭包(在其外部范围内的变量)


0

这取决于一个函数是否使用外部变量来执行操作。

外部变量 -在函数范围之外定义的变量。

  • Lambda表达式是无状态的,因为它依赖于参数,内部变量或常量来执行操作。

    Function<Integer,Integer> lambda = t -> {
        int n = 2
        return t * n 
    }
    
  • 闭包保持状态,因为它使用外部变量(即,在函数体范围之外定义的变量)以及参数和常量来执行操作。

    int n = 2
    
    Function<Integer,Integer> closure = t -> {
        return t * n 
    }
    

Java创建闭包时,它将变量n与函数一起保留,以便在传递给其他函数或在任何地方使用时都可以引用它。

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.