实现尾部呼叫消除的一些好方法是什么?

时间:2011-05-14 16:06:32

标签: c lisp scheme tail-call-optimization trampolines

我用C / C ++的混合编写了一个小的Scheme解释器,但我还没有实现proper tail calls

我知道经典的Cheney on the MTA algorithm,但还有其他很好的实现方法吗?我知道我可以将Scheme堆栈放在堆上,但这仍然不能正确消除,因为标准表示应该支持无限数量的活动尾部调用。

我也摆弄了longjmps,但到目前为止,我认为它只适用于非相互递归尾部调用。

主要的基于C的方案如何实现正确的尾递归?

3 个答案:

答案 0 :(得分:10)

比编写编译器更简单,VM就是注册和蹦床您的解释器。既然你有一个解释器而不是编译器(我假设),你只需要几个简单的转换来获得对尾调用的适当支持。

你必须先用延续传递方式编写所有内容,这在C / C ++中可能很奇怪。 Dan Friedman的 ParentheC 教程将指导您将高级递归程序转换为可以机器翻译为C的表单。

最后,你将基本上实现一个简单的VM,而不是使用常规函数调用来执行eval,applyProc等,通过设置全局变量传递参数,然后执行goto到下一个参数(或使用顶级循环和程序计数器)......

return applyProc(rator, rand)

变为

reg_rator = rator
reg_rand = rand
reg_pc = applyProc
return

也就是说,所有通常以递归方式相互调用的函数都会简化为伪程序集,在这种伪程序集中,它们只是不会重复出现的代码块。顶级循环控制程序:

for(;;) {
  switch(reg_pc) {
    case EVAL:
      eval();
      break;
    case APPLY_PROC:
      applyProc();
      break;
    ...
  }
}

编辑:我使用JavaScript编写了我的业余爱好Scheme解释器的相同过程。我利用了很多匿名程序,但它可能有助于作为一个具体的参考。从2011-03-13( 30707a0432563ce1632a )开始直至2011-03-15( 5dd3b521dac582507086 ),查看 FoxScheme's commit history 。< / p>

编辑^ 2:非尾递归仍会占用内存,即使它不在堆栈中。

答案 1 :(得分:4)

在不知道你拥有什么的情况下,我会说最简单(也是最有启发性)的方法是从Dybvig的“Three Implementation Models for Scheme”中实现方案编译器和VM。
我在这里用Javascript完成了(Dybvig的PDF副本也在那里):https://github.com/z5h/zb-lisp

检查src / compiler.js:compileCons,并在src / vm.js中执行“操作码”

答案 2 :(得分:4)

如果您对口译员的实施技术感兴趣,那里 克里斯蒂安·奎因克(Christian Queinnec)的“LiSP - Lisp in Small Pieces”这本书是不可能的。 它解释了实施Scheme系统的所有方面 完整的代码。这是一本很棒的书。

http://www.amazon.com/exec/obidos/ASIN/0521562473/qid=945541473/sr=1-2/002-2995245-1849825

但请不要忘记查看ReadScheme.org上的论文。

部分

编译器技术/实现技术和优化 http://library.readscheme.org/page8.html

有很多关于尾调用优化的论文。

除此之外,您还可以找到Dybvig论文(经典)的链接, 写得很好。它解释并激励一切 一种非常清晰的方式。