CL Cookbook - loop
CL Cookbookの和訳です。loopの章の和訳を掲載します。
背景
loopマクロは、Common Lispで価値が高いのにも関わらず、ドキュメントが十分に整っていません。なぜ価値が高いのかというと、loopマクロは強力で、コンパクトで、map系の関数や再帰のようなものと比べて読みやすいコードになるからです。loopマクロでは、他の伝統的なプログラミング言語に慣れたプログラマーに親しみやすいスタイルを使います。
ここでは、loopマクロの使い方について説明します。loopマクロは、その内部にCやPascalのような複雑なシンタックスを持っている点で、大半のLispプログラムと異なります。loopマクロで書かれたコードを読むとき、脳の半分をLispモード、もう半分をPascalモードで考える必要があります。
loopマクロには4つのパーツがあると考えてください:
1. 繰り返される変数を設定する式
2. 条件により反復を行う式
3. それぞれの反復に対して何かを行う式
4. loopが終了する前に行う式
さらに、loopマクロは、値を返すこともできます。
loopマクロを使うときに、これらの全てのパーツを使うことはあまりありませんが、それぞれを様々な方法で組み合わせて使うことができます。
このチュートリアルの出典先は、Tutorial for the Common Lisp Loop Macroです。 Peter Karp氏の許可をえて掲載しています。
例
リストを通して反復し、それぞれの要素を出力します。
~~~lisp * (loop for x in '(a b c d e) do (print x) )
A B C D E NIL ~~~
2つのリストを交互に反復して、loopに返される値をコンスセルにまとめます。
~~~lisp * (loop for x in '(a b c d e) for y in '(1 2 3 4 5) collect (list x y) )
*1 ~~~
カウンタと値を用いて反復します。ここでの値は、反復のたびに計算されます。
~~~lisp * (loop for x from 1 to 5 for y = (* x 2) collect y)
(2 4 6 8 10) ~~~
リストを通して反復し、カウンターを交互に反復します。リストの長さは、反復が終了したときに決まります。2つのアクションが定義されており、そのうち1つは条件つきで実行されます。
~~~lisp * (loop for x in '(a b c d e) for y from 1
when (> y 1)
do (format t ", ")
do (format t "~A" x)
)
A, B, C, D, E NIL ~~~
if節を用いてloopを進めることもできます。
~~~lisp * (loop for x in '(a b c d e) for y from 1
if (> y 1)
do (format t ", ~A" x)
else do (format t "~A" x)
)
A, B, C, D, E NIL ~~~
testを用いて、loopを早く実行しましょう。アクションは、任意数の行で構成できます。また、loopのレキシカルスコープの外で定義された変数も参照できます。
~~~lisp * (loop for x in '(a b c d e 1 2 3 4) until (numberp x) collect (list x 'foo))
*2 ~~~
"While"も実行するかのチェックに使うことができます。"do"と"collect"はどちらも、1つの式にまとめることができます。
~~~lisp * (loop for x from 1 for y = (* x 10) while (< y 100)
do (print (* x 5))
collect y)
5 10 15 20 25 30 35 40 45 (10 20 30 40 50 60 70 80 90) ~~~
loopは、様々な方法でネスト(入れ子)にすることが可能です。
~~~lisp * (loop for x from 1 to 10 collect (loop for y from 1 to x collect y) )
*3 ~~~
複数の変数は、複合的なリストの要素を通して、ループすることができます。
~~~lisp * (loop for (a b) in '*4 collect (list b a) )
*5 ~~~
"return"アクションは、loopを止めて、結果を返すことができます。ここでは、文字列のsにある最初の数字を返します。
~~~lisp * (let *6 (loop for i from 0 below (length s) for ch = (char s i) when (find ch "0123456789" :test #'eql) return ch) )
\4
~~~
when/returnの組み合わせを短縮して書くことができるアクションもあります。
lisp
* (loop for x in '(foo 2)
thereis (numberp x))
T
lisp
* (loop for x in '(foo 2)
never (numberp x))
NIL
lisp
* (loop for x in '(foo 2)
always (numberp x))
NIL