'封'和'lambda'有什么区别?
有人可以解释吗? 我了解他们背后的基本概念,但我经常看到他们互换使用,我感到困惑。
现在我们在这里,它们与常规函数有什么不同?
lambda只是一个匿名函数 - 一个没有名字的函数。 在Scheme等语言中,它们相当于命名函数。 实际上,函数定义被重写为在内部将lambda绑定到变量。 在像Python这样的其他语言中,它们之间有一些(相当不必要的)区别,但是它们的行为方式相反。
闭包是关闭它定义的环境的任何函数。 这意味着它可以访问不在其参数列表中的变量。 例子:
def func(): return h
def anotherfunc(h):
return func()
这会导致错误,因为func
在anotherfunc
func
中没有关闭环境 - 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无关 - 它们是两个不同的(尽管相关的)概念。
当大多数人想到功能时,他们会想到命名功能 :
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;
};
}
关于lambdas和闭包有很多困惑,即使在这里的StackOverflow问题的答案中也是如此。 与其询问通过某些编程语言或其他无知程序员从实践中学习关闭的随机程序员,不如去寻找源头(开始的地方)。 由于lambda和闭包来自Alonzo Church早在三十年代发明的Lambda Calculus ,在第一台电子计算机出现之前,这就是我所谈论的来源。
Lambda微积分是世界上最简单的编程语言。 你可以做的唯一事情就是:
fx
。 (把它看作一个函数调用,其中
f
是函数, x
是它唯一的参数) λ
(lambda),然后是符号名称(例如x
),然后是一个点来完成的.
在表达之前。 然后将表达式转换为期望一个参数的函数。 例如:
λx.x+2
采用表达式x+2
并且表示该表达式中的符号x
是一个绑定变量 - 它可以用您提供的值作为参数来替换。 请注意,这种方式定义的函数是匿名的 - 它没有名字,所以你现在还不能引用它,但是你可以立即调用它(记住应用程序),方法是提供它正在等待的参数,比如这个:
(λx.x+2) 7
。 然后,在所应用的lambda的子表达式x+2
中将表达式(在这种情况下是字面值) 7
替换为x
,因此您得到7+2
,然后通过常规算术规则将其减少到9
。 所以我们解决了一个谜团:
lambda是上例中的匿名函数, λx.x+2
。
在不同的编程语言中,功能抽象(lambda)的语法可能不同。 例如,在JavaScript中,它看起来像这样:
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表达式中的符号(变量)。
正如我所说,lambda抽象所做的是在其子表达式中绑定一个符号,以便它成为一个可替代的参数。 这样的符号被称为绑定。 但是如果表达式中还有其他符号呢? 例如: λx.x/y+2
。 在这个表达式中,符号x
被拉姆达抽象λx.
所约束λx.
在它之前。 但另一个符号y
不受限制 - 它是免费的。 我们不知道它是什么,它从何而来,所以我们不知道这意味着什么,什么价值它代表,因此直到我们弄清楚什么我们无法评估该表达式y
手段。
事实上,与其他两个符号2
和+
。 只是我们对这两个符号非常熟悉,以至于我们通常会忘记计算机不知道它们,我们需要通过在某处定义它们来说明它们的含义,例如在库或语言本身中。
您可以将自由符号想象成在表达式之外的其他地方定义的自由符号,在其“周围环境”中称为环境 。 环境可能是一个更大的表达,这个表达是其中的一部分(就像Qui-Gon Jinn所说:“总有一条更大的鱼”)),或者在某个图书馆,或者在语言本身(作为一个原始的)。
这让我们将lambda表达式分为两类:
您可以通过提供环境来关闭打开的lambda表达式,该环境通过将所有这些空闲符号绑定到某些值(可能是数字,字符串,匿名函数aka lambda,任何...)来定义这些空闲符号。
关闭部分来了:
lambda表达式的关闭是在外部上下文(环境)中定义的这组特殊符号,它赋予这个表达式中的自由符号值,使它们不再是自由的。 它将一个开放的lambda表达式转换为一个封闭的符号,该表达式仍然包含一些“未定义的”自由符号,而该符号不再具有任何自由符号。
例如,如果你有下面的lambda表达式: λx.x/y+2
,符号x
是绑定的,而符号y
是open
,因此表达式是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
那么他们为什么弄错了? 为什么很多人说闭包是内存中的一些数据结构,或者它们使用的语言的某些功能,或者为什么他们将闭包与lambdas混淆? :P
那么,Sun / Oracle,微软,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表达式,然后可以进行评估,因为其中包含的所有符号现在都已定义)。
其他任何东西都只是程序员和语言供应商的“货物崇拜”和“虚拟魔术”,并不知道这些概念的真正根源。
我希望能回答你的问题。 但是,如果您有任何后续问题,请随时在评论中询问他们,我会尽力更好地解释它。
链接地址: http://www.djcxy.com/p/1455.html上一篇: What is the difference between a 'closure' and a 'lambda'?
下一篇: What limitations have closures in Python compared to language X closures?