Compiler construction 通常如何为闭包实现引用环境?

Compiler construction 通常如何为闭包实现引用环境?,compiler-construction,programming-languages,closures,Compiler Construction,Programming Languages,Closures,假设我有一个静态/词汇范围的语言,具有深度绑定,我创建了一个闭包。闭包将包括我想要执行的语句,加上所谓的引用环境,或者可以使用的变量集合 这个引用环境在实现方面看起来像什么?我最近读到了ObjectVec的块实现,作者建议在幕后获得堆栈上所有变量的副本以及对堆对象的所有引用。该解释声称,您在创建闭包时获得了引用环境的“快照” 这或多或少是怎么回事,还是我误读了 是否采取了任何措施来“冻结”堆对象的单独副本,或者可以安全地假设,如果在创建闭包和执行闭包之间对它们进行了修改,则闭包将不再在对象的原始

假设我有一个静态/词汇范围的语言,具有深度绑定,我创建了一个闭包。闭包将包括我想要执行的语句,加上所谓的引用环境,或者可以使用的变量集合

这个引用环境在实现方面看起来像什么?我最近读到了ObjectVec的块实现,作者建议在幕后获得堆栈上所有变量的副本以及对堆对象的所有引用。该解释声称,您在创建闭包时获得了引用环境的“快照”

  • 这或多或少是怎么回事,还是我误读了
  • 是否采取了任何措施来“冻结”堆对象的单独副本,或者可以安全地假设,如果在创建闭包和执行闭包之间对它们进行了修改,则闭包将不再在对象的原始版本上运行
  • 如果确实进行了复制,那么在可能需要创建大量闭包并将其存储在某个位置的情况下,是否存在内存使用方面的考虑因素
  • 我认为,对其中一些概念的误解可能会导致一些棘手的问题,如埃里克·利珀特在文章中提到的问题。这很有趣,因为您可能认为保留对可能在调用闭包时消失的值类型的引用是没有意义的,但我猜在C#中,编译器会在以后发现需要变量,并将其放入堆中


    似乎在大多数内存管理语言中,一切都是引用,因此ObjectiveC是一种有点独特的情况,需要处理复制堆栈上的内容。

    在Smalltalk中,闭包可以包含对“外部上下文”的引用。外部上下文通常是创建闭包的方法的堆栈框架,但对于嵌套闭包,它可能是另一个闭包

    保存对外部上下文的引用的闭包是昂贵的,因为(我猜)它们阻止了相应的堆栈被垃圾收集。因此,闭包仅在真正需要时才引用外部上下文:

    干净闭包:闭包不涉及任何本地内容。它们不需要引用外部上下文

    例如,
    [文本显示:'something']

    复制闭包:引用在创建闭包后不会更改的变量的闭包。创建闭包时变量的值复制到闭包本身中。然后,不需要保留对外部上下文的引用。例如

    |列表|
    列表:=OrderedCollection新建。
    1到:5 do:[:i |名单加:i]

    完全闭包:保留对外部上下文的引用的闭包。例如

    |计数器|
    计数器:=0。
    1到:5 do:[:i |计数器:=计数器+1]

    如果在创建闭包后关闭的变量发生变异,则需要完全闭包,但对于非局部返回也需要完全闭包。关于非本地返回,您可能会喜欢这一点


    Brian Goetz关于即将推出的JDK 7中闭包的文章也是一本不错的读物。在其他人中,我发现了一个有趣的讨论:为什么他们会坚持使用Java气候,只捕获最终的变量,而禁止捕获可变的局部变量。不支持上面的完全关闭示例。他们声称的论点是,这主要是连环成语

    下面是一个类似伪javascript语法的运行示例

    function f(x) {
      var y = ...;
      function g(z) {
        function h(w) {
          .... y, z, w ....
        }
        .... x, h ....
      }
      .... x, g ....
    }
    
    一种表示是环境的链接链。也就是说,闭包由代码指针、一些插槽和对封闭闭包或顶级环境的引用组成。在这一陈述中

    f = [<code>, <top-level-env>]
    g = [<code>, f, x, y]
    h = [<code>, g, z]
    
    但有时最好让每个函数都直接引用顶级环境,因为它经常被使用:

    f = [<code>, <top-level-env>]
    g = [<code>, <top-level-env>, f, x, y]
    h = [<code>, <top-level-env>, g, z]
    
    此表示修复了空间/GC问题;无闭包在内存中锁定另一个闭包。另一方面,自由变量插槽是复制的,而不是共享的,因此如果有一个包含许多自由变量的嵌套闭包,或者一个嵌套闭包的许多实例,那么总体内存使用率可能会更高。此外,这种表示通常需要存储堆分配的可变变量(但仅限于实际发生变异的变量,并且仅当变异无法自动重写时)

    还有混合方法。例如,您可能主要使用平面闭包,但会特别处理顶级环境:

    g = [<code>, <top-level-env>, x, y]
    
    g=[
    ,x,y]
    

    或者您可能有一个“足够聪明”(或者至少“足够雄心勃勃”)的编译器,它尝试根据自由变量的数量、嵌套深度等在表示形式之间进行选择。

    太好了,非常感谢您的解释!关于这个问题有几个问题:1)你知道哪些特定语言使用了你建议的一些方法吗?树结构方法非常酷,其中一个引用环境是从父环境中派生出来的,尽管您确实可以通过这种方式保留大块内存。2) 你知道在哪里可以找到更多像我们在这里讨论的细节吗?互联网上的信息还不太成熟,不适合这些晦涩难懂的概念。可能有什么特别的书吗?谢谢你的链接和解释。但愿我能接受不止一次的回答!
    g = [<code>, <top-level-env>, x, y]