'封'和'lambda'有什么区别?
有人可以解释吗? 我了解他们背后的基本概念,但我经常看到他们互换使用,我感到困惑。
而现在我们在这里,它们与常规函数有什么不同呢?
lambda只是一个匿名函数 – 一个没有名字的函数。 在Scheme等语言中,它们相当于命名函数。 实际上,函数定义被重写为在内部绑定一个lambda到一个variables。 在像Python这样的其他语言中,它们之间有一些(相当不必要的)区别,但是它们的行为方式是相同的。
闭包是closures它定义的环境的任何函数。 这意味着它可以访问不在其参数列表中的variables。 例子:
def func(): return h def anotherfunc(h): return func()
这将导致一个错误,因为func
不会closures anotherfunc
func
中的环境 – h
是未定义的。 func
只closures全球环境。 这将工作:
def anotherfunc(h): def func(): return h return func()
因为在这里, func
被定义在anotherfunc
func
中,而在python 2.3和更高版本(或者像这样的一些数字),当它们几乎正确closures时(变异仍然不起作用),这意味着它closures了 anotherfunc
的环境,里面的variables。 在Python 3.1+中,当使用nonlocal
关键字时,突变也起作用。
另一个重要的点 – func
将继续closuresanotherfunc
的环境,即使它不再被另一个func
评估。 此代码也将工作:
def anotherfunc(h): def func(): return h return func print anotherfunc(10)()
这将打印10。
正如你注意到的,这与lambda s无关 – 它们是两个不同的(尽pipe相关的)概念。
当大多数人想到function时 ,他们会想到命名的function :
function foo() { return "This string is returned from the 'foo' function"; }
当然,这些都是按名称调用的:
foo(); //returns the string above
使用lambdaexpression式 ,您可以拥有匿名函数 :
@foo = lambda() {return "This is returned from a function without a name";}
通过上面的例子,你可以通过它分配的variables来调用lambda:
foo();
然而,比将匿名函数分配给variables更有用的是将它们传递给高阶函数,也就是接受/返回其他函数的函数。 在很多这些情况下,命名一个函数是不必要的:
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)});
闭包可能是一个有名或无名的函数,但是在函数被定义的范围内“closures”variables的时候,这个函数就是已知的,也就是说,闭包仍然会引用具有任何外部variables的环境封闭本身。 这是一个命名的封闭:
@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 (yx == 0 + 1) y(); //returns 2 (yx == 1 + 1)
这就是你如何在函数式编程中得到有状态的对象。 由于不需要命名“incrementX”,所以在这种情况下可以使用lambda:
function foo() { @x = 0; return lambda() { x = x + 1; return x; }; }
关于lambdas和闭包有很多的困惑,即使在这里的StackOverflow问题的答案。 而不是要求随机程序员学习使用某些编程语言或其他无知的程序员从实践中closures,然后走向源代码 (开始的地方)。 而且,由于lambda和封闭来自Alonzo教会发明的Lambda Calculus早在三十年代之前,第一台电子计算机甚至已经存在,这就是我所说的来源 。
Lambda微积分是世界上最简单的编程语言。 你唯一能做的事情就是:
- 应用:将一个expression式应用于另一个expression式,表示为
fx
。
(把它看作一个函数调用 ,其中f
是函数,x
是它唯一的参数) - 摘要:绑定一个expression式中出现的符号,标记这个符号只是一个“槽”,一个等待填充值的空白框,一个“variables”。 这是通过预先希腊字母
λ
(lambda),然后是符号名称(例如x
),然后一个点.
之前的expression。 然后将expression式转换为期望一个参数的函数 。
例如:λx.x+2
采用expression式x+2
并且表示该expression式中的符号x
是一个绑定variables – 它可以用您提供的值作为参数replace。
请注意,这种方式定义的函数是匿名的 – 它没有名称,所以你不能引用它,但你可以立即调用它(记住应用程序?)通过提供它正在等待的参数,如这个:(λx.x+2) 7
。 然后,在所应用的lambda的子expression式x+2
中将expression式(在这种情况下是一个字面值)7
replace为x
,所以得到7+2
,然后通过常规算术规则将其减less到9
。
所以我们解决了一个谜团:
lambda是上例中的匿名函数 , λx.x+2
。
在不同的编程语言中,函数抽象(lambda)的语法可能不同。 例如,在JavaScript中,它看起来像这样:
function(x) { return x+2; }
你可以立即将它应用到这样的参数:
function(x) { return x+2; } (7)
或者你可以将这个匿名函数(lambda)存储到某个variables中:
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 )
好的,现在是时候解决另一个谜团:什么是封闭 。 为了做到这一点,我们来谈谈lambdaexpression式中的符号 ( variables )。
正如我所说,lambda抽象所做的是在其子expression式中绑定一个符号,以便它成为一个可替代的参数 。 这样的符号被称为绑定 。 但是如果expression式中还有其他符号呢? 例如: λx.x/y+2
。 在这个expression式中,符号x
被lambda抽象λx.
所约束λx.
在它之前。 但是另外一个符号y
不受限制 – 它是自由的 。 我们不知道它是什么,它来自哪里,所以我们不知道它是什么意思 ,它代表什么价值 ,因此我们不能评估这个expression,直到我们找出你的意思。
事实上,其他两个符号2
和+
。 只是我们对这两个符号非常熟悉,以至于我们通常会忘记计算机不知道它们,我们需要通过在某个地方定义它们来说明它们的含义,例如在图书馆或语言本身。
你可以把自由符号想象成在expression式之外的其他地方,在它的“周围环境”中被称为它的环境 。 (Qui-Gon Jinn说:“总有一条更大的鱼”),或者某个图书馆,或者这个语言本身(作为一个原始的 ),环境可能就是一个更大的expression。
这让我们将lambdaexpression式分为两类:
- CLOSEDexpression式:这些expression式中出现的每个符号都被一些lambda抽象所约束 。 换句话说,他们是独立的 ; 他们不需要评估任何周围的环境。 他们也被称为组合者 。
- OPENexpression式:这些expression式中的某些符号没有被绑定 – 也就是说,它们中出现的一些符号是空闲的 ,它们需要一些外部信息,因此只有在提供这些符号的定义之后才能对它们进行评估。
您可以通过提供环境来closures打开的 lambdaexpression式,该环境通过将所有这些空闲符号绑定到某些值(可能是数字,string,匿名函数,lambdaexpression式等等)来定义它们。
closures部分来了:
lambdaexpression式的closures是在外部上下文(环境)中定义的这个特定符号集,它给这个expression式中的自由符号赋值,使得它们不再是自由的。 它将一个开放的 lambdaexpression式转换成一个封闭的 ,它没有任何空闲的符号,它仍然包含一些“未定义的”自由符号。
例如,如果你有下面的lambdaexpression式: λx.x/y+2
,那么符号x
是绑定的,而符号y
是open
,因此expression式是open
,除非你说y
表示什么与+
和2
相同,这也是免费的)。 但是,假设你也有这样的环境 :
{ y: 3, +: [built-in addition], 2: [built-in number], q: 42, w: 5 }
这个环境为我们的lambdaexpression式( y
, +
, 2
)和几个额外的符号( q
, w
)提供所有“未定义”(自由)符号的定义。 我们需要定义的符号是环境的这个子集:
{ y: 3, +: [built-in addition], 2: [built-in number] }
这正是我们的lambdaexpression式的closures :>
换句话说,它closures了一个开放的lambdaexpression式。 这就是封闭名字来自哪里,这就是为什么这么多人在这个线程中的答案是不正确的:P
那他们为什么弄错了? 为什么很多人说封闭是内存中的一些数据结构,或者是他们使用的语言的某些特性,或者为什么他们把闭包和lambdas混淆? :P
那么Sun / Oracle,微软,Google等公司的市场主体就应该受到指责,因为这就是他们所谓的语言结构(Java,C#,Go等)。 他们经常称之为“closures”,这应该是“lambda”。 或者他们称之为“闭包”是他们用来实现词法范围界定的一种特殊技术,也就是说,一个函数可以访问定义时在其外部范围定义的variables。 他们经常说这个函数“封装”这些variables,也就是将它们捕获到一些数据结构中,以防止外部函数执行完后被销毁。 但是,这只是后缀 “民俗词源”和营销,这只会让事情更混乱,因为每个语言厂商都使用自己的术语。
而且更糟糕的是,他们所说的总是有一点点的真相,这不允许你轻易将其视为假:P让我解释一下:
如果你想实现一种使用lambdas作为一等公民的语言,你需要允许他们使用周围环境中定义的符号(也就是说,在你的lambda中使用自由variables)。 即使周围的函数返回,这些符号也必须存在。 问题是这些符号绑定到函数的某些本地存储(通常在调用堆栈上),当函数返回时,这些符号将不再存在。 因此,为了使lambda按照您期望的方式工作,您需要以某种方式从外部上下文中“捕获”所有这些自由variables,并在以后保存它们,即使外部上下文将消失。 也就是说,你需要find你的lambda(它使用的所有这些外部variables)的closures ,并将它存储在别的地方(通过复制,或者为它们预先准备空间,而不是在堆栈上)。 你用来实现这个目标的实际方法是你的语言的“实现细节”。 这里最重要的是闭包 ,它是你的lambda 环境中的一组自由variables ,需要保存在某个地方。
人们没有花太长时间就开始调用他们在其语言实现中使用的实际数据结构来实现闭包,就像“闭包”本身一样。 结构通常看起来像这样:
Closure { [pointer to the lambda function's machine code], [pointer to the lambda function's environment] }
这些数据结构被作为parameter passing给其他函数,从函数返回,并存储在variables中,以表示lambdaexpression式,并允许它们访问它们的封闭环境以及在该上下文中运行的机器代码。 但这只是实现闭包的一种方式(其中之一),而不是闭包本身。
正如我上面所解释的那样,lambdaexpression式的closures是其环境中的定义的子集,它给包含在该lambdaexpression式中的自由variables赋值,从而有效地closuresexpression式(将尚无法评估的开放式 lambdaexpression式转换为一个封闭的 lambdaexpression式,然后可以被评估,因为它包含的所有符号现在被定义)。
其他任何东西都只是程序员和语言厂商的“货物崇拜”和“虚拟魔法”,并不知道这些概念的真正根源。
我希望能回答你的问题。 但是,如果您有任何后续问题,请随时在评论中询问他们,我会尽力解释。
并非所有closures都是lambdas,并不是所有的lambda都是closures。 两者都是function,但不一定是我们习惯了解的方式。
一个lambda本质上是一个内联定义的函数,而不是声明函数的标准方法。 兰巴达斯可以经常作为对象传递。
闭包是通过引用它的主体外部的字段来封装其周围状态的函数。 封闭的状态仍然是封闭的调用。
在面向对象的语言中,闭包通常是通过对象来提供的。 但是,一些面向对象语言(例如C#)实现的特殊function更接近纯函数式语言 (如lisp)提供的闭包定义,这些语言没有对象来包围状态。
有趣的是,在C#中引入Lambda和Closures使函数式编程更接近主stream使用。
就像这样简单:lambda是一种语言结构,即匿名函数的简单语法; 闭包是一种实现它的技术,或者任何一stream的function,对于这个问题,有名字或匿名的。
更确切地说,闭包是一个第一类函数在运行时是如何表示的,作为一对“代码”和一个环境“closures”该代码中使用的所有非局部variables。 这样,即使外部作用域已经退出,这些variables仍然可以访问。
不幸的是,那里有许多语言不支持函数作为一stream的值,或者只是以残缺的forms支持它们。 所以人们常常用“封闭”来区分“真实的东西”。
从编程语言来看,它们完全是两回事。
基本上,对于图灵完整的语言,我们只需要非常有限的元素,例如抽象,应用和减less。 抽象和应用程序提供了构buildlamdbaexpression式的方式,并减less了lambdaexpression式的含义。
Lambda提供了一种可以将计算过程抽象出来的方法。 例如,为了计算两个数的和,可以抽取两个参数x,y并返回x + y的过程。 在scheme中,你可以把它写成
(lambda (xy) (+ xy))
您可以重命名这些参数,但其完成的任务不会更改。 在几乎所有的编程语言中,您都可以给lambdaexpression式一个名称,这些名称是函数。 但没有太大的区别,它们在概念上可以被认为只是语法糖。
好吧,现在想象一下如何实现。 每当我们将lambdaexpression式应用于某些expression式,例如
((lambda (xy) (+ xy)) 2 3)
我们可以简单地用参数replaceexpression式来评估。 这个模型已经非常强大了。 但是这种模式并不能使我们改变符号的价值,例如我们不能模仿地位的变化。 因此我们需要一个更复杂的模型。 为了简短起见,每当我们要计算lambdaexpression式的含义时,我们把符号对和相应的值放到一个环境(或表)中。 然后剩下的(+ xy)通过在表格中查找相应的符号进行评估。 现在,如果我们直接提供一些基本操作环境,我们可以模拟状态的变化!
有了这个背景,请检查这个function:
(lambda (xy) (+ xyz))
我们知道,当我们评估lambdaexpression式时,xy将被绑定在一个新的表中。 但是,我们如何以及在哪里可以看到? 实际上z被称为自由variables。 必须有一个包含z的外部环境。 否则expression式的含义不能仅通过绑定x和y来确定。 为了说清楚,你可以在scheme中写下如下内容:
((lambda (z) (lambda (xy) (+ xyz))) 1)
所以z将被绑定到1在外部表中。 我们仍然得到一个接受两个参数的函数,但它的真实含义也取决于外部环境。 换句话说,外部环境在自由variables上closures。 在set!的帮助下,我们可以使函数成为有状态的,即它不是math意义上的函数。 它返回的不仅取决于input,而且还取决于z。
这是你已经非常熟悉的东西,对象的方法几乎总是依赖于对象的状态。 这就是为什么有人说“封闭是穷人的对象”。但是我们也可以把对象看作是穷人的封闭,因为我们真的喜欢一stream的function。
我用scheme来说明由于这个scheme的思想是最早的具有真正封闭性的语言之一。 所有这些材料在SICP第3章中都有更好的介绍。
总而言之,lambda和closure是真正不同的概念。 lambda是一个函数。 闭包是一对lambda和closureslambda的相应环境。
概念和上面描述的一样,但是如果你是来自PHP的背景,这个进一步解释使用PHP代码。
$input = array(1, 2, 3, 4, 5); $output = array_filter($input, function ($v) { return $v > 2; });
函数($ v){return $ v> 2; }是lambda函数定义。 我们甚至可以把它存储在一个variables中,所以它可以重用:
$max = function ($v) { return $v > 2; }; $input = array(1, 2, 3, 4, 5); $output = array_filter($input, $max);
现在,如果你想改变过滤数组中允许的最大数量呢? 你将不得不写另一个lambda函数或创build一个闭包(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));
闭包是一个在自己的环境中进行评估的函数,它有一个或多个绑定variables,可以在函数调用时访问。 它们来自function性编程世界,这里有许多概念。 闭包就像lambda函数,但是更聪明一些,它们有能力与来自定义闭包的外部环境的variables进行交互。
这是一个简单的PHPclosures的例子:
$string = "Hello World!"; $closure = function() use ($string) { echo $string; }; $closure();
在这篇文章中很好地解释了。
这个问题很老,得到了很多答案。 现在Java 8和官方Lambda是非官方的closures项目,它重新提出了这个问题。
Java语境中的答案(通过Lambdas和闭包 – 有什么区别? ):
“闭包是一个lambdaexpression式,它将每个自由variables绑定到一个值,在Java中,lambdaexpression式将通过闭包来实现,所以这两个术语在社区中可以互换使用。
简而言之,闭包是关于范围的一个技巧,lambda是一个匿名函数。 我们可以使用lambda更优雅地实现闭包,而lambda常常被用作传递给更高级函数的参数
一个lambda只是一个匿名函数 – 一个没有name定义的函数。闭包是closures它定义的环境的任何函数。 这意味着它可以访问不在其参数列表中的variables。