【发布时间】:2011-01-18 23:59:38
【问题描述】:
在编译高阶函数调用时,是否有标准的方法来处理单独编译和不同类型的闭包转换之间的交互?
我知道三种类似函数的构造,它们在大多数编程语言中都有明显的编译:闭包、(顶级)函数和 C++ 风格的函数对象。从语法上讲,它们的调用方式相同,但编译器会以最佳方式生成形状各异的调用点:
Syntax: | clo(args) | func(args) | obj(args)
--------------------------------------------------------------------------------
Codegen: | clo.fnc(&clo.env, args) | func(args) | cls_call(&obj, args)
^ ^ ^ ^ ^
fn ptr | +--"top level" fn --+ |
+--- "extra" param, compared to source type -----+
(在 C++ 中,cls_call 将是 T::operator() 用于 obj 的类 T。C++ 也允许使用虚函子,但这本质上是带有额外间接的闭包情况。)
此时,对map (x => x > 3) lst和map (x => x > y) lst的调用应该调用不同的map函数,因为第一个是提升后的简单函数指针,第二个是闭包。
我能想到四种处理这个问题的方法:
C++ (98) 方法,它强制被调用者要么选择调用点形状(通过形式参数类型:虚拟函子、函数指针或非虚拟函子),要么通过使用模板,有效地指定下面的解决方案 #2。
重载:编译器可以对
map和所有其他高阶函数进行多次实例化,并进行适当的名称修改。实际上,每个调用站点形状都有一个单独的内部函数类型,并且重载解决方案选择了正确的类型。要求一个全球统一的呼叫站点形状。这意味着所有顶级函数都采用显式的
env参数,即使它们不需要它,并且必须引入“额外的”闭包来包装非闭包参数。保留顶级函数的“自然”签名,但要求所有高阶函数参数的处理都必须通过闭包来完成。已关闭函数的“额外”闭包调用包装蹦床函数以丢弃未使用的
env参数。这似乎比选项 3 更优雅,但更难有效实施。编译器要么生成大量调用约定无关的包装器,要么使用少量调用约定敏感的 thunk...
拥有一个优化的闭包转换/lambda提升混合方案,每个函数都可以选择是否将给定的闭包参数粘贴在 env 或参数列表中,这似乎会使问题更加严重。
无论如何,问题:
- 此问题在文献中有明确的名称吗?
- 除了以上四种,还有其他方法吗?
- 方法之间是否存在众所周知的权衡?
【问题讨论】:
标签: compiler-construction programming-languages functional-programming closures