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 変数 |
[ |
|
] | [ |
|
] | [ by N ] 〜 |
次の3要素の1つ以上が必要:
from
数値 (始値の指定)from N
/upfrom N
/downfrom N
(デフォルトは0)to
数値 (上限または下限の指定)to
N /upto
N /downto
N /below
N /above
Nby 数値
(増分の指定)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 変数 |
|
[ 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 |
|
in ハッシュ表 [ using ( |
|
変数 ) ] 〜 |
for 変数 being the |
|
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が使われる 〜)
ループ終了条件の追加
|
式 〜 |
式が偽になったら(until
の場合は真になったら)ループは終了。
(loop for y = nil then (cons 'x y) while (< (length y) 3) collect y) ==> (NIL (X) (X X))
集約
|
式 [ into 変数 ] |
式を評価しその結果をそれぞれの方法で集約する。
into
句があると、集約の結果はループ内ローカルな変数に代入される。ない場合は集約の結果はloop
のデフォルトの戻り値となる。
動詞はそれぞれ〜ingという動名詞形でもよい。
collect
: 式を並べたリストを作るappend
: 式をリストとみて、それらを結合したリストを作るnconc
: 〃count
: 式が真となる場合の回数を数えるsum
: 式を足し合わせるmaximize
: 最大値を選ぶminimize
: 最小値を選ぶ
ループ内ローカル変数
with 変数 [ = 式 ] |
任意の式の実行
do 式 [ 式 ... ] |
任意のLisp式を毎回実行。別のloopマクロ内キーワードが出現するかloopマクロの終わりまで任意個のLisp式を続けられる。
条件分岐
|
条件 〜 [ 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) ...)))
繰り返しの初期化・後始末
|
式 [ 式 ... ] |
どちらもdo同様に次のloopキーワードまで任意個の式が書ける。
finally
節が実行されない3条件:
return
節で脱出した場合RETURN
,RETURN-FROM
で脱出した場合 (つまり、finally
はunwind-protect
で実現されるわけではない)- ループが
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)) 〜
参考
- Peter Seibel, Practical Common Lisp / 著者のサイト(オンライン版有)
- Paul Graham, ANSI Common Lisp