Grahamの ANSI Common Lisp では嫌われていて碌に説明のないloopマクロ。一方、 Practical Common Lisp では対照的に好んで用いられていて、全編に渡って頻繁に使われている。しかしloopマクロは難しいという意識があるのかその説明は第22章とかなり後回しにされており、ちぐはぐな感を受ける。ここでは、LOOP for Black-Belts という題のつけられたその章で解説されているloopマクロの用法を整理してみた。

ANSI Common Lisp での黒魔術扱いに敬遠していたloopマクロだったが、こうして整理してみるとそれほど難しく考えずとも便利に使うことができそうだ。







目次

繰り返し 計数繰り返し コレクション内繰り返し 変数更新しながら繰り返し ループ終了条件の追加

アクション 集約 ループ内ローカル変数 任意の式の実行 条件分岐 ループ中断

その他 初期化・後始末 不変条件 分割代入

参考

概要

一つ以上の繰り返し( for 節)の後に、ループの中身であるアクションを一つ以上書く。アクションの主たるものは値の集約だが、任意の式の実行も可能。

繰り返しは複数個指定でき、その場合は入れ子ではなく同じループの中で並行して扱われる。

繰り返しの終了条件のどれかが満たされるとループは終了する。

アクションも複数指定でき、それらは毎回順番に実行される。

例: この繰り返しではxとyがそれぞれ並行して進む。先にxのリストが尽きるため、yはまだ残りがあるがループは3回で終了する。

(loop for x in (1 3 5) for y in (2 4 6 8 10) collect (cons x y)) ==> ((1 . 2) (3 . 4) (5 . 6))

例: 一つのループで複数のアクション。一つめでyを更新し、二つめでloopの戻り値となる値に集約。

(loop for x in '(1 2 3 4 5) sum x into y collect y) ==> (1 3 6 10 15)

計数繰り返し

for 変数 [ from N upfrom N downfrom N ] [ to N upto N downto N below N above N ] [ by N ] 〜

次の3要素の1つ以上が必要:

from 数値 (始値の指定) from N / upfrom N / downfrom N (デフォルトは0) to 数値 (上限または下限の指定) to N / upto N / downto N / below N / above N by 数値 (増分の指定) by N (デフォルトは1または-1)

デクリメント時の注意点:

始値のデフォルトは存在しないため省略不可。

downfrom か downto / below によって増分の方向が負であることを明示する必要あり。(例えば、 from 20 to 10 等は不可。 downfrom 20 to 10 や from 20 downto 10 等とする)

例:

(loop for i from 10 to 50 by 5 collect i) ==> (10 15 20 25 30 35 40 45 50)

repeat N 〜

N 回繰り返し。カウンタ変数の値そのものは不要な時に。

コレクション内繰り返し

for 変数 in リスト on リスト across ベクタ [ by ステップ関数 ] 〜

ステップ関数は次の要素を保持する位置を得る関数で、 デフォルトは当然 #'cdr 。この関数次第でリストに限らず広く応用もできそうだ。

「 on リスト 」の場合、変数には要素ではなくconsセルが代入される。

(loop for x in '(1 2 3 4) collect x) ==> (1 2 3 4) (loop for x in '(1 2 3 4) by #'cddr collect x) ==> (1 3) ; 一つ飛ばし (loop for x on '(1 2 3) collect x) ==> ((1 2 3) (2 3) (3)) (loop for x on '(1 2 3 4 5) by #'cddr collect x) ==> ((1 2 3 4 5) (3 4 5) (5))

for 変数 being the hash-keys hash-values in ハッシュ表 [ using ( hash-value hash-key 変数 ) ] 〜

for 変数 being the symbols present-symbols external-symbols in パッケージ 〜

ハッシュ表やパッケージの要素についての繰り返し。ハッシュ表ではループ変数はキーか値の一方について繰り返すが using を使うことで他方も参照可能。

(defvar ht (make-hash-table)) (setf (gethash 'foo ht) 1) (setf (gethash 'bar ht) 2) (loop for x being the hash-keys in ht collect x) ==> (BAR FOO) (loop for x being the hash-keys in ht using (hash-value y) collect (cons x y)) ==> ((BAR . 2) (FOO . 1))

変数更新しながら繰り返し

for 変数 = 式1 [ then 式2 ] 〜

最初のループでは式1を評価した結果を変数の値とし、以降のループでは式2(省略された場合は式1)を評価した値で更新する。 do マクロとは異なり、式2が省略された場合も式1を毎回評価して変数を更新する。

複数の変数を更新する場合、 for を複数並べるか、 and を使う。

(loop repeat 5 for x = 0 then y ← 前回のyが使われる for y = 1 then (+ x y) ← 新しいxが使われる 〜) (loop repeat 5 for x = 0 then y ← 前回のyが使われる and y = 1 then (+ x y) ← 前回のxが使われる 〜)

ループ終了条件の追加

while until 式 〜

式 が偽になったら( until の場合は真になったら)ループは終了。

(loop for y = nil then (cons 'x y) while (< (length y) 3) collect y) ==> (NIL (X) (X X))

集約

collect append nconc count sum maximize minimize 式 [ into 変数 ]

式を評価しその結果をそれぞれの方法で集約する。

into 句があると、集約の結果はループ内ローカルな変数に代入される。ない場合は集約の結果は loop のデフォルトの戻り値となる。

動詞はそれぞれ〜ingという動名詞形でもよい。

collect : 式を並べたリストを作る

: 式を並べたリストを作る append : 式をリストとみて、それらを結合したリストを作る

: 式をリストとみて、それらを結合したリストを作る nconc : 〃

: 〃 count : 式が真となる場合の回数を数える

: 式が真となる場合の回数を数える sum : 式を足し合わせる

: 式を足し合わせる maximize : 最大値を選ぶ

: 最大値を選ぶ minimize : 最小値を選ぶ

ループ内ローカル変数

with 変数 [ = 式 ]

任意の式の実行

do 式 [ 式 ... ]

任意のLisp式を毎回実行。別のloopマクロ内キーワードが出現するかloopマクロの終わりまで任意個のLisp式を続けられる。

条件分岐

if when unless 条件 〜 [ else … ] [ end ]

条件は任意のLisp式。 if と when は同義。

〜や…の部分はloopマクロの節。複数の節を入れたい場合はそれらを and で繋ぐ。

end は省略可。

(loop for x from 1 to 5 if (evenp x) collect x) ==> (2 4)

ループ中断

return 式

named ラベル for ...

return はループを中断する。 finally 節は実行されない。ループから脱出したいが正常終了と同様に finally 節を実行したり値を返したい場合、 do 節の中で LOOP-FINISH マクロを用いる。

named はループが作るブロックに名前をつける。その名前を使って RETURN-FROM してもループを抜けられる。

(loop named outer for xs in lists do (loop for x in xs do (if (foop x) (return-from outer x) ...)))

繰り返しの初期化・後始末

initially finally 式 [ 式 ... ]

どちらもdo同様に次のloopキーワードまで任意個の式が書ける。

finally 節が実行されない3条件:

return 節で脱出した場合

節で脱出した場合 RETURN , RETURN-FROM で脱出した場合 (つまり、 finally は unwind-protect で実現されるわけではない)

, で脱出した場合 (つまり、 は で実現されるわけではない) ループが always , never , thereis 条件で終了した場合

不変条件

always never thereis 式 〜

ループの全繰り返しに渡って不変条件が守られたかをloopの戻り値とする。条件が破られるとループを中断、 finally によるエピローグもスキップする。

中断する条件 中断時の戻り値 完了時の戻り値 always 式が偽になったら中断 nil t never 式が真になったら中断 nil t thereis 式がnil以外になったら中断 式の値 nil

分割代入

記述力は destructuring-bind に劣るものの、 for や with による変数への代入は、リストやconsの分割代入が可能。

値を無視したい部分にはnilを使う。

for (a b) in '((1 2) (3 4) (5 6)) 〜 for (kar . kdr) on '((1 . 2) (3 . 4) (5 . 6)) 〜

参考





