我有一个修改过的代码示例,我从维基百科的匿名函数文章中获取。
comp(threshold)
{
return lambda(x)
{
return x < threshold;
};
}
main()
{
a = comp(10);
lib::print( a(5) );
}
匿名函数不应该太难添加到我的脚本语言中。它应该只是以正常方式添加功能代码的情况,除了访问该功能的唯一方法是通过它所分配的变量。
上面的闭包示例的问题是匿名函数的函数体引用(在一般意义上)在调用闭包时无效(或将会)的内存位置。
我已经有两种可能解决问题的方法了;我想在我尝试将此功能添加到我的语言之前先得到一些建议。
答案 0 :(得分:2)
我不知道大多数明智的方法,但你可以阅读Lua如何在The implementation of Lua 5.0中实现闭包。另请参阅slides。
Lua实现闭包的要点是有效处理 upvalues 或外部局部变量。这使得Lua能够支持完整的词法范围。有关Lua设计中这种支持如何演变的说明,请参阅HOPL III论文The Evolution of Lua。
答案 1 :(得分:1)
如果我将其翻译成C ++(没有lambdas),它将如下所示:
struct comp_lamda_1 {
int threshold;
comp_lamda_1(int t) :threshold(t) {}
bool operator()(int x) {
return x < threshold;
};
};
comp_lambda_1 comp(int threshold)
{
return comp_lamda_1(threshold);
}
int main()
{
auto a = comp(10);
std::cout << a(5);
}
这表明解释器不应将匿名函数视为独立函数,而应将其视为函数 - 对象,它具有捕获所需变量的成员。
(很明显,关键是comp_lamda_1
是一个函数对象,我知道你不是要求上述代码的C ++翻译)
答案 2 :(得分:1)
由于您的问题不是非常具体,因此只能通过一般算法来回答。我也没有声称这是“最明智的”方式,只是一种有效的方式。
首先,我们需要定义问题空间。
您的脚本语言具有局部变量(使用Lua术语):非全局变量。这些变量理论上可以由lambda捕获。
现在,让我们假设您的脚本语言没有办法动态选择局部变量。这意味着,只需检查语法树,就可以看到以下内容:
根据这些信息,局部变量现在分为两组:纯局部变量和捕获的局部变量。我将这些称为“纯粹的当地人”和“被捕的当地人”。
由于缺乏更好的术语,纯粹的本地人会注册。当您编译为字节代码时,纯本地语言是最简单的处理。它们是特定的堆栈索引,或者它们是特定的寄存器名称。但是,您正在进行堆栈管理,纯粹的本地人在特定范围内被分配了固定位置。如果你挥舞着JIT的力量,那么它们将成为寄存器,或者是最接近JIT的东西。
您需要了解捕获的本地化的第一件事是:它们必须由您的内存管理器管理。它们独立于当前的调用堆栈和范围而存在,因此,它们需要是由捕获它们的函数引用的独立对象。这允许多个函数捕获相同的本地,因此引用彼此的私有数据。
因此,当您输入包含捕获的lambda的范围时,您将分配一块内存,其中包含属于该特定范围的所有捕获的本地。例如:
comp(threshold)
{
local data;
return lambda(x)
{
return x < (threshold + data);
};
}
comp
函数的根范围有两个局部变量。它们都被捕获了。因此,捕获的本地数量为2,纯本地数量为零。
因此,您的编译器(到字节代码)将为纯本机分配0个寄存器/堆栈变量,它将分配一个包含两个变量的独立对象。假设您正在使用垃圾收集,您将需要一些东西来引用它以便它继续存在。这很容易:您在寄存器/堆栈位置引用它,脚本无法直接访问它。实际上,你确实分配了一个寄存器/堆栈变量,但脚本不能直接触摸它。
现在,让我们来看看lambda
的作用。它创造了一个功能。同样,我们知道该函数捕获了其范围之外的一些变量。我们知道它捕获的变量。我们看到它捕获了两个变量,但我们也发现这两个变量来自同一个独立的内存块。
因此lambda
所做的是创建一个函数对象,该对象具有对某些字节码的引用以及对与其关联的变量的引用。字节码将使用该引用来获取其捕获的变量。您的编译器知道哪些变量是函数的纯本地变量(如参数x
),哪些是外部捕获的本地变量(如阈值)。所以它可以弄清楚如何访问每个变量。
现在,当lambda
完成时,它会返回一个函数对象。此时,捕获的变量由两部分引用:lambda函数和堆栈:函数的当前范围。但是,当return
完成时,将销毁当前作用域,并且不再引用先前引用的所有内容。因此,当它返回函数对象时,只有lambda函数具有对捕获变量的引用。
但这一切都相当复杂。一个更简单的实现就是让所有局部变量得到有效捕获; 所有局部变量都是捕获的本地变量。如果你这样做,那么你的编译器可以更简单(并且可能更快)。输入新范围时,该范围的所有本地都将分配在内存块中。创建函数时,它会引用它使用的所有外部作用域(如果有)。退出范围时,它会删除对其分配的本地的引用。如果没有其他人正在引用它,那么可以释放内存。
这非常简单明了。
答案 3 :(得分:0)
我一直在读Lua中使用的upvalues。我将尝试实现一个类似的系统来处理闭包和完整的词法范围。棘手的部分是让编译器根据需要将close命令放在正确的位置。
function()
{
a = 6, b;
{
local c = 5;
b = lambda() { return a*c; };
// close c in closure;
}
b();
// close a in closure
}