这是一个论坛海报给出的例子,我不知道这个尾巴是否优化了。此外,有人可以给一个非专业人员描述尾部优化版本如何胜过正常版本。
(defun mylength (s)
(labels ((mylength-inner (s x)
(if (car s) (mylength-inner (cdr s) (+ x 1)) x)))
(mylength-inner s 0)))
非尾部优化版本?
(defun l (s) (if (car s) (+ 1 (l (rest s))) 0))
答案 0 :(得分:2)
如果函数返回对自身的直接调用或者没有调用自身,则可以对函数进行尾调用优化。函数mylength-inner将返回x
或(mylength-inner (cdr s) (+ x 1))
,因此可以进行尾部优化。
这意味着编译器可以将其转换为循环而不是递归调用函数。要么返回x,要么将(cdr s)赋值给s,递增x,然后再从顶部开始。 (Scheme标准要求实现能够进行此优化,而Common Lisp标准将其留给实现。当然,这种优化是非常有用的,因此大多数实现都会这样做。)
在非优化版本中,l不只是返回对l的调用,而是调用l的结果,添加了一个。这意味着它不能直接转换为循环,因此必须进行所有函数调用。
假设编译器想将l转换为循环。将(rest s)赋值给s是没有问题的,但它放在哪里(1 + ...)
?
答案 1 :(得分:1)
FWIW,CL并不保证它会优化尾调用;这取决于实施。 SBCL支持它。这与Scheme不同,其中规范要求编译器消除尾调用。如果你不这样做,那你就不是Scheme。
此外,尾递归在CL中是非惯用的。我们有一个loop
宏,所以请使用它:)
答案 2 :(得分:0)
可以优化尾调用以在调用堆栈上不需要额外的空间,并且它要求函数中的最后一个操作是递归调用,这在您的论坛源代码示例中就是这种情况。非尾部版本中的最后一个操作是一个加法,因此递归调用需要它自己的堆栈帧。
这是一个简单的模式,定义一个内部函数,除了外部函数参数之外还接受一个累加器参数,并随时累积你的答案。当你到达最后,产生累计值。
这里可能有更好的解释:
http://mitpress.mit.edu/sicp/full-text/book/book-Z-H-11.html#%_sec_1.2.1
答案 3 :(得分:0)
方案的“教科书”列表长度示例如下:http://www.scheme.com/tspl3/start.html#./start:h8搜索“长度”。