我正在尝试编写一个Scheme解释器,但发现TCO很难实现。我不确定函数必须具有哪些属性才能使TCO启动。
1)在定义结尾处具有自递归调用的函数:
(define (sum-list list accum)
(if (null list) accum
(sum-list (cdr list) (+ accum 1))))
2)具有自递归调用的函数,该函数不在定义的末尾:
(define (sum-list list accum)
(cond ((not (null list))
(sum-list (cdr list) (+ accum 1)))
(else accum)))
3)在返回变量之前将递归调用存储在变量中的函数:
(define (sum-list list accum)
(let ((sum
(if (null list)
accum
(sum-list (cdr list (+ accum 1))))))
sum))
4)相互递归函数:
(define (sum-list list accum)
(if (null list)
accum
(other-function (cdr list) (+ accum 1))))
(define (other-function list accum)
(sum-list list accum))
5)在函数定义的末尾简单地调用另一个不相关的函数:
(define (foo)
(bar))
6)我能想到的最棘手的案例是封闭。如果我继续引用范围内的变量怎么办?
(define (sum-list list accum ignored)
(let ((local-var 12345))
(if (null list)
accum
(sum-list
(cdr list)
(+ accum 1)
(lambda () local-var)))))
7)在函数定义的末尾调用另一个函数,并以自递归调用作为参数:
(define (sum-list list)
(if (null list)
0
(+ 1 (sum-list (cdr list)))))
据我了解,TCO实现(在Scheme和Common Lisp中)都重写了TCO友好函数,因此它们必须能够静态检测TCO友好函数。
我想知道的是:
答案 0 :(得分:7)
看看Scheme规范,那里定义了所有可能的尾部上下文。特别是在R6RS(当前批准的标准)中,您应该查看§11.20 Tail calls and tail contexts部分:
尾调用是在尾部上下文中发生的过程调用。尾部上下文是归纳的。请注意,尾部上下文始终根据特定的lambda表达式确定。
lambda表达式正文中的最后一个表达式,显示为< tail expression>下面,发生在尾部上下文中。
(lambda <formals> <definition>* <expression>* <tail expression>)
如果以下表达式之一位于尾部上下文中,则子表达式显示为&lt; tail expression&gt;处于尾巴背景下。这些是通过替换某些&lt; expression&gt;的出现来源自本章所述形式语法的规范。与&lt;尾部表达&gt;。这里仅显示包含尾部上下文的那些规则。
(if <expression> <tail expression> <tail expression>) (if <expression> <tail expression>) (cond <cond clause>+) (cond <cond clause>* (else <tail sequence>)) (case <expression> <case clause>+) (case <expression> <case clause>* (else <tail sequence>)) (and <expression>* <tail expression>) (or <expression>* <tail expression>) (let <bindings> <tail body>) (let <variable> <bindings> <tail body>) (let* <bindings> <tail body>) (letrec* <bindings> <tail body>) (letrec <bindings> <tail body>) (let-values <mv-bindings> <tail body>) (let*-values <mv-bindings> <tail body>) (let-syntax <bindings> <tail body>) (letrec-syntax <bindings> <tail body>) (begin <tail sequence>)
A&lt; cond子句&gt;是
(<test> <tail sequence>)
,&lt; case子句&gt;是((<datum>*) <tail sequence>)
,&lt;尾体&gt;是<definition>* <tail sequence>
,&lt;尾序列&gt;是<expression>* <tail expression>
。如果
cond
表达式位于尾部上下文中,并且具有(<expression1> => <expression2>)
形式的子句,那么(隐含)调用由&lt;表达式求值得出的过程<子> 2 子>&GT;处于尾巴背景下。 &LT;表达<子> 2 子>&GT;本身不是尾巴背景。
答案 1 :(得分:7)
你还没有提到这将解释Scheme的运行时或语言,所以可能这个答案有点偏。
实现尾部呼叫优化并同时获得呼叫/ cc的万无一失的方法是进行CPS转换。使用CPS,每个调用都是一个尾调用,而非尾递归代码则是嵌套的延续。
在应用尾调用之前弹出堆栈时会得到TCO。由于每次通话都是CPS的尾部通话,因此您知道该怎么做。
除了3和7之外,你的每个程序都会从中受益。当递归调用返回时,这些程序还有继续。
编辑Python:
Python没有TCO,因此您无法使用主机语言直接进行调用。但是,你可以do it with trampolines。