如果一个人总是需要这样子思考程序,递归会是艰难的、没有帮助的。递归的优点是它精确地让我们更抽象地来设计算法。你不需要考虑真正函数时所有的调用过程,就可以判断一个递归函数是否是正确的。
要知道一个递归函数是否做它该做的事,你只需要问,它包含了所有的情况吗?举例来说,下面是一个寻找列表长度的递归函数:
- 对长度为 的列表是有效的。
如果这两点是成立的,我们知道这个函数对于所有可能的列表都是正确的。
我们的定义显然地满足第一点:如果列表( lst
) 是空的( nil
),函数直接返回 0
。现在假定我们的函数对长度为 n
的列表是有效的。我们给它一个 长度的列表。这个定义说明了,函数会返回列表的 cdr
的长度再加上 1
。 cdr
是一个长度为 n
的列表。我们经由假定可知它的长度是 n
。所以整个列表的长度是 n+1
。
更复杂的递归函数,可能会有更多的情况需要讨论,但是流程是一样的。举例来说, 41 页的 ,我们需要讨论三个情况: 原子,单一的 Cons 对象, n+1
的 Cons 树。
第一个情况(长度零的列表)称之为基本用例( base case )。当一个递归函数不像你想的那样工作时,通常是处理基本用例就错了。下面这个不正确的 member
定义,是一个常见的错误,整个忽略了基本用例:
(defun our-member (obj lst)
lst
能够判断一个递归函数是否正确只不过是理解递归的上半场,下半场是能够写出一个做你想做的事情的递归函数。 6.9 节讨论了这个问题。