Macro DO
, DO*
do
({
var |
(var [init-form [step-form]])}
*) (end-test-form result-form*) declaration* {
tag | statement}
*
=> result*
do*
({
var |
(var [init-form [step-form]])}
*) (end-test-form result-form*) declaration* {
tag | statement}
*
=> result*
var - シンボル
init-form - フォーム
step-form - フォーム
end-test-form - フォーム
result-form - 暗黙のprogn
declaration - 宣言式。評価されません。
tag - go
のタグ。評価されません。
statement - compound-form。下記の説明に従って評価されます。
result - もしreturn
かreturn-from
が実行されたときは、 そのフォームから渡された返却値であり、 それ以外の場合はresult-formによる返却値です。
do
は、テスト条件に入っている状態のときに statememtのグループを繰り返します。 do
は任意の数の繰り返し変数varを束縛し、 それは繰り返し中に並列に実行されます。 初期値は、繰り返し変数にinit-formを使うことによって 与えることができます。 step-formは、繰り返し中にvarを どのように更新して進めるべきかを指定します。 もしend-test-form条件が、ボディ部の実行前に合致したとき、 その繰り返しは終了します。 tagはstatementにラベル付けされます。
do*
は正確にdo
と似ていますが、 ただvarの束縛の進め方が 並列ではなく順番に行われることが違っています。
最初の繰り返し前に、全てのinit-formが評価され、 各varにinit-formの指定があったものに それぞれの値を束縛します。 これは束縛であり代入ではありませんので、 もしループが終了したらそれらの変数は古い値に復旧されるでしょう。 do
は、全てのinit-formは どんなvarについても束縛される前に実行されます。 init-formは、do
の実行が始まる前の varとして見えている束縛を参照することができます。 do*
は、最初のinit-formが評価されたら、 最初のvarにその値の束縛が行われ、 2番目のinit-formが評価されたら、 2番目のvarにその値が束縛され、 それを繰り返します。 つまり一般的に、k
番目のinit-formは、j
番目(j
< k
)の 新しい束縛を参照することができ、 それ以外のとき、j
番目のvarは古い値が参照されます。
各繰り返しが始まり、各変数の処理が終わったあと、 end-test-formが評価されます。 もし結果がfalseのときは、 do
の(あるいはdo*
の)フォームのボディ部が 続けて実行されます。 もし結果がtrueのときは、 result-formが暗黙のprognとして順に評価されれ、 それからdo
かdo*
が返却されます。
各繰り返しが始まり、最初以外の実行のとき、 varは次のように更新されます。 全てのstep-formは、もし指定されていたときは評価され、 それは左から右に行われ、 返却値は対応するvarに代入されます。 step-formを持たないvarは、代入されません。 do
は、全てのstep-formがvarの更新前に評価されます。 varへの値の代入は、psetq
が使用されたかのように並列で行われます。 全てのstep-formはvarが変更される前に評価されるため、 step-formは、他のstep-formの前にあっても 全てのvarは常に古い値を参照するような状況で評価されます。 do*
は、最初のstep-formが評価され、 それから最初のvarにその値が代入され、 そのあと2番目のstep-formが評価され、 そして2番目のvarにその値が代入され、 繰り返します。 変数への代入はsetq
を使用したかのように順番に行われます。 do
とdo*
のどちらも、 全てのvarを更新したあとで すでに説明した通りend-test-formが評価されて、 そして繰り返しが継続します。
do
の(そしてdo*
も同様)残りの部分は、 暗黙のtagbody
で構成されます。
tagはdo
の繰り返し内のボディ部内に出現し、 それはボディ部にgo
文の出現によって使用されます (しかしそのようなgo
文は、変数定義やend-test-form、 result-formには現れないでしょう)。 do
のボディ部が終わりに到達したとき、 次の繰り返しのサイクル(各step-formの評価の最初)が始まります。
nil
という名前の暗黙のblock
が、 do
とdo*
のフォーム全体を囲みます。 return
文は、ループを即座に終了させるために、 どこの地点でも使用することができます。
init-formはvarに関連付けられる初期値です。 もしinit-formが省略されていたときの varの初期値はnil
です。 もしdeclarationがvarに指定されていたときは、 init-formはdeclarationに一致しなければなりません。
declarationは、do
とdo*
のボディ部の最初に 配置することができます。 それらはdo
とdo*
のボディ部のコードと、 do
とdo*
のvarの束縛と、 step-formと、end-test-formと、result-formに適用します。
do ((temp-one 1 (1+ temp-one))
(0 (1- temp-two)))
(temp-two > (- temp-one temp-two) 5) temp-one)) => 4
((
do ((temp-one 1 (1+ temp-one))
(0 (1+ temp-one)))
(temp-two = 3 temp-two) temp-one)) => 3
((
do* ((temp-one 1 (1+ temp-one))
(0 (1+ temp-one)))
(temp-two = 3 temp-two) temp-one)) => 2
((
do ((j 0 (+ j 1)))
(nil) ;永久に実行
(format t "~%Input ~D:" j)
(let ((item (read)))
(if (null item) (return) ;*itemがNILになるまで処理
(format t "~&Output ~D: ~S" j item))))
(0: banana
>> Input 0: BANANA
>> Output 1: (57 boxes)
>> Input 1: (57 BOXES)
>> Output 2: NIL
>> Input => NIL
setq a-vector (vector 1 nil 3 nil))
(do ((i 0 (+ i 1)) ;a-vectorのnullの要素を全部0に設定
(array-dimension a-vector 0)))
(n (= i n))
((when (null (aref a-vector i))
(setf (aref a-vector i) 0))) => NIL
(=> #(1 0 3 0) a-vector
(do ((x e (cdr x))
(oldx x x))
((null x))
body)
上記は、インデックスの変数を並列に代入する例です。 最初の繰り返しでは、 oldx
の値は、x
の値が何であれ、 do
に入る前のx
に設定されます。 続く繰り返しでは、oldx
にはx
が持つ以前の繰り返しの値が含まれます。
do ((x foo (cdr x))
(cdr y))
(y bar (cons (f (car x) (car y)) z)))
(z '() (or (null x) (null y))
((nreverse z))) (
上記の例は(mapcar #'f foo bar)
と同じことを実行します。 x
の次のステップの計算は、 変数が並列で次のステップに計算されるという事実の例になります。 また、繰り返しのボディ部は空です。
defun list-reverse (list)
(do ((x list (cdr x))
(cons (car x) y)))
(y '() (endp x) y))) ((
次にネストされた繰り返しの例として、 コンスのリストというデータ構造を考えるものを示します。 各コンスのcar
はシンボルのリストであり、 各コンスのcdr
は対応する値を含む長さが等しいリストです。 そのようなデータ構造は連想リストに似ていますが、 しかし「フレーム」によって分割されるので、 全体の構造はrib-case(胸郭)に似ています そのようなデータ構造を見る関数を下記に示します。
defun ribcage-lookup (sym ribcage)
(do ((r ribcage (cdr r)))
(null r) nil)
((do ((s (caar r) (cdr s))
(cdar r) (cdr v)))
(v (null s))
((when (eq (car s) sym)
(return-from ribcage-lookup (car v)))))) => RIBCAGE-LOOKUP (
なし。
なし。
他の繰り返し関数(dolist
, dotimes
, loop
)と、 より原始的な機能(tagbody
, go
, block
, return
, let
, setq
)。
もしend-test-formがnil
のとき、 テストは決して成功しないでしょう。 これは「永久に実行」という語を提供し、 do
とdo*
のボディ部は繰り返し実行されます。 このような無限ループは、 return
, return-from
, go
の外側の脱出, throw
によって終了させることができます。
do
フォームはより原始的なフォームである、 block
, return
, let
, loop
, tagbody
, psetq
の語句によって次のように説明できます。
block nil
(let ((var1 init1)
(
(var2 init2)
...
(varn initn))
declarationsloop (when end-test (return (progn . result)))
(tagbody . tagbody)
(psetq var1 step1
(
var2 step2
... varn stepn))))
do*
も似ていますが、 let*
とsetq
を、 それぞれlet
とpsetq
に置き換えたものになります。