编译器构造:如何从函数返回不同的闭包?

时间:2018-07-31 12:47:03

标签: compiler-construction language-agnostic closures

我为玩具语言编写了一个编译器。我想以我的语言成为一流的公民。 如果我理解正确,当编译器看到嵌套函数使用外部变量时,它将创建一个闭包。闭包可以实现为包含自由变量和函数本身的类。

现在考虑以下代码:

func bar(a:Int, b:Bool) -> (Void -> Int)
  func foo1() -> Int
    return a + 1
  end

  func foo2() -> Int
    return a + 2
  end

  if b then
    return foo1
  else
    return foo2
  end
end

编译器可以将此代码重写为:

class Foo1
  var a := 0
  func foo1() -> Int
    return self.a + 1
  end
end

class Foo2
  var a := 0
  func foo2() -> Int
    return self.a + 2
  end
end

func bar(a:Int, b:Bool) -> //what is the return type of this function?
  if b then
    var foo1: Foo1
    foo1.a = a
    return foo1
  else
    var foo2: Foo2
    foo2.a = a
    return foo2
  end
end

但是在这种情况下,bar()的返回类型将是什么?还是关闭的实现方式有所不同?

3 个答案:

答案 0 :(得分:1)

如果我正确理解了您的问题,则编译器会有效地为这两种类型生成通用的基类。

真正的答案取决于您实际编写的是哪种编译器。

您的示例基本上是一个编译器,它会生成另一种(更简单的)OO语言,因此它将为具有相同签名的所有块类型生成一个公共基类,因此将使用一个方法生成一个VoidBlockIntReturn类, func call() -> Int。这样,您的foo1foo2都将实现call(),并且任何调用块的人都会在该对象上调用call()

如果您的OO语言支持正确的Smalltalk风格的消息发送和鸭子键入,则您甚至不需要基类,只需约定该方法具有相同的消息名称即可(例如{{ 1}})。

如果您实际上是编译成汇编程序,那会更加容易:汇编程序实际上没有数据类型,也没有区别数据和代码。数据只是一堆字节,代码也是如此。

您在一条数据上运行的指令决定将这些字节解释为哪种类型。因此,就像我们的鸭子输入法一样,只要call方法的指令在数据(您的块)中具有相同的偏移量,并且采用相同的参数和返回类型,这些块就可以互换为止。一个汇编程序。

此外,汇编语言/机器代码可以寻址相对于代码的数据,因此您可以在同一代码块中生成一个在其代码之后立即包含数据的函数,并使用指令相对寻址(“ PC相对寻址” )进行查找。然后,您基本上有了函数指针,可以将其传递给可以处理函数指针的任何人,他们仍然可以捕获数据。

但是,在这种情况下,每次捕获新的变量值时,您都必须复制整个块(包括其代码),并且由于拥有可执行文件,因此很难实现适当的内存保护。代码和可变数据在同一内存页面上。

尽管可以通过创建一个块作为对象(或结构)来减轻某些负担,该块的起始字节与与跳转指令相同的字节开头,然后将指令写入指向其数据的指针到寄存器或将其压入堆栈。这样,实际的块代码将作为常规函数共享,并且该对象可以类型转换为函数指针,并由不了解块的人调用。

然后,开始的指令将基本上调用该函数,并将指针作为隐藏的额外参数传递给数据。

我实际上写了一篇关于块在这里如何工作的博客,以防您想阅读另一篇可能有帮助的文章:http://orangejuiceliberationfront.com/what-a-block-really-is/

答案 1 :(得分:-1)

根据编译器在其中生成代码的PL(目标PL)选择bar函数的结果类型。

例如,使类Foo1Foo2从类Foo继承或实现接口Foo。在这种情况下,任何函数都必须具有类型FooFoo将是bar的结果类型。这种方法在以下意义上会丢失类型信息。 Foo必须具有应用/调用函数的方法,并且该方法将采用类型Value的值并返回类型Value的值,因为类型为{{1}的值}可以是任何类型的函数。因此,生成的代码将不可避免地包含类型转换。但是,如果未键入目标PL,则不是真正的问题。

原则上,您可以使用更具体的类型。例如,用两个参数定义一个Foo类型的构造函数。在这种情况下,从FuncX的任何函数在生成的代码中都必须具有类型YFunc (X, Y)将是Func (Void, Int)的结果类型。但是目标PL的类型系统应该很复杂。我从未听说过这种方法。

答案 2 :(得分:-1)

这是类型理论问题。

根据您的意思,我了解您的玩具语言是静态输入的。静态类型语言要求您向编译器提供显式类型(或足够的信息来推断类型,如Ocaml所述)。
为了告诉编译器bar的类型,您需要foor1和foo2具有相同的类型,或者在玩具语言中添加对联合类型的支持(例如:Ocaml union types)。