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

现在我们到了这里,它们和普通函数有什么不同?


当前回答

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

基本上,对于图灵完备语言,我们只需要非常有限的元素,例如抽象、应用和还原。抽象和应用提供了构建lambda表达式的方法,而约简决定了lambda表达式的含义。

Lambda提供了一种将计算过程抽象出来的方法。 例如,要计算两个数字的和,可以抽象出一个接受两个参数x、y并返回x+y的过程。在scheme中,可以写成

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

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

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

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

We can simply substitute the parameters with the expression to be evaluated. This model is already very powerful. But this model doesn't enable us to change the values of symbols, e.g. We can't mimic the change of status. Thus we need a more complex model. To make it short, whenever we want to calculate the meaning of the lambda expression, we put the pair of symbol and the corresponding value into an environment(or table). Then the rest (+ x y) is evaluated by looking up the corresponding symbols in the table. Now if we provide some primitives to operate on the environment directly, we can model the changes of status!

有了这个背景,检查这个函数:

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

我们知道,当我们求lambda表达式的值时,x y将被绑定到一个新的表中。但是我们怎样才能查到z呢?实际上z是一个自由变量。一定有一个外在的 否则,表达式的含义不能仅通过绑定x和y来确定。为了清楚地说明这一点,可以在scheme中这样写:

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

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

这是你们已经非常了解的东西,对象的方法几乎总是依赖于对象的状态。这就是为什么有人说“闭包是穷人的东西”。但我们也可以把对象看作穷人的闭包,因为我们真的喜欢第一类函数。

我用scheme来说明这个想法,因为scheme是最早的有真正闭包的语言之一。这里的所有材料在SICP第3章中都有更好的呈现。

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

其他回答

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

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

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;
           };
 }

关于lambda和闭包有很多困惑,甚至在这个StackOverflow问题的答案中也是如此。与其询问那些从某些编程语言的实践中学习闭包的随机程序员或其他一无所知的程序员,不如去寻找源头(一切开始的地方)。因为Lambda和闭包来自于Lambda微积分,由Alonzo Church在30年代发明,那时第一台电子计算机还不存在,这就是我说的来源。

Lambda Calculus是世界上最简单的编程语言。你唯一能做的事情是:►

APPLICATION: Applying one expression to another, denoted f x.(Think of it as a function call, where f is the function and x is its only parameter) ABSTRACTION: Binds a symbol occurring in an expression to mark that this symbol is just a "slot", a blank box waiting to be filled with value, a "variable" as it were. It is done by prepending a Greek letter λ (lambda), then the symbolic name (e.g. x), then a dot . before the expression. This then converts the expression into a function expecting one parameter.For example: λx.x+2 takes the expression x+2 and tells that the symbol x in this expression is a bound variable – it can be substituted with a value you supply as a parameter. Note that the function defined this way is anonymous – it doesn't have a name, so you can't refer to it yet, but you can immediately call it (remember application?) by supplying it the parameter it is waiting for, like this: (λx.x+2) 7. Then the expression (in this case a literal value) 7 is substituted as x in the subexpression x+2 of the applied lambda, so you get 7+2, which then reduces to 9 by common arithmetics rules.

所以我们解开了其中一个谜团: 是上面例子中的匿名函数,λ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表达式中的符号(变量)。

As I said, what the lambda abstraction does is binding a symbol in its subexpression, so that it becomes a substitutible parameter. Such a symbol is called bound. But what if there are other symbols in the expression? For example: λx.x/y+2. In this expression, the symbol x is bound by the lambda abstraction λx. preceding it. But the other symbol, y, is not bound – it is free. We don't know what it is and where it comes from, so we don't know what it means and what value it represents, and therefore we cannot evaluate that expression until we figure out what y means.

事实上,另外两个符号2和+也是如此。只是我们对这两个符号太熟悉了,以至于我们经常忘记计算机不认识它们,我们需要通过在某个地方定义它们来告诉它它们的意思,例如在图书馆或语言本身。

你可以认为自由符号是在表达式之外的其他地方定义的,在它的“周围上下文”中,这被称为它的环境。环境可能是一个更大的表达式,这个表达式是其中的一部分(就像奎冈·金说的:“总有更大的鱼”;)),或者在一些库中,或者在语言本身中(作为原始语言)。

这让我们将lambda表达式分为两类:

CLOSED表达式:出现在这些表达式中的每个符号都被一些lambda抽象绑定。换句话说,它们是独立的;它们不需要任何周围的上下文来计算。它们也被称为组合子。 OPEN表达式:这些表达式中的一些符号是不绑定的——也就是说,其中出现的一些符号是自由的,它们需要一些外部信息,因此在您提供这些符号的定义之前,它们不能被求值。

你可以通过提供环境来关闭一个开放lambda表达式,环境通过将这些自由符号绑定到一些值(可能是数字、字符串、匿名函数或lambdas等等)来定义所有这些自由符号。

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

例如,如果你有这样的λ表达式:λx。X /y+2,符号X是有界的,而符号y是自由的,因此表达式是开放的,除非你说出y的意思,否则无法计算(+和2也是一样,它们也是自由的)。但假设你也有一个这样的环境:

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

这个环境为lambda表达式(y, +, 2)中的所有“未定义”(自由)符号和几个额外的符号(q, w)提供定义。我们需要定义的符号是环境的这个子集:

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

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

换句话说,它关闭了一个开lambda表达式。这就是名称闭包最初的来源,这也是为什么在这个帖子中有那么多人的答案不太正确的原因:P 那么,他们为什么错了呢?为什么这么多人说闭包是内存中的一些数据结构,或者他们使用的语言的一些特性,或者为什么他们把闭包和lambdas混淆?: P

Well, the corporate marketoids of Sun/Oracle, Microsoft, Google etc. are to blame, because that's what they called these constructs in their languages (Java, C#, Go etc.). They often call "closures" what are supposed to be just lambdas. Or they call "closures" a particular technique they used to implement lexical scoping, that is, the fact that a function can access the variables that were defined in its outer scope at the time of its definition. They often say that the function "encloses" these variables, that is, captures them into some data structure to save them from being destroyed after the outer function finishes executing. But this is just made-up post factum "folklore etymology" and marketing, which only makes things more confusing, because every language vendor uses its own terminology.

更糟糕的是,他们说的话总有一点是真的,这让你不能轻易地把它当作错误来看待。

If you want to implement a language that uses lambdas as first-class citizens, you need to allow them to use symbols defined in their surrounding context (that is, to use free variables in your lambdas). And these symbols must be there even when the surrounding function returns. The problem is that these symbols are bound to some local storage of the function (usually on the call stack), which won't be there anymore when the function returns. Therefore, in order for a lambda to work the way you expect, you need to somehow "capture" all these free variables from its outer context and save them for later, even when the outer context will be gone. That is, you need to find the closure of your lambda (all these external variables it uses) and store it somewhere else (either by making a copy, or by preparing space for them upfront, somewhere else than on the stack). The actual method you use to achieve this goal is an "implementation detail" of your language. What's important here is the closure, which is the set of free variables from the environment of your lambda that need to be saved somewhere.

没过多久,人们就开始将他们在语言实现中使用的实际数据结构称为“闭包”本身。结构通常是这样的:

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

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

如上所述,lambda表达式的闭包是其环境中定义的子集,这些定义将值赋给该lambda表达式中包含的自由变量,从而有效地关闭表达式(将目前还不能求值的开放lambda表达式转换为随后可以求值的封闭lambda表达式,因为现在已经定义了其中包含的所有符号)。

其他任何东西都只是程序员和语言供应商的“货物崇拜”和“巫毒魔法”,他们不知道这些概念的真正根源。

我希望这能回答你的问题。但如果你有任何后续问题,请在评论中提出,我会试着更好地解释。

概念与上面描述的相同,但如果您有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();

这篇文章解释得很好。

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

基本上,对于图灵完备语言,我们只需要非常有限的元素,例如抽象、应用和还原。抽象和应用提供了构建lambda表达式的方法,而约简决定了lambda表达式的含义。

Lambda提供了一种将计算过程抽象出来的方法。 例如,要计算两个数字的和,可以抽象出一个接受两个参数x、y并返回x+y的过程。在scheme中,可以写成

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

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

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

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

We can simply substitute the parameters with the expression to be evaluated. This model is already very powerful. But this model doesn't enable us to change the values of symbols, e.g. We can't mimic the change of status. Thus we need a more complex model. To make it short, whenever we want to calculate the meaning of the lambda expression, we put the pair of symbol and the corresponding value into an environment(or table). Then the rest (+ x y) is evaluated by looking up the corresponding symbols in the table. Now if we provide some primitives to operate on the environment directly, we can model the changes of status!

有了这个背景,检查这个函数:

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

我们知道,当我们求lambda表达式的值时,x y将被绑定到一个新的表中。但是我们怎样才能查到z呢?实际上z是一个自由变量。一定有一个外在的 否则,表达式的含义不能仅通过绑定x和y来确定。为了清楚地说明这一点,可以在scheme中这样写:

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

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

这是你们已经非常了解的东西,对象的方法几乎总是依赖于对象的状态。这就是为什么有人说“闭包是穷人的东西”。但我们也可以把对象看作穷人的闭包,因为我们真的喜欢第一类函数。

我用scheme来说明这个想法,因为scheme是最早的有真正闭包的语言之一。这里的所有材料在SICP第3章中都有更好的呈现。

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