【问题标题】:What are some good ways of implementing tail call elimination?有哪些实现尾调用消除的好方法?
【发布时间】:2011-08-25 13:44:57
【问题描述】:

我已经用 C/C++ 的邪恶组合编写了一个小型 Scheme 解释器,但我还没有实现 proper tail calls

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

我也玩过 longjmps,但到目前为止,我认为它只适用于非相互 recursive 尾调用。

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

【问题讨论】:

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


【解决方案1】:

如果您对解释器的实现技术感兴趣, 无法绕过 Christian Queinnec 的“LiSP - Lisp in Small Pieces”一书。 它非常彻底地解释了实施方案系统的各个方面 完整的代码。这是一本很棒的书。

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 的论文(经典)的链接, 写得很好。它解释并激发了一切 很明确的方式。

【讨论】:

  • +1 支持 LiSP 推荐,但请注意任何离开并从 Queinnec 网站下载代码的人:有几章严重依赖 Meroonet,这是一个类似 CLOS 的对象系统,在结尾处进行了描述的书。在发现有人打包了 Meroon,一个与 Gambit 一起使用的相关对象系统之前,我努力了好几天让它在现代方案中工作。您可以很容易地调整代码以使用 Meroon 而不是 Meroonet 运行,并且它在 Gambit 中没有任何大惊小怪的工作。 YMMV,自然。 math.purdue.edu/~lucier/software/Meroon
  • 感谢您的阅读建议。我确实有 Queinnec 的书,并且我看过他的第一章 eval 和 evlis 解决方案。猜猜他在后面的章节中也使用了 CPS,这似乎是事实上的方式。
【解决方案2】:

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

您必须首先以 continuation-passing 风格编写所有内容,这在 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;
    ...
  }
}

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

Edit^2:非尾递归仍然会消耗内存,即使它不在堆栈中。

【讨论】:

  • (Edit^2) 我更正了有关标准内容的问题,谢谢。
【解决方案3】:

在不知道您拥有什么的情况下,我想说最简单(也是最有启发性)的方法是实现 Dybvig 的“Scheme 的三种实现模型”中的 scheme 编译器和 VM。
我在这里用 Javascript 完成了它(那里也有 Dybvig 的 PDF 的副本):https://github.com/z5h/zb-lisp

检查 src/compiler.js:compileCons,以及 src/vm.js 中“操作码”的实现

【讨论】:

  • 我没有使用底层虚拟机,至少现在还没有。我有 eprogn、eval 和 evlis。但它使用 C 堆栈,因此它在递归循环中爆炸。但是感谢您的建议!
  • 我同意这一点,但假设你不想拥有一个编译器,只想要一个解释器。您将如何在用 C 编写的解释器中实现尾递归?
猜你喜欢
  • 2023-04-02
  • 2010-11-27
  • 2013-08-22
  • 2015-06-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多