没有调用堆栈的架构中的Tail调用

时间:2009-09-28 02:30:08

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

我对最近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,但边缘方向相反。调用堆栈等价物与我的兴趣高度相关。

2 个答案:

答案 0 :(得分:4)

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

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

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

答案 1 :(得分:2)

如果这个问题与我以外的其他人感兴趣的话,我有另一个问题的expanded answer也可以回答这个问题。这是简洁,非严格的版本。

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

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

  • 在第2频道订阅A时,A频道的订阅将被取消
  • 处理程序是自动取消订阅(调用时,取消订阅)

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