t-cool

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

*1:A 1) (B 2) (C 3) (D 4) (E 5

*2:A FOO) (B FOO) (C FOO) (D FOO) (E FOO

*3:1) (1 2) (1 2 3) (1 2 3 4) (1 2 3 4 5) (1 2 3 4 5 6) (1 2 3 4 5 6 7) (1 2 3 4 5 6 7 8) (1 2 3 4 5 6 7 8 9) (1 2 3 4 5 6 7 8 9 10

*4:x 1) (y 2) (z 3

*5:1 X) (2 Y) (3 Z

*6:s "alpha45"