2.13 节提到 do
的第一个参数必须是说明变量规格的列表,列表可以是如下形式:
initial
与 update
形式是选择性的。若 update
形式忽略时,每次迭代时不会更新变量。若 initial
形式也忽略时,变量会使用 nil
来初始化。
在 23 页的例子中(译注: 2.13 节),
(defun show-squares (start end)
(do ((i start (+ i 1)))
((> i end) 'done)
(format t "~A ~A~%" i (* i i))))
update
形式引用到由 do
所创造的变量。一般都是这么用。如果一个 do
的 update
形式,没有至少引用到一个 do
创建的变量时,反而很奇怪。
当同时更新超过一个变量时,问题来了,如果一个 形式,引用到一个拥有自己的 update
形式的变量时,它会被更新呢?或是获得前一次迭代的值?使用 do
的话,它获得后者的值:
但也有一个 do*
,它有着和 let
与 let*
一样的关系。任何 initial
或 update
形式可以参照到前一个子句的变量,并会获得当下的值:
> (do* ((x 1 (+ x 1))
(y x x))
(format t "(~A ~A) " x y))
(1 1) (2 2) (3 3) (4 4) (5 5)
NIL
除了 do
与 do*
之外,也有几个特别用途的迭代操作符。要迭代一个列表的元素,我们可以使用 dolist
:
当迭代结束时,初始列表内的第三个表达式 (译注: done
) ,会被求值并作为 dolist
的返回值。缺省是 nil
。
有着同样的精神的是 ,给定某个 n
,将会从整数 0
,迭代至 n-1
:
(dotimes (x 5 x)
(format t "~A " x))
0 1 2 3 4
dolist
与 dotimes 初始列表的第三个表达式皆可省略,省略时为 ``nil
。注意该表达式可引用到迭代过程中的变量。
Note
do 的重点 (THE POINT OF do)
在 “The Evolution of Lisp” 里,Steele 与 Garbriel 陈述了 do 的重点, 表达的实在太好了,值得整个在这里引用过来:
撇开争论语法不谈,有件事要说明的是,在任何一个编程语言中,一个循环若一次只能更新一个变量是毫无用处的。 几乎在任何情况下,会有一个变量用来产生下个值,而另一个变量用来累积结果。如果循环语法只能产生变量, 那么累积结果就得借由赋值语句来“手动”实现…或有其他的副作用。具有多变量的 do 循环,体现了产生与累积的本质对称性,允许可以无副作用地表达迭代过程:
当然在 step 形式里实现所有的实际工作,一个没有主体的 do 循环形式是较不寻常的。
> (mapc #'(lambda (x y)
(format t "~A ~A " x y))
'(hip flip slip)
'(hop flop slop))
HIP HOP FLIP FLOP SLIP SLOP
(HIP FLIP SLIP)
总是返回 的第二个参数。