用于计算列表长度的尾部优化函数的最佳方法是什么?

时间:2009-01-23 18:31:57

标签: lisp stack tail-recursion

这是一个论坛海报给出的例子,我不知道这个尾巴是否优化了。此外,有人可以给一个非专业人员描述尾部优化版本如何胜过正常版本。

(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))

4 个答案:

答案 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搜索“长度”。