はじめに
この連載ではCommon LispのLOOPマクロをサンプルを使って紹介する。
LOOPマクロは (cl-loopを用いることで) Emacs Lispでも利用可能である。
【Common Lisp: loopマクロ用法抄より引用】
Grahamの ANSI Common Lisp では嫌われていて碌に説明のないloopマクロ。一方、 実践Common Lisp では対照的に好んで用いられていて、全編に渡って頻繁に使われている。しかしloopマクロは難しいという意識があるのかその説明は第22章とかなり後回しにされており、ちぐはぐな感を受ける。ここでは、 黒帯のためのLOOP という題のつけられたその章で解説されているloopマクロの用法を整理してみた。
ANSI Common Lisp での黒魔術扱いに敬遠していたloopマクロだったが、こうして整理してみるとそれほど難しく考えずとも便利に使うことができそうだ。
実践Common Lisp
作者: Peter Seibel,佐野匡俊,水丸淳,園城雅之,金子祐介
出版社/メーカー: オーム社
発売日: 2008/07/26
メディア: 単行本(ソフトカバー)
LOOPのパーツ(黒帯のためのLOOPより)
LOOPの中では次のようなことができる。
- 数値的な変数や多様なデータ構造にわたる変数を更新していく
- ループしている間に見える値を収集(collect)、計数(count)、合計(sum)、最小化(minimize)、最大化(maximize)する
- 任意のLisp式を実行する
- いつループを終了するかを決定する
- それらを条件付きで実行する
上記に加えて、LOOPは以下のようなシンタックスを提供する。
- ループ内で使用するローカル変数の生成
- ループの前後に実行する任意のLisp式の指定
サンプル (GNU Emacs 28.2 で動作確認しています)
また、@javacommons さんの以下の記事内の xpand-macro/xpand を用いてマクロ展開結果を取得しています。
最も単純なループ
(cl-loop for x in '(a b c d e)
do (print x))
|
|
v
(let* ((--cl-var-- '(a b c d e))
(x nil))
(while (consp --cl-var--)
(setq x (car --cl-var--))
(print x)
(setq --cl-var-- (cdr --cl-var--)))
nil)
a
b
c
d
e
nil
- 2つのリストの要素を並列に反復して、collect で指定されたフォームを評価した値をリストにつないでLOOPの戻り値とする。
(cl-loop for x in '(a b c d e)
for y in '(1 2 3 4 5)
collect (list x y))
|
|
v
(let* ((--cl-var--_1 '(a b c d e))
(x nil)
(--cl-var--_2 '(1 2 3 4 5))
(y nil)
(--cl-var--_3 nil))
(while (and (consp --cl-var--_1)
(progn
(setq x (car --cl-var--_1))
(consp --cl-var--_2)))
(setq y (car --cl-var--_2))
(setq --cl-var--_3 (cons (list x y) --cl-var--_3))
(setq --cl-var--_1 (cdr --cl-var--_1))
(setq --cl-var--_2 (cdr --cl-var--_2)))
(nreverse --cl-var--_3))
((a 1) (b 2) (c 3) (d 4) (e 5))
条件付きdo (when, if ~ else ~)
- 1つのリストと1つのカウンターを並列に反復する。反復の終了タイミングはリストの長さによって決まる。2セットのアクション(do)が定義されていて、1つは条件付きで実行される。
(cl-loop for x in '(a b c d e)
for y from 1
when (> y 1) do (princ ", ")
do (princ x))
|
|
v
(let* ((--cl-var-- '(a b c d e))
(x nil)
(y 1))
(while (consp --cl-var--)
(setq x (car --cl-var--))
(if (> y 1) (progn (princ ", ")))
(princ x)
(setq --cl-var-- (cdr --cl-var--))
(setq y (+ y 1)))
nil)
a, b, c, d, e
nil
- 直前の loop は if コンストラクトを用いて書くこともできる。
(cl-loop for x in '(a b c d e)
for y from 1
if (> y 1) do (princ ", ") (princ x)
else do (princ x))
|
|
v
(let* ((--cl-var-- '(a b c d e))
(x nil)
(y 1))
(while (consp --cl-var--)
(setq x (car --cl-var--))
(if (> y 1) (progn (princ ", ") (princ x)) (princ x))
(setq --cl-var-- (cdr --cl-var--))
(setq y (+ y 1)))
nil)
a, b, c, d, e
nil
until, while
- テストを用いて loop を早期に終了させることができる。(この例では until で指定した(numberp x))
(cl-loop for x in '(a b c d e 1 2 3 4)
until (numberp x)
collect (list x 'foo))
|
|
v
(let* ((--cl-var--_1 '(a b c d e 1 2 3 4))
(x nil)
(--cl-var--_2 nil))
(while (and (consp --cl-var--_1)
(progn
(setq x (car --cl-var--_1))
(not (numberp x))))
(setq --cl-var--_2 (cons (list x 'foo) --cl-var--_2))
(setq --cl-var--_1 (cdr --cl-var--_1)))
(nreverse --cl-var--_2))
((a foo) (b foo) (c foo) (d foo) (e foo))
- while も終了チェックとしての役目を果たすことができる。do と collect の両方が1つのループ表現の中で併せて利用できる。
(cl-loop for x from 1
for y = (* x 10)
while (< y 100)
do (print (* x 5))
collect y)
|
|
v
(let* ((x 1)
(y nil)
(--cl-var--_1 nil)
(--cl-var--_2 t))
(while (progn
(setq y (* x 10))
(< y 100))
(print (* x 5))
(setq --cl-var--_1 (cons y --cl-var--_1))
(setq x (+ x 1))
(setq --cl-var--_2 nil))
(nreverse --cl-var--_1))
5
10
15
20
25
30
35
40
45
(10 20 30 40 50 60 70 80 90)
終わりに
Emacs Lisp で LOOP マクロを展開すると割と読みやすいソースになるので、この形式(マクロ展開と実行結果の組み合わせ)で、LOOP マクロの紹介をしたいと思う。今回は(1)で尻切れトンボみたいになっているが、何番まで続くかは未定。(説明向けのサンプルを収集中!)
参考文献およびLOOPマクロサンプル