2.7 递归 (Recursion)
上一节我们所定义的函数,调用了别的函数来帮它们做事。比如 sum-greater
调用了 +
和 >
。函数可以调用任何函数,包括自己。自己调用自己的函数是递归的。 Common Lisp 函数 member
,测试某个东西是否为列表的成员。下面是定义成递归函数的简化版:
> (defun our-member (obj lst)
(if (null lst)
nil
(if (eql (car lst) obj)
lst
(our-member obj (cdr lst)))))
OUR-MEMBER
谓词 eql
测试它的两个实参是否相等;此外,这个定义的所有东西我们之前都学过了。下面是运行的情形:
> (our-member 'b '(a b c))
(B C)
> (our-member 'z '(a b c))
NIL
下面是 our-member
的定义对应到英语的描述。为了知道一个对象 obj
是否为列表 lst
的成员,我们
- 首先检查
lst
列表是否为空列表。如果是空列表,那obj
一定不是它的成员,结束。- 否则,若
obj
是列表的第一个元素时,则它是列表的成员。- 不然只有当
obj
是列表其余部分的元素时,它才是列表的成员。
当你想要了解递归函数是怎么工作时,把它翻成这样的叙述有助于你理解。
起初,许多人觉得递归函数很难理解。大部分的理解难处,来自于对函数使用了错误的比喻。人们倾向于把函数理解为某种机器。原物料像实参一样抵达;某些工作委派给其它函数;最后组装起来的成品,被作为返回值运送出去。如果我们用这种比喻来理解函数,那递归就自相矛盾了。机器怎可以把工作委派给自己?它已经在忙碌中了。
较好的比喻是,把函数想成一个处理的过程。在过程里,递归是在自然不过的事情了。日常生活中我们经常看到递归的过程。举例来说,假设一个历史学家,对欧洲历史上的人口变化感兴趣。研究文献的过程很可能是:
- 取得一个文献的复本
- 寻找关于人口变化的资讯
- 如果这份文献提到其它可能有用的文献,研究它们。
过程是很容易理解的,而且它是递归的,因为第三个步骤可能带出一个或多个同样的过程。
所以,别把 our-member
想成是一种测试某个东西是否为列表成员的机器。而是把它想成是,决定某个东西是否为列表成员的规则。如果我们从这个角度来考虑函数,那么递归的矛盾就不复存在了。
当前内容版权归 readthedocs 或其关联方所有,如需对内容或内容相关联开源项目进行关注与资助,请访问 readthedocs .