'闭包'和'lambda'有什么区别?

时间:2008-10-21 03:12:55

标签: function lambda functional-programming closures

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

现在我们在这里,它们与常规功能有什么不同?

13 个答案:

答案 0 :(得分:652)

lambda 只是一个匿名函数 - 一个没有名称的函数。在某些语言中,例如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()

因为在这里,funcanotherfunc中定义,并且在python 2.3及更高版本(或者像这样的某个数字)中,当它们几乎得到闭包时(变异仍然没有)不行,这意味着关闭 anotherfunc的环境并可以访问其中的变量。在Python 3.1+中,使用the nonlocal keyword时,变异也起作用。

另一个重点 - func将继续关闭anotherfunc的环境,即使它不再在anotherfunc中进行评估。此代码也可以使用:

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

print anotherfunc(10)()

这将打印10。

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

答案 1 :(得分:210)

围绕lambdas和闭包存在很多混淆,即使在这个StackOverflow问题的答案中也是如此。而不是要求随机程序员从某些编程语言或其他无能的程序员那里学习关于实践的闭包,而是前往 source (这一切都开始了)。由于lambdas和闭包来自Alonzo Church发明的 Lambda Calculus ,在第一台电子计算机出现之前的30年代,这就是 I'我在谈论。

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

  • APPLICATION:将一个表达式应用于另一个表达式,表示为f x
    (将其视为函数调用,其中f是函数,{{ 1}}是唯一的参数)
  • 摘要:绑定表达式中出现的符号,以标记此符号只是一个"插槽",一个等待填充值的空白框,一个"变量"原样。这是通过在表达式之前添加希腊字母x(lambda),然后是符号名称(例如λ),然后是点x来完成的。然后,将表达式转换为函数,期待一个参数
    例如:.采用表达式λx.x+2并告诉此表达式中的符号x+2绑定变量 - 它可以替换为您提供的值作为参数。
    请注意,以这种方式定义的函数是匿名 - 它没有名称,因此您无法引用它,但您可以立即调用它(记住应用程序吗?)通过提供它正在等待的参数,如:x。然后,表达式(在本例中为文字值)(λx.x+2) 7在应用的lambda的子表达式7中替换为x,因此得到x+2,然后缩小为7+2通过常见的算术规则。

所以我们解决了其中一个谜团:
lambda 是上述示例中的匿名函数9

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

λx.x+2

你可以立即将它应用到这样的参数:

function(x) { return x+2; }

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

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

有效地为其命名var f = function(x) { return x+2; } ,允许您引用它并在以后多次调用它,例如:

f

但你没有必要说出它的名字。你可以马上叫它:

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

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

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

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

(lambda (x) (+ x 2))

<小时/> 好的,现在是解决另一个谜团的时候了:什么是闭包。 为此,请在lambda表达式中讨论 symbols variables )。

正如我所说,lambda抽象所做的是在其子表达式中绑定一个符号,以便它成为可替代的参数。这样的符号称为 bound 。但是如果表达式中还有其他符号呢?例如:( (lambda (x) (+ x 2)) 7 ) 。在此表达式中,符号λx.x/y+2由其前面的lambda抽象x绑定。但是另一个符号λx.没有绑定 - 它是 free 。我们不知道它是什么以及它来自哪里,因此我们不知道意味着什么以及它所代表的,因此我们在我们弄清楚y的含义之前,我无法评估该表达式。

事实上,其他两个符号y2也是如此。只是我们对这两个符号非常熟悉,我们通常会忘记计算机并不知道它们,我们需要通过在某处定义它们来告诉它它们的含义,例如:在图书馆或语言本身。

您可以将 free 符号视为表达式之外的其他地方,在其周围环境&#34;中被定义,称为环境 。环境可能是一个更大的表达,这个表达是其中的一部分(正如Qui-Gon Jinn所说:&#34;总有一条更大的鱼&#34 ;;)),或者在某些图书馆,或者语言本身(作为原语)。

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

  • CLOSED表达式:这些表达式中出现的每个符号都是由某个lambda抽象绑定。换句话说,它们是自包含的;他们不需要评估任何周围环境。它们也被称为组合器
  • OPEN表达式:这些表达式中的某些符号不是绑定 - 也就是说,其中出现的一些符号是 free ,它们需要一些外部信息,因此在提供这些符号的定义之前,不能对它们进行评估。

您可以通过提供环境来关闭 open lambda表达式,环境通过将它们绑定到某些值(可能是数字,字符串,匿名)来定义所有这些自由符号函数aka lambdas,无论......)。

这里有闭包部分:
lambda表达式闭包是在外部上下文(环境)中定义的特定符号集,它为此处的自由符号赋值表达,使他们不再自由。它变成了一个 open lambda表达式,它仍然包含一些&#34; undefined&#34;自由符号,进入已关闭的,不再有任何自由符号。

例如,如果您有以下lambda表达式:+,则绑定符号λx.x/y+2,而符号x是空闲的,因此表达式为y除非您说出open的含义(与y+相同,也是免费的),否则无法进行评估。但是假设您还有这样的环境

2

环境为所有&#34; undefined&#34;提供定义。我们的lambda表达式({ y: 3, +: [built-in addition], 2: [built-in number], q: 42, w: 5 } y+)中的(免费)符号,以及一些额外的符号(2q)。我们需要定义的符号是环境的这个子集:

w

这正是我们lambda表达式的闭包:&gt;

换句话说,关闭一个开放的lambda表达式。这就是名称​​ closure 首先出现的地方,这就是为什么这个帖子中很多人的答案都不正确的原因:P

<小时/> 那他们为什么会弄错?为什么这么多人说闭包是内存中的某些数据结构,或者是他们使用的语言的某些特性,或者为什么它们会将闭包与lambdas混淆? :P

嗯,Sun / Oracle,微软,谷歌等公司的市场主体应该受到指责,因为他们用他们的语言(Java,C#,Go等)称他们为这些结构。他们经常打电话给#34;关闭&#34;什么应该只是lambdas。或者他们打电话给#34;关闭&#34;他们用于实现词法作用域的一种特殊技术,即函数可以访问在定义时在其外部作用域中定义的变量。他们经常说功能&#34;包含&#34;这些变量,即将它们捕获到一些数据结构中,以防止它们在外部函数完成执行后被销毁。但这只是制作了 post factum &#34;民俗文学词源&#34;和营销,只会让事情变得更加混乱,因为每个语言供应商都使用自己的术语。

情况更糟,因为他们说的话总是有点真实,这不允许你轻易将其视为假:P让我解释一下:

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

人们花了很长时间才开始调用他们在语言实现中使用的实际数据结构来实现闭包作为&#34;闭包&#34;本身。结构通常看起来像这样:

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

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

正如我上面所解释的,lambda表达式的闭包是其环境中定义的子集,它为lambda表达式中包含的自由变量赋值,实际上关闭表达式(转为< em> open lambda表达式,无法进行评估,进入关闭的 lambda表达式,然后可以对其进行评估,因为现在定义了包含在其中的所有符号。)

其他任何东西都只是一个&#34;货运崇拜&#34;和&#34; voo-doo魔法&#34;程序员和语言供应商并不知道这些概念的真正根源。

我希望能回答你的问题。但如果您有任何后续问题,请随时在评论中询问他们,我会尝试更好地解释。

答案 2 :(得分:172)

当大多数人想到功能时,他们会想到命名函数

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

答案 3 :(得分:52)

并非所有闭包都是lambdas,并非所有lambdas都是闭包。两者都是功能,但不一定是我们习惯的方式。

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

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

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

有趣的是,在C#中引入Lambdas和Closures使函数式编程更接近主流使用。

答案 4 :(得分:14)

这很简单:lambda是一种语言结构,即简单函数的语法;闭包是一种实现它的技术 - 或任何一流的函数,就此而言,命名或匿名。

更准确地说,闭包是first-class function在运行时表示的方式,作为一对“代码”和一个环境“关闭”该代码中使用的所有非局部变量。这样,即使已经退出它们的外部范围,这些变量仍然可以访问。

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

答案 5 :(得分:12)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

我使用方案来说明由于该方案是最早的具有真正闭包的语言之一的想法。这里的所有材料都更好地呈现在SICP第3章中。

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

答案 6 :(得分:7)

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

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

function($ v){return $ v&gt; 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();

Nicely explained in this article.

答案 7 :(得分:7)

这个问题很老,得到了很多答案。现在Java 8和Official Lambda是非正式的闭包项目,它重新回答了这个问题。

Java上下文中的答案(通过Lambdas and closures — what’s the difference?):

  

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

答案 8 :(得分:5)

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

答案 9 :(得分:1)

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始终可以访问其闭包(外部作用域中的变量)

答案 10 :(得分:1)

Lambda 与闭包

Lambda匿名函数(方法)

Closure 是一个函数,它关闭(捕获)其封闭范围内的变量(例如非局部变量)

Java

interface Runnable {
    void run();
}

class MyClass {
    void foo(Runnable r) {

    }

    //Lambda
    void lambdaExample() {
        foo(() -> {});
    }

    //Closure
    String s = "hello";
    void closureExample() {
        foo(() -> { s = "world";});
    }
}

斯威夫特[Closure]

class MyClass {
    func foo(r:() -> Void) {}
    
    func lambdaExample() {
        foo(r: {})
    }
    
    var s = "hello"
    func closureExample() {
        foo(r: {s = "world"})
    }
}

答案 11 :(得分: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与函数一起保留,以便在传递给其他函数或在任何地方使用时都可以引用它。

答案 12 :(得分:0)

Lambda 是一个匿名函数定义,它不(必然)绑定到标识符。

<块引用>

“匿名函数起源于 Alonzo Church 在他发明 lambda 演算中的工作,其中所有函数都是匿名的” - Wikipedia

闭包是 lambda 函数的实现。

<块引用>

“Peter J. Landin 在 1964 年将术语闭包定义为具有环境部分和控制部分,正如他的 SECD 机器用于评估表达式一样”-Wikipedia

Lambda 和 Closure 的一般解释包含在其他回复中。

对于那些有 C++ 背景的人来说,Lambda 表达式是在 C++11 中引入的。将 Lambda 视为创建匿名函数和函数对象的便捷方式。

<块引用>

"lambda 和对应闭包的区别,正好相当于类和类的实例的区别。类只存在于源代码中;它在运行时不存在。运行时存在的是类类型的对象。闭包对于 lambdas 就像对象对于类一样。这应该不足为奇,因为每个 lambda 表达式都会导致生成一个唯一的类(在编译期间),并且还会导致该类类型的对象,即闭包将被创建(在运行时)。” - Scott Myers

C++ 允许我们检查 Lambda 和闭包的细微差别,因为您必须明确指定要捕获的自由变量。

在下面的示例中,Lambda 表达式没有自由变量,一个空的捕获列表 ([])。它本质上是一个普通的函数,在最严格的意义上不需要闭包。所以它甚至可以作为函数指针参数传递。

void register_func(void(*f)(int val))   // Works only with an EMPTY capture list
{
    int val = 3;
    f(val);
}
 
int main() 
{
    int env = 5;
    register_func( [](int val){ /* lambda body can access only val variable*/ } );
}

只要在捕获列表 ([env]) 中引入来自周围环境的自由变量,就必须生成闭包。

    register_func( [env](int val){ /* lambda body can access val and env variables*/ } );

由于这不再是一个普通的函数,而是一个闭包,所以会产生编译错误。
no suitable conversion function from "lambda []void (int val)->void" to "void (*)(int val)" exists

可以使用函数包装器 std::function 修复错误,该包装器接受任何可调用目标,包括生成的闭包。

void register_func(std::function<void(int val)> f)

有关 C++ 示例的详细说明,请参阅 Lambda and Closure