有人能解释一下吗?我理解它们背后的基本概念,但我经常看到它们被交替使用,让我感到困惑。
既然我们在这里了,那么它们与常规函数有何不同呢?
有人能解释一下吗?我理解它们背后的基本概念,但我经常看到它们被交替使用,让我感到困惑。
既然我们在这里了,那么它们与常规函数有何不同呢?
Lambda是一个匿名函数,没有名称的函数。在一些语言中,比如Scheme,它们等同于被命名的函数。事实上,函数定义会在内部被重写为将一个lambda绑定到一个变量上。在其他语言中,比如Python,虽然它们有一些(相对无关紧要的)区别,但它们在其它方面的行为方式是相同的。
Closure是任何一个能够封闭它所定义的环境的函数。这意味着它可以访问不在参数列表中的变量。例如:
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无关-它们是两个不同的(尽管相关的)概念。
d
是一个闭合的字典,则d[a] = b
可以正常工作),它只是不支持名称重新绑定(d = {a: b}
创建一个本地变量并隐藏任何闭包变量)。 就Python而言,这些是完全独立的操作。 因此,在旧版本的Python中,您可以合理地想要使用可变性来做任何事情,只是更麻烦,因为您必须将所有内容都包装在可变字典中。 - Kevin关于lambda和闭包的概念,即使是在这里提出的StackOverflow问题的答案中也存在着很多混淆。与其向那些从某些编程语言的实践中学习闭包或其他无知程序员询问,不如前往源头(一切始于此)。由于lambda和闭包源自于上世纪30年代甚至还没有第一台电子计算机的时期发明的Lambda演算,因此我说的就是这个源头。
Lambda演算是世界上最简单的编程语言。你可以在其中做的唯一的事情有:
f x
。(将其视为一个函数调用,其中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中,Lambda表达式的写法如下:(lambda (x) (+ x 2))
你可以通过立即将参数应用于lambda来调用它:
( (lambda (x) (+ x 2)) 7 )
如我所说,lambda 抽象所做的是将符号绑定到其子表达式中,使其成为可替换的参数。这样的符号称为“bound”。但如果表达式中还有其他符号呢?例如:λx.x/y+2
。在此表达式中,符号 x
由前面的 lambda 抽象 λx.
绑定。但是另一个符号 y
则没有被绑定 - 它是“free”的。我们不知道它是什么和来自哪里,因此我们不知道它的“意义”和代表的“值”,因此我们不能评估该表达式,除非我们弄清楚 y
的含义。
事实上,其他两个符号 2
和 +
也是一样的。只是我们对这两个符号非常熟悉,以至于通常会忘记计算机不认识它们,我们需要通过在库或语言本身中定义它们来告诉它们的含义。
您可以将“free”符号视为在表达式之外的“surrounding context”中定义的符号,这称为其“environment”。环境可以是该表达式是其中一部分的更大表达式(正如 Qui-Gon Jinn 所说:“总有更大的鱼”;)),或者在某个库或语言本身中(作为“primitive”)。
这使我们将 lambda 表达式分为两类:
您可以通过提供环境来关闭一个“open”lambda表达式,该环境定义了所有这些自由符号,并将它们绑定到某些值上(这些值可能是数字、字符串、匿名函数即 lambdas 等等…)。
这里是 闭包 部分:
lambda表达式 的闭包是定义在外部上下文(环境)中的一组特定符号,它们为该表达式中的 自由符号 提供值,使它们不再是自由的。它将一个开放的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
嗯,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表达式,因为其中所有的符号现在都已定义)。
除此之外,任何东西都只是程序员和语言供应商对这些概念真正根源不了解而产生的"装船崇拜"和"巫术魔法"。
希望这回答了您的问题。但如果您有任何后续问题,请随意在评论中提出,我会尽力更好地解释。
当大多数人想到函数时,他们会想到命名函数:
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";}
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)
function foo()
{ @x = 0;
return lambda()
{ x = x + 1;
return x;
};
}
简单来说:lambda是一种语言结构,即匿名函数的语法;闭包则是一种实现它(或者任何一级函数,无论是具名还是匿名)的技术。
更准确地说,闭包用于在运行时表示一个一级函数,它由其“代码”和“环境”构成,该环境“封闭”了该代码中使用的所有非本地变量。这样,在外部作用域已经退出时,那些变量仍然是可访问的。
不幸的是,有许多语言不支持将函数作为一级值,或者只以受限的形式支持它们。因此,人们通常使用术语“闭包”来区分“真正的东西”。
从编程语言的角度来看,它们是完全不同的两个东西。
基本上对于一个图灵完备的语言,我们只需要非常有限的元素,例如抽象、应用和规约。抽象和应用提供了构建lambda表达式的方式,而规约确定了lambda表达式的含义。
Lambda提供了一种将计算过程抽象化的方式。例如,要计算两个数字的和,可以将接受两个参数x、y并返回x+y的过程抽象出来。在scheme中,您可以写成:
(lambda (x y) (+ x y))
您可以更改参数的名称,但它完成的任务不会改变。在几乎所有的编程语言中,您都可以给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是无法确定表达式的含义的。为了清楚起见,你可以在Scheme中编写以下内容:
((lambda (z) (lambda (x y) (+ x y z))) 1)
因此,在外部表中,z将绑定为1。我们仍然得到一个接受两个参数的函数,但它的真正含义也取决于外部环境。 换句话说,外部环境会封闭自由变量。借助set!,我们可以使函数具有状态性,即它不是数学意义上的函数。它的返回值不仅取决于输入,还取决于z。
这是你非常熟悉的东西,对象的方法几乎总是依赖于对象的状态。这就是为什么有些人说“闭包是穷人的对象”的原因。但我们也可以认为对象是闭包的穷人版本,因为我们非常喜欢头等函数。
我使用Scheme来说明这些想法,因为Scheme是最早具有真正闭包的语言之一。所有这里的材料在SICP第3章中都有更好的呈现。
总而言之,λ和闭包是真正不同的概念。λ是一个函数。闭包是一个λ和相应环境的对,它封闭了λ。
这个概念与上述描述相同,但如果你来自PHP背景,以下使用PHP代码进一步解释。
$input = array(1, 2, 3, 4, 5);
$output = array_filter($input, function ($v) { return $v > 2; });
function ($v) { return $v > 2; }
是lambda函数的定义。我们甚至可以将其存储在变量中,以便重复使用。在IT技术中,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));
$string = "Hello World!";
$closure = function() use ($string) { echo $string; };
$closure();
Lambda vs Closure
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";});
}
}
Swift[闭包]
class MyClass {
func foo(r:() -> Void) {}
func lambdaExample() {
foo(r: {})
}
var s = "hello"
func closureExample() {
foo(r: {s = "world"})
}
}
简单来说,闭包是关于作用域的技巧,lambda是一个匿名函数。我们可以使用lambda更优雅地实现闭包,并且lambda经常被用作传递给更高级函数的参数。