我在查看Erlang编译器源代码时遇到了这个问题。
我真的不太明白它(想象一下;)),因为我刚刚5分钟前才意识到有这样一件事情存在。
请原谅我在没有理解其存在原因之前先问一个问题。
关于此事,有一个维基百科文章,但是它相当晦涩难懂。
我在查看Erlang编译器源代码时遇到了这个问题。
我真的不太明白它(想象一下;)),因为我刚刚5分钟前才意识到有这样一件事情存在。
请原谅我在没有理解其存在原因之前先问一个问题。
关于此事,有一个维基百科文章,但是它相当晦涩难懂。
Lambda lifting是一种将lambda函数提升到更高层次(主要是顶层)的技术。
Doug Currie解释了为什么会想这样做。
这里有一些示例代码(使用JavaScript),展示如何手动完成此操作:
function addFive(nr)
{
var x = 5;
function addX(y)
{
return x + y;
}
return addX(nr);
}
现在,如果你不想让函数addX
嵌套在addFive
的定义中,你可以将其提升到顶层:
function addX(y)
{
return x + y;
}
function addFive(nr)
{
var x = 5;
return addX(nr);
}
不过这样是行不通的,因为addX
函数的上下文中已经不存在变量x
。
修复这个问题的方法是在函数中添加一个额外的形式参数:
function addX(y, x)
{
return x + y;
}
function addFive(nr)
{
var x = 5;
return addX(nr, x);
}
补充:这里有一个非常牵强附会的lambda“逃逸”示例,您将无法像我描述的那样轻松进行lambda提取。
function getAddFiveFunc()
{
var x = 5;
function addX(y)
{
return x + y;
}
return addX;
}
现在,如果有人调用getAddFiveFunc
函数,他们将得到一个函数作为返回值。这个函数可以在各种场合下使用。如果您确实想提升addX
函数,那么您将不得不更新所有这些调用点。
警告:我的答案实际上描述的是捕获变量,这与Lambda提升不同。我读错了问题(需要睡觉)。但我花了一些时间编写它,所以不想删除它。将其保留为社区WIKI。
Lambda提升,通常称为闭包,是一种无缝地允许从嵌套的Lambda表达式中访问范围内变量的方法。
在没有选择特定语言的情况下深入细节会很难理解闭包。在任何语言中,Lambda提升的一个副作用是它倾向于将变量的生命周期从本地短暂的范围延长到更长的范围。通常,这是通过编译器将变量从堆栈转移到堆中来实现的。这是一种非常特定于语言的操作,因此基于语言产生非常不同的实现。
我将重点放在C#上,因为这可能是Stack Overflow读者最常见的语言。让我们从以下代码开始。
public Func<int> GetAFunction() {
var x = 42;
Func<int> lambda1 = () => x;
Func<int> lambda2 = () => 42;
...
return lambda1;
}
public static int RealLambda2() {
return 42;
}
public static int RealLambda1() {
return x;
}
这段代码显然无法编译,因为x是不可访问的。为了使其工作,C#编译器必须将变量x提升为闭包。然后它可以返回指向闭包内函数的指针来满足委托表达式。
class Closure1 {
int x;
public int RealLambda1() {
return x;
}
}
这是一个相当简单的例子,但希望能详细说明提取技巧。不幸的是,魔鬼就在于细节,情景变得更加复杂。