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

    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 形式的变量时,它会被更新呢?或是获得前一次迭代的值?使用 do 的话,它获得后者的值:

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

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

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

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

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

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

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

    Note

    do 的重点 (THE POINT OF do)

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

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

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

    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)

    总是返回 的第二个参数。