关于功能:’闭包’和’lambda’有什么区别?

What is the difference between a 'closure' and a 'lambda'?

有人能解释一下吗?我理解它们背后的基本概念,但我经常看到它们可以互换使用,我会感到困惑。

既然我们在这里,它们和正则函数有什么不同?


lambda只是一个匿名函数-一个没有名称定义的函数。在某些语言中,例如scheme,它们等价于命名函数。实际上,函数定义被重新编写为在内部将lambda绑定到变量。在其他语言中,比如python,它们之间有一些(相当不必要的)区别,但是它们的行为方式不同。

闭包是在定义它的环境中关闭的任何函数。这意味着它可以访问参数列表之外的变量。实例:

1
2
3
def func(): return h
def anotherfunc(h):
   return func()

这将导致一个错误,因为func不关闭anotherfunc中的环境,h未定义。func只关闭全球环境。这将起作用:

1
2
3
def anotherfunc(h):
    def func(): return h
    return func()

因为在这里,funcanotherfunc中定义,在python 2.3及更高版本(或类似的数字)中,当它们几乎得到正确的闭包(突变仍然不起作用)时,这意味着它在anotherfunc的环境中关闭,并且可以访问其中的变量。在python 3.1+中,使用nonlocal关键字时,突变也起作用。

另一个重要的观点——func将继续关闭anotherfunc的环境,即使不再在anotherfunc中进行评估。此代码也适用于:

1
2
3
4
5
def anotherfunc(h):
    def func(): return h
    return func

print anotherfunc(10)()

这将打印10。

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


关于lambda和闭包有很多混淆,甚至在这里的stackoverflow问题的答案中也是如此。不要问随机的程序员谁从实践中学习了闭包与某些编程语言或其他笨拙的程序员,而是走到源代码的旅程(一切都从那里开始)。因为lambdas和闭包来自于alonzo church在30年代发明的lambda微积分,早在第一台电子计算机出现之前,这就是我所说的源代码。好的。

lambda微积分是世界上最简单的编程语言。你唯一能做的事情是:?好的。

  • 应用:将一个表达式应用于另一个表达式,表示为f x
    (将其视为函数调用,其中f是函数,x是它的唯一参数)
  • 抽象:绑定表达式中出现的一个符号,以标记该符号只是一个"槽",一个等待填充值的空白框,和它原来的"变量"。它是通过在表达式前面加上一个希腊字母λ,然后加上一个符号名(例如x,再加上一个点.。然后,它将表达式转换为一个需要一个参数的函数。
    例如:λx.x+2接受表达式x+2并告知此表达式中的符号x是一个绑定变量–它可以用您提供的参数值替换。
    注意,以这种方式定义的函数是匿名的——它没有名称,所以您还不能引用它,但是您可以立即调用它(还记得应用程序吗?)通过向它提供它正在等待的参数,如:(λx.x+2) 7。然后,在应用lambda的子表达式x+2中,将表达式(在本例中是文字值)7替换为x,得到7+2,然后根据常用的算术规则将其简化为9

所以我们已经解开了其中一个谜团:
lambda是上面例子中的匿名函数,λx.x+2


在不同的编程语言中,函数抽象(lambda)的语法可能不同。例如,在javascript中,它看起来如下:好的。

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

您可以立即将其应用于如下参数:好的。

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

或者可以将此匿名函数(lambda)存储到某个变量中:好的。

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

这实际上给它起了一个名称f,允许您引用它并在以后多次调用它,例如:好的。

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

但你不必说出它的名字。你可以马上打电话给它:好的。

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

在Lisp中,lambda的制作如下:好的。

1
(lambda (x) (+ x 2))

您可以通过将lambda立即应用于参数来调用它:好的。

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


好吧,现在是时候解决另一个谜团了:什么是终结。为了做到这一点,我们来谈谈lambda表达式中的符号(变量)。好的。

如我所说,lambda抽象所做的就是在其子表达式中绑定一个符号,使其成为一个可替换的参数。这样的符号称为绑定。但是如果表达式中还有其他符号呢?例如:λx.x/y+2。在这个表达式中,符号x由它前面的lambda抽象λx.绑定。但是另一个符号,y,是不受约束的,它是自由的。我们不知道它是什么,从何而来,所以我们不知道它是什么意思,它代表了什么价值,因此,在我们弄清楚y的意思之前,我们无法评估这个表达式。好的。

事实上,其他两个符号也一样,分别是2+。仅仅是因为我们对这两个符号如此熟悉,以至于我们常常忘记计算机不知道它们,我们需要通过在某个地方定义它们来告诉它它们的含义,例如在图书馆或语言本身。好的。

你可以把自由符号看作是在表达式之外的其他地方定义的,在它的"周围环境"中,这被称为它的环境。环境可能是一个更大的表达,这个表达是其中的一部分(正如魁刚金所说:"总是有一条更大的鱼"),或者在一些图书馆,或者在语言本身(作为一个原始人)。好的。

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

  • 封闭表达式:这些表达式中出现的每个符号都受一些lambda抽象的约束。换句话说,它们是独立的;它们不需要评估任何周围环境。它们也被称为组合器。
  • 开放式表达式:这些表达式中的某些符号没有绑定——也就是说,其中出现的一些符号是自由的,它们需要一些外部信息,因此,在提供这些符号的定义之前,无法对它们进行计算。

您可以通过提供环境来关闭一个打开的lambda表达式,该环境通过将这些自由符号绑定到一些值(可能是数字、字符串、匿名函数,也就是lambda,随便什么…)来定义这些自由符号。好的。

这里是结束部分:
lambda表达式的闭包是在外部上下文(环境)中定义的一组特殊符号,这些符号为表达式中的自由符号赋予值,使它们不再自由。它将一个仍然包含一些"未定义"的自由符号的开放lambda表达式转换为一个不再有任何自由符号的封闭表达式。好的。

例如,如果您有以下lambda表达式:λx.x/y+2,则符号x是绑定的,而符号y是自由的,因此表达式是open,除非您说出y的含义(与+2相同,它也是自由的),否则无法计算。但是假设您也有这样的环境:好的。

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

这个环境从lambda表达式(y+2和几个额外的符号(qw提供了所有"未定义"(自由)符号的定义。我们需要定义的符号是环境的这个子集:好的。

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

这正是lambda表达式的闭包:>好的。

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


那他们为什么搞错了呢?为什么这么多闭包会说闭包是内存中的一些数据结构,或者它们使用的语言的一些特性,或者为什么它们会将闭包与lambda混淆?P好的。

嗯,Sun/Oracle、微软、谷歌等公司的市场营销模式应该受到指责,因为这就是他们所说的这些结构的语言(Java、C语言、GO等)。他们经常称之为"闭幕式",这应该只是兰姆达斯。或者他们称"闭包"为他们用来实现词汇范围的一种特殊技术,也就是说,函数可以访问定义时在其外部范围中定义的变量。他们经常说函数"包含"这些变量,也就是说,将它们捕获到一些数据结构中,以避免在外部函数完成执行后被破坏。但这仅仅是由事后的"民俗学词源学"和市场营销组成的,这只会让事情变得更加混乱,因为每个语言供应商都使用自己的术语。好的。

更糟糕的是,他们所说的话总是有一点真实性,这不允许你轻易地将其视为错误:p让我解释一下:好的。

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

人们很快就开始调用他们在语言实现中使用的实际数据结构来实现闭包,称之为"闭包"本身。结构通常如下所示:好的。

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

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

如上所述,lambda表达式的闭包是其环境中定义的子集,这些定义为lambda表达式中包含的自由变量提供值,从而有效地关闭表达式(将尚未计算的打开lambda表达式转换为可计算的关闭lambda表达式,since其中包含的所有符号现在都已定义)。好的。

其他任何东西都只是程序员和语言供应商不知道这些概念的真正根源的"货物崇拜"和"voo doo魔法"。好的。

我希望能回答你的问题。但是如果你有任何后续问题,可以在评论中问他们,我会尽量解释清楚。好的。好啊。


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

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

当然,这些都是通过名字来命名的:

1
foo(); //returns the string above

使用lambda表达式,可以使用匿名函数:

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

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

1
foo();

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

1
2
3
4
5
6
7
8
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)});

闭包可以是一个命名的或匿名的函数,但是当它在定义函数的作用域中"关闭"变量时,也就是说,闭包仍然引用包含闭包本身使用的任何外部变量的环境。以下是一个命名的结束:

1
2
3
4
5
@x = 0;

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

incrementX(); // x now equals 1

这看起来不太像,但是如果这都是在另一个函数中,而您将incrementX传递给了一个外部函数呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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:

1
2
3
4
5
6
7
8
function foo()
 { @x = 0;

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


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

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

闭包是通过引用其主体外部的字段来封闭其周围状态的函数。封闭状态在调用闭包时保持不变。

在面向对象的语言中,闭包通常是通过对象提供的。但是,一些OO语言(例如C)实现了更接近于纯函数语言(例如Lisp)提供的闭包定义的特殊功能,这些语言没有对象来包含状态。

有趣的是,在C中引入lambda和闭包使函数式编程更接近主流用法。


简单地说:lambda是一种语言构造,也就是说,它只是匿名函数的语法;闭包是一种实现它的技术——或者说是任何一类函数,不管是命名的还是匿名的。

更准确地说,闭包是一个一级函数在运行时如何表示的,它是一对"代码"和一个环境"闭包",覆盖了代码中使用的所有非局部变量。通过这种方式,即使这些变量源自的外部作用域已经退出,这些变量仍然可以访问。

不幸的是,有许多语言不支持作为第一类值的函数,或者只支持残缺形式的函数。所以人们经常用"闭包"这个词来区分"真实的事物"。


从编程语言的角度来看,它们完全是两种不同的东西。

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

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

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

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

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

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

我们可以简单地用要计算的表达式替换参数。这个型号已经很强大了。但是这个模型不能改变符号的值,例如我们不能模仿状态的变化。因此我们需要一个更复杂的模型。为了简短起见,每当我们想要计算lambda表达式的含义时,我们都将符号对和相应的值放入一个环境(或表)中。然后,通过查找表中相应的符号来计算其余的(+x y)。现在,如果我们提供一些原语来直接对环境进行操作,我们就可以对状态的变化进行建模!

在此背景下,检查此函数:

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

我们知道,当我们计算lambda表达式时,x y将被绑定到一个新表中。但是我们如何以及在哪里可以找到z呢?实际上z被称为自由变量。一定有一个外皮包含z的环境。否则,表达式的含义不能仅通过绑定x和y来确定。为了清楚地说明这一点,您可以在Scheme中编写如下内容:

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

所以z将绑定到外表中的1。我们仍然得到一个接受两个参数的函数,但它的真正含义也取决于外部环境。换言之,外部环境关闭自由变量。在赛特的帮助下!我们可以使函数有状态,也就是说,它不是数学意义上的函数。它返回的结果不仅取决于输入,还取决于z。

这是您已经非常熟悉的东西,对象的方法几乎总是依赖于对象的状态。这就是为什么有些人说"封闭是穷人的东西"。但我们也可以将对象视为穷人的闭包,因为我们真的很喜欢一流的函数。

我用Scheme来说明这个方案的思想,因为它是最早有真正闭包的语言之一。这里的所有材料在SICP第3章中都有更好的介绍。

总而言之,lambda和闭包是完全不同的概念。lambda是一个函数。闭包是一对lambda和关闭lambda的相应环境。


这个问题由来已久,有很多答案。现在,Java 8和官方lambda是非官方的封闭项目,它复活了这个问题。

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

"A closure is a lambda expression paired with an environment that binds each of its free variables to a value. In Java, lambda expressions will be implemented by means of closures, so the two terms have come to be used interchangeably in the community."


概念和上面描述的一样,但如果您来自PHP背景,这将进一步解释使用PHP代码。

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

函数($v)返回$v>2;是lambda函数定义。我们甚至可以将它存储在一个变量中,这样它就可以重用:

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

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

现在,如果要更改筛选数组中允许的最大数目,该怎么办?您必须编写另一个lambda函数或创建一个闭包(php 5.3):

1
2
3
4
5
6
$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闭包示例:

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

$closure();

这篇文章解释得很好。


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


lambda表达式只是一个匿名函数。例如,在普通Java中,你可以这样写:

1
2
3
4
5
6
Function<Person, Job> mapPersonToJob = new Function<Person, Job>() {
    public Job apply(Person person) {
        Job job = new Job(person.getPersonId(), person.getJobDescription());
        return job;
    }
};

其中类函数只是用Java代码构建的。现在您可以在某个地方调用mapPersonToJob.apply(person)来使用它。这只是一个例子。这是一个lambda,在它有语法之前。兰布达对此很感兴趣。

关闭:

当lambda可以访问这个范围之外的变量时,它就成为一个闭包。我想你可以说它很神奇,它可以神奇地环绕它所创建的环境,并在它的作用域(外部作用域)之外使用变量。所以要明确一点,闭包意味着lambda可以访问其外部范围。

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


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

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

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

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

    1
    2
    3
    4
    5
    int n = 2

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

当Java创建闭包时,它将变量N保持在函数中,这样当传递到其他函数或在任何地方使用时,它都可以被引用。