我看到以下两个函数都是语法尾递归的函数,但是,在 racket 中,哪些函数实际上被视为尾递归,或两者兼而有之?我的意思是它是否被解释器优化为尾递归。
;;1
(define (foo i m s)
(if (< i m)
(foo (+ i 1) m (+ i s))
s))
;;2
(define (foo i m s)
(if (= i m)
s
(foo (+ i 1) m (+ i s))))
其他lisps中是否有不同的答案?
答案 0 :(得分:4)
递归调用是在尾部位置完成的,这两个都是尾递归的,也就是说:这是调用递归时最后完成的事情。如果在if
表达式中的结果和替代部分的顺序在所示的过程中被颠倒,则完全没有关系。
并且通过Scheme的specification,所有尾部递归必须被优化掉,不会出现在代码中出现的地方,从语法上讲:
Scheme的实现必须适当地尾递归。在称为tail contexts的某些语法上下文中发生的过程调用是尾调用。如果Scheme实现支持无限数量的活动尾调用,则它是正确的尾递归。如果被调用的过程仍可返回,则调用处于活动状态。请注意,这包括常规返回以及通过稍后调用的call-with-current-continuation先前捕获的continuation返回。如果没有捕获的延续,则呼叫最多可以返回一次,并且活动呼叫将是尚未返回的呼叫。关于正确尾递归的正式定义可以在Clinger的论文中找到[5]。从(rnrs base(6))库中构造尾部调用的规则在11.20节中描述。
答案 1 :(得分:3)
只要我们谈论 Scheme :没有。所有符合要求的实现都需要检测尾调用并进行适当的优化,以便那些只需要一个恒定的堆栈空间。在您的示例中,both are perfectly valid tail-calls,并且必须通过任何一致的实现来识别。
另一方面,如果我们正在谈论“一般的Lisp”,那么事情就会有所不同。例如,ANSI Common Lisp 不需要符合条件的实现来专门处理尾调用。虽然大多数现代实现确实识别尾调用(并且会在声明的正确组合下将其优化掉),但语言本身没有任何内容可以保证这种行为。
答案 2 :(得分:3)
Scheme R7RS draft 8的第3.5节第11页根据语法形式识别所有尾递归需求。对于if
,要求是:
(if expression <tail expression> <tail expression>)
(if expression <tail expression>)
因此,基于您的代码示例并假设Racket忠实于Scheme,两者都是尾递归的。至于其他lisps,我不相信Common Lisp需要尾递归优化。
答案 3 :(得分:0)
emacs lisp不优化尾递归:
(defun fact (n) (if (zerop n) 1 (* n (fact (- n 1)))))
(fact 12)
479001600
(fact 1000)
Lisp nesting exceeds max-lisp-eval-depth
(没关系,这个值是一个固定的整数,所以你不能返回1000的准确值!无论如何 - (fact 30)
有效,但返回错误的值。)
要添加前面的记者所说的内容,实现可以在更高的optimization
级别和/或更低的safety
上自由优化尾递归。如果有疑问,请尝试不同的值和(disassemble 'foo)
同时尝试(trace foo)
- 如果尾递归没有优化,那么应在您调用它时看到每个调用,如果是你的话应该只看到“顶级”电话。