【问题标题】:Implementing lambdas in a scripting language用脚本语言实现 lambda
【发布时间】:2011-12-20 17:32:09
【问题描述】:

我有这个修改过的代码示例,我取自关于匿名函数的维基百科文章。

comp(threshold)
{
    return lambda(x)
    {
        return x < threshold;
    };
}

main()
{
    a = comp(10);

    lib::print( a(5) );
}

匿名函数应该不会太难添加到我的脚本语言中。它应该只是以正常方式添加函数代码的情况,除了访问该函数的唯一方法是通过分配它的变量。

上述闭包示例的问题在于,匿名函数的函数体引用(在一般意义上)在调用闭包时无效(或将是)的内存位置。

我已经想到了两个可能的解决方案;在我尝试将此功能添加到我的语言之前,我只想先获得一些建议。

【问题讨论】:

  • 这与游戏开发无关,应该移至 stackoverflow。
  • 你必须以某种方式捕捉上下文。 C++ 有两个选项 - 您可以通过引用(在这种情况下,lambda 秘密地在实例中保留一个指针/引用,并且不能有效地超出范围)或按值(在这种情况下,lambda 对象需要秘密地保留一个在实例中复制)。不过,您的语言可能允许第三个选项——无论您用作堆栈,如果 lambda 对象可以保留对堆栈帧的引用,并且如果堆栈帧本身是垃圾收集的(或类似的),那么 lambda 的存在可以延长该局部变量的存在。
  • 另一种选择是通过 CPS 变换执行闭包消除。这就是 Scheme 实现的作用(不难理解,请阅读 Scheme 的创建者 Guy Lewis Steele 的 Lambda the Ultimate XXX 论文)。您将需要为此进行垃圾收集,因为它相当于(一个理智的版本)@SteveJessop 的第三个选项。
  • 有很多方法可以做到这一点。你能更详细地描述你的脚本语言吗?例如,它是否支持动态调度?您希望封闭的局部变量具有词法范围(如在 C# 中)还是动态范围(如在 PostScript 中)?功能已经“一流”了吗?您的语言执行多少静态类型分析?您能否表示类/记录/结构/关联数组/等之类的东西,如果可以,如何表示?
  • Implementation of closures in Lua,Nicol Bolas 的回答非常好

标签: c++ scripting lambda closures scripting-language


【解决方案1】:

我不知道 明智的方式,但您可以在 The implementation of Lua 5.0 中了解 Lua 如何实现闭包。另请参阅slides

Lua 实现闭包的要点是有效处理 upvalues 或外部局部变量。这使得 Lua 能够支持完整的词法作用域。有关这种支持如何在 Lua 设计中演变的说明,请参阅 HOPL III 论文The Evolution of Lua

【讨论】:

  • 刚刚阅读并了解了“upvalue”......很有趣。如果你想接受,你应该在回答中提到这一点。
  • @Truncheon,我刚刚添加了更多细节。感谢您的建议。
【解决方案2】:

由于您的问题不是很具体,因此只能使用通用算法来回答。我也没有声称这是“最明智”的方式,只是一种有效的方式。

首先,我们需要定义问题空间。

您的脚本语言有局部变量(使用 Lua 术语):不是全局变量。这些是理论上可以被 lambda 捕获的变量。

现在,让我们假设您的脚本语言无法动态选择局部变量。这意味着,只需检查语法树,就可以看到以下内容:

  1. 函数捕获了哪些局部变量。
  2. 哪些局部变量没有被函数捕获。
  3. 哪些函数在其范围之外捕获局部变量。
  4. 哪些函数捕获其范围之外的局部变量。

鉴于此信息,局部变量现在分为两组:局部变量和捕获的局部变量。我将这些称为“纯本地人”和“捕获的本地人”。

纯粹的本地人,如果没有更好的术语,就是注册人。当你编译成你的字节码时,纯本地是最容易处理的。它们是特定的堆栈索引,或者它们是特定的寄存器名称。无论您如何进行堆栈管理,都会为纯本地人分配特定范围内的固定位置。如果您使用 JIT 的力量,那么这些将成为寄存器,或者最接近可能的东西。

关于捕获的本地变量,您首先需要了解的是:它们必须由您的内存管理器管理。它们独立于当前调用堆栈和范围而存在,因此,它们需要是由捕获它们的函数引用的独立对象。这允许多个函数捕获相同的本地数据,从而相互引用彼此的私有数据。

因此,当您输入一个包含捕获的 lambda 的作用域时,您将分配一块内存,其中包含属于该特定作用域的所有捕获的局部变量。例如:

comp(threshold)
{
    local data;
    return lambda(x)
    {
        return x < (threshold + data);
    };
}

comp 函数的根作用域有两个局部变量。两人都被俘虏了。因此,捕获的局部数为2,纯局部数为零。

因此,您的编译器(字节码)将为纯本地变量分配 0 个寄存器/堆栈变量,并将分配一个包含两个变量的独立对象。假设您正在使用垃圾收集,您将需要一些东西来引用它以使其继续存在。这很容易:您在寄存器/堆栈位置引用它,脚本不能直接访问它。所以真的,你确实分配了一个寄存器/堆栈变量,但脚本不能直接接触它。

现在,让我们看看lambda 做了什么。它创建了一个函数。同样,我们知道这个函数捕获了一些超出其范围的变量。我们知道它捕获了哪些变量。我们看到它捕获了两个变量,但我们也看到这两个变量来自同一个独立的内存块。

所以lambda 所做的是创建一个函数对象,该对象具有对某些字节码的引用和对其关联的变量的引用。字节码将使用该引用来获取其捕获的变量。你的编译器知道哪些变量是函数的纯局部变量(如参数x),哪些是外部捕获的局部变量(如阈值)。所以它可以弄清楚如何访问每个变量。

现在,当lambda 完成时,它会返回一个函数对象。此时,捕获的变量被两个东西引用:lambda 函数和 stack:函数的当前作用域。但是,当return 完成时,当前作用域被销毁,并且不再引用之前引用的所有内容。所以当它返回函数对象时,只有 lambda 函数有对捕获变量的引用。

不过,这一切都相当复杂。一个更简单的实现是只有效地捕获所有局部变量; 所有 局部变量是捕获的局部变量。如果你这样做,那么你的编译器会简单得多(而且可能更快)。当进入一个新的作用域时,该作用域的所有局部变量都被分配在一块内存中。创建函数时,它会引用它使用的所有外部作用域(如果有)。并且当一个范围退出时,它会删除对它分配的局部变量的引用。如果没有其他人引用它,则可以释放内存。

非常简单明了。

【讨论】:

    【解决方案3】:

    如果我把它翻译成 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++ 翻译)

    【讨论】:

      【解决方案4】:

      我一直在阅读有关 Lua 中使用的上值的信息。我将尝试实现一个类似的系统来处理闭包和完整的词法作用域。棘手的部分是让编译器根据需要将关闭命令放置在正确的位置。

      function()
      {
          a = 6, b;
      
          {
              local c = 5;
      
              b = lambda() { return a*c; };
      
              // close c in closure;
          }
      
          b();
      
          // close a in closure
      }
      

      【讨论】:

        猜你喜欢
        • 2010-11-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2010-11-12
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多