【问题标题】:Tail calls in architectures without a call stack没有调用堆栈的架构中的尾调用
【发布时间】:2009-10-10 13:06:22
【问题描述】:

我对最近question about GOTOs and tail recursion 的回答是用调用堆栈来表达的。我担心它不够通用,所以我问你:尾调用(或等价物)的概念在没有调用堆栈的架构中有何用处?

在继续传递中,所有被调用的函数都会替换调用函数,因此是尾调用,因此“尾调用”似乎没有什么有用的区别。在基于消息传递和事件的架构中,似乎没有等价物,但如果我错了,请纠正我。后两种架构是有趣的案例,因为它们与 OOP 而不是 FP 相关联。其他架构呢?旧的 Lisp 机器是基于调用堆栈的吗?

编辑:根据“What the heck is: Continuation Passing Style (CPS)”(以及下面的 Alex),在延续传递下尾调用的等价物不是“被调用函数替换调用函数”,而是“调用函数传递给定的延续,而不是创建新的延续”。与我所说的不同,这种尾调用很有用。

另外,我对在较低级别使用调用堆栈的系统不感兴趣,因为较高级别不需要调用堆栈。这个限制不适用于 Alex 的回答,因为他写的是其他调用架构 (is this the right term?) 通常有一个等效的调用堆栈,而不是他们在引擎盖下的某个地方有一个调用堆栈。在继续传递的情况下,结构类似于arborescence,但边缘方向相反。调用堆栈等价物与我的兴趣高度相关。

【问题讨论】:

    标签: functional-programming callstack tail-call-optimization


    【解决方案1】:

    “没有调用堆栈的架构”通常在某个级别“模拟”一个——例如,早在 IBM 360 时代,我们使用S-Type Linkage Convention 使用寄存器保存区域和参数列表,按照惯例, 通过某些通用寄存器。

    所以“尾调用”仍然很重要:调用函数是否需要保留在调用点之后恢复执行所需的信息(一旦被调用函数完成),或者它是否知道之后将不再执行调用点,因此只需重用 其调用者的“信息以恢复执行”吗?

    因此,例如,尾调用优化可能意味着不在任何用于此目的的链表上添加恢复执行所需的继续......我喜欢将其视为“调用堆栈模拟”(在某种程度上,虽然这显然是一种更灵活的安排——不想让继续传球的粉丝跳到我的答案上;-)。

    【讨论】:

    • 是的,在阅读 sidhe.org/~dan/blog/archives/000185.html 后,我意识到我在 CPS 中的尾调用是错误的
    • 顺便说一句:一个很好的、真实的、相关的无调用堆栈架构的例子是 Parrot 虚拟机,它的控制流完全基于延续。 (有趣的是,它也是基于寄存器的,即它不仅没有调用堆栈,也没有数据堆栈。)
    • 是的,上周四,Alison Randall 在旧金山湾区 Python 兴趣小组 Baypiggies 上谈到了 Parrot(以及她的 Python 3 编译器 Pynie)。我特别向她询问了尾调用优化,她向我保证它就在那里(有一个小小的转折:一些信息被积累起来仍然允许在异常情况下提供信息的“非堆栈跟踪”,这是一个简单的尾调用优化不会给出的) .
    【解决方案2】:

    如果这个问题对我以外的其他人感兴趣,我有一个expanded answer 用于回答这个问题的另一个问题。这是简明扼要的、不严谨的版本。

    当一个计算系统执行子计算时(即一个计算开始并且必须暂停,而另一个计算在执行,因为第一个计算取决于第二个的结果),执行点之间的依赖关系自然会出现。在基于调用堆栈的体系结构中,关系在拓扑上是path graph。在 CPS 中,它是一棵树,其中根和节点之间的每条路径都是一个延续。在消息传递和线程中,它是路径图的集合。同步事件处理基本上是消息传递。开始子计算涉及扩展依赖关系,除了在尾调用中替换叶子而不是附加到它。

    将尾调用转换为异步事件处理更复杂,因此请考虑使用更通用的版本。如果 A 订阅了通道 1 上的事件,B 订阅了通道 2 上的相同事件,并且 B 的处理程序仅触发通道 1 上的事件(它跨通道转换事件),则 A 可以订阅通道上的事件2而不是订阅B。这更通用,因为相当于尾调用需要

    • 当 A 在频道 2 上订阅时,A 在频道 1 上的订阅将被取消
    • 处理程序自行取消订阅(调用时,它们会取消订阅)

    现在针对两个不执行子计算的系统:lambda 演算(或一般的术语重写系统)和 RPN。对于 lambda 演算,尾调用大致对应于项长度为 O(1) 的一系列缩减(参见 SICP section 1.2 中的迭代过程)。让 RPN 使用数据堆栈和操作堆栈(与操作流相反;操作是那些尚未处理的操作),以及将符号映射到操作序列的环境。尾调用可能对应于具有 O(1) 堆栈增长的进程​​。

    【讨论】:

      猜你喜欢
      • 2014-12-10
      • 2012-03-21
      • 1970-01-01
      • 2013-01-30
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-05
      • 2016-02-25
      相关资源
      最近更新 更多