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的情况。仅使用函数指针不足以表示