5.4 迭代 (Iteration)

最基本的迭代操作符是 do ,在 2.13 小节介绍过。由于 do 包含了隐式的 blocktagbody ,我们现在知道是可以在 do 主体内使用 returnreturn-from 以及 go

2.13 节提到 do 的第一个参数必须是说明变量规格的列表,列表可以是如下形式:

  1. (variable initial update)

initialupdate 形式是选择性的。若 update 形式忽略时,每次迭代时不会更新变量。若 initial 形式也忽略时,变量会使用 nil 来初始化。

在 23 页的例子中(译注: 2.13 节),

  1. (defun show-squares (start end)
  2. (do ((i start (+ i 1)))
  3. ((> i end) 'done)
  4. (format t "~A ~A~%" i (* i i))))

update 形式引用到由 do 所创造的变量。一般都是这么用。如果一个 doupdate 形式,没有至少引用到一个 do 创建的变量时,反而很奇怪。

当同时更新超过一个变量时,问题来了,如果一个 update 形式,引用到一个拥有自己的 update 形式的变量时,它会被更新呢?或是获得前一次迭代的值?使用 do 的话,它获得后者的值:

  1. > (let ((x 'a))
  2. (do ((x 1 (+ x 1))
  3. (y x x))
  4. ((> x 5))
  5. (format t "(~A ~A) " x y)))
  6. (1 A) (2 1) (3 2) (4 3) (5 4)
  7. NIL

每一次迭代时, x 获得先前的值,加上一; y 也获得 x 的前一次数值。

但也有一个 do* ,它有着和 letlet* 一样的关系。任何 initialupdate 形式可以参照到前一个子句的变量,并会获得当下的值:

  1. > (do* ((x 1 (+ x 1))
  2. (y x x))
  3. ((> x 5))
  4. (format t "(~A ~A) " x y))
  5. (1 1) (2 2) (3 3) (4 4) (5 5)
  6. NIL

除了 dodo* 之外,也有几个特别用途的迭代操作符。要迭代一个列表的元素,我们可以使用 dolist :

  1. > (dolist (x '(a b c d) 'done)
  2. (format t "~A " x))
  3. A B C D
  4. DONE

当迭代结束时,初始列表内的第三个表达式 (译注: done ) ,会被求值并作为 dolist 的返回值。缺省是 nil

有着同样的精神的是 dotimes ,给定某个 n ,将会从整数 0 ,迭代至 n-1 :

  1. (dotimes (x 5 x)
  2. (format t "~A " x))
  3. 0 1 2 3 4
  4. 5

dolistdotimes 初始列表的第三个表达式皆可省略,省略时为 ``nil 。注意该表达式可引用到迭代过程中的变量。

(译注:第三个表达式即上例之 x ,可以省略,省略时 dotimes 表达式的返回值为 nil 。)

Note

do 的重点 (THE POINT OF do)

在 “The Evolution of Lisp” 里,Steele 与 Garbriel 陈述了 do 的重点, 表达的实在太好了,值得整个在这里引用过来:

撇开争论语法不谈,有件事要说明的是,在任何一个编程语言中,一个循环若一次只能更新一个变量是毫无用处的。 几乎在任何情况下,会有一个变量用来产生下个值,而另一个变量用来累积结果。如果循环语法只能产生变量, 那么累积结果就得借由赋值语句来“手动”实现…或有其他的副作用。具有多变量的 do 循环,体现了产生与累积的本质对称性,允许可以无副作用地表达迭代过程:

  1. (defun factorial (n)
  2. (do ((j n (- j 1))
  3. (f 1 (* j f)))
  4. ((= j 0) f)))

当然在 step 形式里实现所有的实际工作,一个没有主体的 do 循环形式是较不寻常的。

函数 mapcmapcar 很像,但不会 cons 一个新列表作为返回值,所以使用的唯一理由是为了副作用。它们比 dolist 来得灵活,因为可以同时遍历多个列表:

  1. > (mapc #'(lambda (x y)
  2. (format t "~A ~A " x y))
  3. '(hip flip slip)
  4. '(hop flop slop))
  5. HIP HOP FLIP FLOP SLIP SLOP
  6. (HIP FLIP SLIP)

总是返回 mapc 的第二个参数。