Macro DO, DO*

UP


Macro DO, DO*

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 - もしreturnreturn-fromが実行されたときは、 そのフォームから渡された返却値であり、 それ以外の場合はresult-formによる返却値です。

定義

doは、テスト条件に入っている状態のときに statememtのグループを繰り返します。 doは任意の数の繰り返し変数varを束縛し、 それは繰り返し中に並列に実行されます。 初期値は、繰り返し変数にinit-formを使うことによって 与えることができます。 step-formは、繰り返し中にvarを どのように更新して進めるべきかを指定します。 もしend-test-form条件が、ボディ部の実行前に合致したとき、 その繰り返しは終了します。 tagstatementにラベル付けされます。

do*は正確にdoと似ていますが、 ただvarの束縛の進め方が 並列ではなく順番に行われることが違っています。

最初の繰り返し前に、全てのinit-formが評価され、 各varinit-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として順に評価されれ、 それからdodo*が返却されます。

各繰り返しが始まり、最初以外の実行のとき、 varは次のように更新されます。 全てのstep-formは、もし指定されていたときは評価され、 それは左から右に行われ、 返却値は対応するvarに代入されます。 step-formを持たないvarは、代入されません。 doは、全てのstep-formvarの更新前に評価されます。 varへの値の代入は、psetqが使用されたかのように並列で行われます。 全てのstep-formvarが変更される前に評価されるため、 step-formは、他のstep-formの前にあっても 全てのvarは常に古い値を参照するような状況で評価されます。 do*は、最初のstep-formが評価され、 それから最初のvarにその値が代入され、 そのあと2番目のstep-formが評価され、 そして2番目のvarにその値が代入され、 繰り返します。 変数への代入はsetqを使用したかのように順番に行われます。 dodo*のどちらも、 全てのvarを更新したあとで すでに説明した通りend-test-formが評価されて、 そして繰り返しが継続します。

doの(そしてdo*も同様)残りの部分は、 暗黙のtagbodyで構成されます。

tagdoの繰り返し内のボディ部内に出現し、 それはボディ部にgo文の出現によって使用されます (しかしそのようなgo文は、変数定義やend-test-formresult-formには現れないでしょう)。 doのボディ部が終わりに到達したとき、 次の繰り返しのサイクル(各step-formの評価の最初)が始まります。

nilという名前の暗黙のblockが、 dodo*のフォーム全体を囲みます。 return文は、ループを即座に終了させるために、 どこの地点でも使用することができます。

init-formvarに関連付けられる初期値です。 もしinit-formが省略されていたときの varの初期値はnilです。 もしdeclarationvarに指定されていたときは、 init-formdeclarationに一致しなければなりません。

declarationは、dodo*のボディ部の最初に 配置することができます。 それらはdodo*のボディ部のコードと、 dodo*varの束縛と、 step-formと、end-test-formと、result-formに適用します。

例文

(do ((temp-one 1 (1+ temp-one))
      (temp-two 0 (1- temp-two)))
     ((> (- temp-one temp-two) 5) temp-one)) =>  4

(do ((temp-one 1 (1+ temp-one))
      (temp-two 0 (1+ temp-one)))     
     ((= 3 temp-two) temp-one)) =>  3

(do* ((temp-one 1 (1+ temp-one))
       (temp-two 0 (1+ temp-one)))
      ((= 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))))
>>  Input 0: banana
>>  Output 0: BANANA
>>  Input 1: (57 boxes)
>>  Output 1: (57 BOXES)
>>  Input 2: NIL
=>  NIL

(setq a-vector (vector 1 nil 3 nil))
(do ((i 0 (+ i 1))     ;a-vectorのnullの要素を全部0に設定
     (n (array-dimension a-vector 0)))
    ((= i n))
  (when (null (aref a-vector i))
    (setf (aref a-vector i) 0))) =>  NIL
a-vector =>  #(1 0 3 0)
(do ((x e (cdr x))
     (oldx x x))
    ((null x))
  body)

上記は、インデックスの変数を並列に代入する例です。 最初の繰り返しでは、 oldxの値は、xの値が何であれ、 doに入る前のxに設定されます。 続く繰り返しでは、oldxにはxが持つ以前の繰り返しの値が含まれます。

(do ((x foo (cdr x))
     (y bar (cdr y))
     (z '() (cons (f (car x) (car y)) z)))
    ((or (null x) (null y))
     (nreverse z)))

上記の例は(mapcar #'f foo bar)と同じことを実行します。 xの次のステップの計算は、 変数が並列で次のステップに計算されるという事実の例になります。 また、繰り返しのボディ部は空です。

(defun list-reverse (list)
       (do ((x list (cdr x))
            (y '() (cons (car x) 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))
              (v (cdar r) (cdr 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-formnilのとき、 テストは決して成功しないでしょう。 これは「永久に実行」という語を提供し、 dodo*のボディ部は繰り返し実行されます。 このような無限ループは、 return, return-from, goの外側の脱出, throwによって終了させることができます。

doフォームはより原始的なフォームである、 block, return, let, loop, tagbody, psetq の語句によって次のように説明できます。

(block nil        
  (let ((var1 init1)
        (var2 init2)
        ...
        (varn initn))
    declarations
    (loop (when end-test (return (progn . result)))
          (tagbody . tagbody)
          (psetq var1 step1
                 var2 step2
                 ...
                 varn stepn))))

do*も似ていますが、 let*setqを、 それぞれletpsetqに置き換えたものになります。


TOP, Github