协慌网

登录 贡献 社区

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

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

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

答案

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 + 中,使用nonlocal关键字时,变异也起作用。

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

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

print anotherfunc(10)()

这将打印 10。

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

关于 lambdas 和闭包有很多混淆,即使在这个 StackOverflow 问题的答案中也是如此。而不是要求随机程序员通过某些编程语言或其他无知程序员学习关于实践的闭包,而是前往源头 (这一切都开始了)。由于 lambdas 和闭包来自 Alonzo Church 发明的Lambda 微积分 ,早在 30 年代,在第一台电子计算机出现之前,这就是我所说的源头

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

  • APPLICATION:将一个表达式应用于另一个表达式,表示为fx
    (把它想象成一个函数调用 ,其中f是函数, x是它唯一的参数)
  • 摘要:绑定表达式中出现的符号,以标记此符号只是一个 “槽”,一个等待填充值的空白框,一个 “变量”。它是通过在希腊字母λ (lambda)之前,然后是符号名称(例如x ),然后是一个点来完成的.在表达之前。然后,将表达式转换为期望一个参数函数
    例如: λx.x+2采用表达式x+2并告诉该表达式中的符号x是一个绑定变量 - 它可以替换为您提供的值作为参数。
    请注意,以这种方式定义的函数是匿名的 - 它没有名称,所以你还不能引用它,但是你可以通过提供它正在等待的参数来立即调用它(记住应用程序吗?),就像这个: (λx.x+2) 7 。然后表达式(在本例中为文字值) 7在应用的 lambda 的子表达式x+2中被替换为x ,因此得到7+2 ,然后通过常见的算术规则将其减少到9

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


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 中,lambdas 是这样的:

(lambda (x) (+ x 2))

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

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


闭包 符号 变量

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

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

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

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

  • CLOSED 表达式:这些表达式中出现的每个符号都受到一些 lambda 抽象的约束 。换句话说,它们是独立的 ; 它们不需要评估任何周围的上下文。它们也被称为组合器
  • OPEN 表达式:这些表达式中的某些符号不受约束 - 也就是说,它们中出现的某些符号是空闲的 ,它们需要一些外部信息,因此在提供这些符号的定义之前无法对它们进行求值。

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

关闭部分:
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


好吧,Sun / Oracle,微软,谷歌等公司的市场主体应该受到指责,因为这就是他们所谓的语言结构(Java,C#,Go 等)。他们经常把 “封闭” 称为 “lambdas”。或者他们将 “闭包” 称为他们用于实现词法作用域的特定技术,即,函数可以访问在定义时在其外部作用域中定义的变量。他们经常说这个函数 “包含” 这些变量,即将它们捕获到一些数据结构中,以防止它们在外部函数完成执行后被销毁。但这只是后事实上的 “民俗词源学” 和市场营销,只会让事情变得更加混乱,因为每个语言供应商都使用自己的术语。

更糟糕的是,因为他们所说的内容总是有一些真相,这不允许你轻易将其视为假:P 让我解释一下:

如果要实现使用 lambdas 作为一等公民的语言,则需要允许它们使用在其周围上下文中定义的符号(即,在 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 魔术”,并不知道这些概念的真正根源。

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

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

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