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

Compiler construction 编译器构造:如何从函数返回不同的闭包?,compiler-construction,language-agnostic,closures,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

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

现在考虑下面的代码:

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()
的返回类型是什么?还是闭包的实现方式不同?

您可以根据编译器生成代码的PL(目标PL)选择
bar
函数的结果类型

例如,使类
Foo1
Foo2
从类
Foo
继承或实现接口
Foo
。在这种情况下,任何函数都必须具有类型
Foo
Foo
将是
bar
的结果类型。这种方法会丢失以下意义上的类型信息
Foo
必须有一个应用/调用函数的方法,该方法将获取
value
类型的值并返回
value
类型的值,因为
Foo
类型的值可能是任何类型的函数。因此,生成的代码将不可避免地包含类型转换。但是,如果目标PL是非类型化的,则这不是一个真正的问题


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

这是一个类型理论问题

从你所说的,我知道你的玩具语言是静态类型的。静态类型语言要求您向编译器提供显式类型(或足够的信息来推断类型,如在Ocaml中所做的)。

为了告诉编译器bar的类型,您需要对foor1和foo2使用相同的类型,或者在您的玩具语言中添加对联合类型的支持(例如:)。

如果我正确理解您的问题,编译器将有效地为这两种类型生成一个公共基类

真正的答案取决于您实际编写的编译器类型

您的示例基本上是一个生成另一种(更简单?)OO语言的transpiler,因此它将为具有相同签名的所有块类型生成一个公共基类,
VoidBlockIntReturn
类,使用一个方法,
func call()->Int
。然后,您的
foo1
foo2
都将实现
call()
,任何调用块的人都将对该对象调用
call()

如果您的OO语言支持适当的Smalltalk风格的消息发送和duck类型,那么您甚至不需要基类,只需要方法具有相同消息名称的约定(即,
调用

若你们真的编译成汇编程序,那个就更容易了:汇编程序并没有真正的数据类型,也并没有区分数据和代码。数据只是一堆字节,代码也是

对一段数据运行的指令决定了这些字节的解释类型。因此,就像我们的duck类型方法一样,只要
调用
方法的指令在数据(您的块)中的偏移量相同,并且采用相同的参数和返回类型,就汇编程序而言,这些块就是可交换的

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

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

尽管可以通过创建一个块作为对象(或结构)来缓解一些问题,该对象(或结构)以字节开始,字节与跳转指令相同,之前的指令将指向其数据的指针写入寄存器或将其推送到堆栈上。这样,实际的块代码将作为常规函数共享,对象可以被类型转换为函数指针,并由对块一无所知的人调用

开始时的指令基本上会调用函数,并将指向数据的指针作为隐藏的额外参数传递


实际上,我在博客上写了关于块是如何工作的,以防您想阅读另一篇可能有用的文章:

它返回一个函数引用/指针。我要求您指定编写最后一个带有“类”声明的程序的PL。@FrankC。函数指针只包含函数的地址,不包含任何数据。因此,您不能仅使用函数指针来实现闭包。@sepp2k我不同意您的观点,但基于实现偏差,答案是非常主观的。此外,还存在模糊性,使得封闭式var
a
没有类型。我回答了第一个
type
问题,关于返回的是什么,它是一个函数指针。如图所示,这是一个合理的实现(我不会同意)。仅仅使用函数指针是不可能正确实现闭包的。不能存储任何环境信息。考虑一下用不同参数调用bar的情况。仅使用函数指针不足以表示