有人可以解释吗?我了解它们背后的基本概念,但是我经常看到它们互换使用,并且感到困惑。
现在我们在这里,它们与常规函数有何不同?
有人可以解释吗?我了解它们背后的基本概念,但是我经常看到它们互换使用,并且感到困惑。
现在我们在这里,它们与常规函数有何不同?
Answers:
一个拉姆达只是一个匿名函数-没有名字定义的函数。在某些语言(例如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()
因为在func
中定义了anotherfunc
,并且在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无关-它们是两个不同的(尽管相关)概念。
即使在此StackOverflow问题的答案中,lambda和闭包也有很多困惑。与其去问那些从某些编程语言的实践中学到闭包的随机程序员或其他笨拙的程序员,不如去探寻源头(一切从此开始)。而且,由于lambda表达式和封锁来自演算发明了在20世纪30年代第一台电子计算机,甚至出现之前邱奇回来了,这是源我谈论。
Lambda微积分是世界上最简单的编程语言。您只能在其中执行以下操作:►
f x
。f
,函数x
是它的唯一参数)λ
(lambda)之前加上符号名称(例如x
),再.
在表达式之前加一个小圆点来完成。然后,将表达式转换为需要一个参数的函数。例如:使用表达式,并告诉该表达式中的符号是一个绑定变量 –可以用您提供的值作为参数替换它。
请注意,以这种方式定义的函数是匿名的λx.x+2
x+2
x
(λx.x+2) 7
。然后,将表达式(在这种情况下为文字值)7
替换为所应用的lambda x
的子表达式x+2
,因此您得到7+2
,然后9
通过通用算术规则将其简化为。因此,我们已经解决了其中的奥秘:
拉姆达是匿名函数从上面的例子,λ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中,lambda是这样制作的:
(lambda (x) (+ x 2))
您可以通过将其立即应用于参数来调用这样的lambda:
( (lambda (x) (+ x 2)) 7 )
就像我说的那样,lambda抽象所做的是在符号的子表达式中绑定符号,以便它成为可替换的参数。这样的符号称为绑定。但是,如果表达式中还有其他符号怎么办?例如:λx.x/y+2
。在此表达式中,该符号x
受其λx.
前面的lambda抽象约束。但是另一个符号y
没有限制-它是免费的。我们不知道它是什么以及它来自何处,所以我们也不知道它的含义以及它代表什么价值,因此,在弄清楚y
含义之前,我们无法评估该表达式。
实际上,其他两个符号2
和也是如此+
。只是我们对这两个符号非常熟悉,以至于我们通常会忘记计算机不知道它们,而我们需要通过在某个地方定义它们(例如在库或语言本身中)来告诉它们的含义。
您可以在表达式外部的“周围环境”中将其定义为自由符号,这被称为环境。环境可能是该表达式的一部分(如Qui-Gon Jinn所说:“总是有一条更大的鱼”;)),或者在某个库中,或者在语言本身(作为原始语言)中。
这使我们可以将lambda表达式分为两类:
您可以通过提供环境来关闭打开的 lambda表达式,该环境通过将所有这些自由符号绑定到某些值(可以是数字,字符串,匿名函数又称为lambda,等等)来定义它们。
和这里来的闭合部分:
该封闭件上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
),和一些额外的符号(q
,w
)。我们需要定义的符号是环境的以下子集:
{ y: 3,
+: [built-in addition],
2: [built-in number] }
而这恰恰是封我们的lambda表达式:>
换句话说,它关闭一个打开的lambda表达式。这是名称闭包最初来自的地方,这就是为什么这个线程中这么多人的答案不太正确的原因:P
好吧,应该指责Sun / Oracle,Microsoft,Google等公司的企业市场,因为这就是他们在其语言(Java,C#,Go等)中所说的这些构造。他们通常称其为“闭包”(lambda)。或者,他们将“闭包”称为一种用于实现词法作用域的特定技术,也就是说,一个函数可以在定义时访问在其外部范围内定义的变量。他们经常说函数将这些变量“封闭”起来,也就是说,将它们捕获到某种数据结构中,以防止它们在外部函数完成执行后被破坏。但这只是事后 “民俗语源”和市场营销的虚构内容,只会使事情更加混乱,
而且,由于他们所说的话总是有些道理,这甚至更糟,这不允许您轻易将其视为假:P让我解释一下:
如果要实现使用lambda作为一等公民的语言,则需要允许它们使用在其周围上下文中定义的符号(即,在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;
};
}
并非所有的闭包都是lambda,也不是所有的lambda都是闭包。两者都是函数,但不一定以我们习惯的方式知道。
Lambda本质上是内联定义的函数,而不是声明函数的标准方法。Lambda经常可以作为对象传递。
闭包是通过引用其主体外部的字段来封闭其周围状态的功能。封闭状态在关闭的所有调用中保持不变。
在面向对象的语言中,闭包通常是通过对象提供的。但是,某些OO语言(例如C#)实现的特殊功能更接近于由纯功能语言(例如lisp)提供的闭包的定义,而闭包没有对象来封装状态。
有趣的是,C#中Lambda和Closure的引入使函数式编程更接近于主流用法。
就是这么简单:lambda是一种语言构造,即匿名函数的简单语法;闭包是一种实现它的技术-或任何一流的函数,无论是命名函数还是匿名函数。
更确切地说,闭包是在运行时如何将一等函数表示为一对“代码”和一个环境,“闭包”该代码中使用的所有非局部变量。这样,即使已经退出了这些变量的外部作用域,也仍然可以访问这些变量。
不幸的是,有许多语言不支持将函数作为一等值,或仅以残缺的形式支持它们。因此,人们经常使用“封闭”一词来区分“真实的事物”。
从编程语言的角度来看,它们完全是两件事。
基本上,对于图灵完整的语言,我们只需要非常有限的元素,例如抽象,应用和简化。抽象和应用程序提供了构建lamdba表达的方式,而归约法则确定了lambda表达的含义。
Lambda提供了一种抽象计算过程的方法。例如,为了计算两个数字的和,可以抽象出一个过程,该过程采用两个参数x,y并返回x + y。在方案中,您可以将其写为
(lambda (x y) (+ x y))
您可以重命名参数,但是它完成的任务不会改变。在几乎所有的编程语言中,您都可以给lambda表达式命名,即函数。但是并没有太大的区别,它们在概念上可以看作只是语法糖。
好,现在想象一下如何实现。每当我们将lambda表达式应用于某些表达式时,例如
((lambda (x y) (+ x y)) 2 3)
我们可以简单地将参数替换为要评估的表达式。该模型已经非常强大。但是该模型无法使我们更改符号的值,例如,我们无法模仿状态的更改。因此,我们需要一个更复杂的模型。简而言之,每当我们要计算lambda表达式的含义时,我们都将一对符号和相应的值放入环境(或表)中。然后,通过查找表中的相应符号来评估其余(+ xy)。现在,如果我们提供一些直接在环境上运行的原语,就可以对状态的变化进行建模!
在此背景下,请检查以下功能:
(lambda (x y) (+ x y z))
我们知道,当我们评估lambda表达式时,xy将绑定到新表中。但是我们如何以及在哪里查找z?实际上,z称为自由变量。必须有一个包含z的外部环境。否则,仅通过绑定x和y不能确定表达式的含义。为了清楚起见,您可以在scheme中编写以下内容:
((lambda (z) (lambda (x y) (+ x y z))) 1)
因此,z将在外部表中绑定为1。我们仍然得到一个接受两个参数的函数,但是它的真正含义还取决于外部环境。换句话说,外部环境在自由变量上关闭。借助set !,我们可以使该函数成为有状态的,即,它不是数学意义上的函数。它返回的内容不仅取决于输入,还取决于z。
这是您已经非常了解的东西,对象的方法几乎总是依赖于对象的状态。这就是为什么有人说“闭包是穷人的对象。”但是我们也可以将对象视为穷人的对象,因为我们真的很喜欢一流的功能。
我使用方案来说明想法,因为该方案是最早的具有真正封闭性的语言之一。SICP第3章将更好地介绍此处的所有材料。
总而言之,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();
这个问题很老,有很多答案。
现在有了非官方的封闭项目Java 8和Official Lambda,它使这个问题复活了。
在Java上下文中的答案(通过Lambda和闭包-有什么区别?):
“闭包是一个lambda表达式,它与将其每个自由变量绑定到一个值的环境配对。在Java中,lambda表达式将通过闭包来实现,因此这两个术语在社区中已可以互换使用。”
简单地说,闭包是关于范围的一个技巧,lambda是一个匿名函数。我们可以更优雅地使用lambda实现闭包,并且lambda通常用作传递给更高功能的参数
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始终可以访问其闭包(在其外部范围内的变量)
这取决于一个函数是否使用外部变量来执行操作。
外部变量 -在函数范围之外定义的变量。
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与函数一起保留,以便在传递给其他函数或在任何地方使用时都可以引用它。