11.3.3 列表递归

一个用while循环打印数字列表元素的例子可以用递归的方式重写。下面是代码,包括一个表达式,将变量animals的值设置为一个列表。

如果你在Emacs的Info中阅读此内容,你可以直接在Info中评估这个表达式。否则,你必须将示例复制到*scratch*缓冲区,并在那里逐个评估每个表达式。使用C-u C-x C-e来评估(print-elements-recursively animals)表达式,以便结果打印在缓冲区中;否则,Lisp解释器将尝试将结果压缩成回显区域的一行。

此外,在print-elements-recursively函数的最后一个闭合括号之后,在注释之前将光标放置在此处。否则,Lisp解释器将尝试评估注释。

(setq animals '(gazelle giraffe lion tiger))

(defun print-elements-recursively (list)
  "将LIST的每个元素单独打印到一行上。
使用递归。"
  (when list                            ; do-again-test
        (print (car list))              ; body
        (print-elements-recursively     ; recursive call
         (cdr list))))                  ; next-step-expression

(print-elements-recursively animals)

print-elements-recursively函数首先测试列表中是否有内容;如果有,函数将打印列表的第一个元素,即列表的CAR。然后,函数调用自身,但将自身作为参数传递,而不是整个列表,而是列表的第二个及后续元素,即列表的CDR

换句话说,如果列表不为空,函数调用另一个与初始代码相似但是不同执行线程的代码实例,其参数与第一个实例不同。

再换一种说法,如果列表不为空,第一个机器人组装第二个机器人并告诉它该做什么;第二个机器人是第一个机器人之外的另一个个体,但是是相同型号。

当进行第二次评估时,when表达式将被评估,如果为真,则打印作为其参数接收到的列表的第一个元素(这是原始列表的第二个元素)。然后,函数使用它调用的列表的CDR调用自身,这是原始列表的CDRCDR(第二次调用时)。

请注意,尽管我们说函数“调用自身”,但我们的意思是Lisp解释器将装配并指导程序的新实例。新实例是第一个的克隆,但是是独立的个体。

每次函数调用自身时,它都是在原始列表的较短版本上进行的。它创建一个在较短列表上运行的新实例。

最终,函数在空列表上调用自身。它创建一个参数为nil的新实例。条件表达式测试list的值。由于list的值是nilwhen表达式测试为假,因此不会评估then部分。因此,整个函数返回nil

当在*scratch*缓冲区中评估表达式(print-elements-recursively animals)时,你将看到以下结果:

gazelle

giraffe

lion

tiger
nil