【復刻版】黒帯のためのLOOP(1)

はじめに
========

この連載ではCommon LispのLOOPマクロをサンプルを使って紹介する。
LOOPマクロは (cl-loopを用いることで) Emacs Lispでも利用可能である。

【[Common Lisp: loopマクロ用法抄](http://smpl.seesaa.net/article/29800843.html)より引用】
> Grahamの _ANSI Common Lisp_ では嫌われていて碌に説明のないloopマクロ。一方、 _実践Common Lisp_ では対照的に好んで用いられていて、全編に渡って頻繁に使われている。しかしloopマクロは難しいという意識があるのかその説明は第22章とかなり後回しにされており、ちぐはぐな感を受ける。ここでは、 **黒帯のためのLOOP** という題のつけられたその章で解説されているloopマクロの用法を整理してみた。
> 
> _ANSI Common Lisp_ での黒魔術扱いに敬遠していたloopマクロだったが、こうして整理してみるとそれほど難しく考えずとも便利に使うことができそうだ。
> 
> [実践Common Lisp](http://www.amazon.co.jp/exec/obidos/ASIN/4274067211/)
> 作者: Peter Seibel,佐野匡俊,水丸淳,園城雅之,金子祐介
> 出版社/メーカー: オーム社
> 発売日: 2008/07/26
> メディア: 単行本（ソフトカバー）

LOOPのパーツ(**黒帯のためのLOOP**より)
============

LOOPの中では次のようなことができる。

* 数値的な変数や多様なデータ構造にわたる変数を更新していく
* ループしている間に見える値を収集(collect)、計数(count)、合計(sum)、最小化(minimize)、最大化(maximize)する
* 任意のLisp式を実行する
* いつループを終了するかを決定する
* それらを条件付きで実行する

上記に加えて、LOOPは以下のようなシンタックスを提供する。

* ループ内で使用するローカル変数の生成
* ループの前後に実行する任意のLisp式の指定

サンプル (GNU Emacs 28.2 で動作確認しています)
=======

また、@javacommons さんの以下の記事内の xpand-macro/xpand を用いてマクロ展開結果を取得しています。

https://qiita.com/javacommons/items/8fb62f9052df31582168

## 最も単純なループ

```el:マクロ展開
(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の戻り値とする。

```el:マクロ展開
(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つは条件付きで実行される。

```el:マクロ展開
(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 コンストラクトを用いて書くこともできる。

```el:マクロ展開
(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))

```el:マクロ展開
(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つのループ表現の中で併せて利用できる。

```el:マクロ展開
(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マクロサンプル
=======

https://gigamonkeys.com/book/loop-for-black-belts.html

https://programgenjin.hatenablog.com/entry/2019/03/29/152157

http://www.lispworks.com/documentation/lw51/CLHS/Body/m_loop.htm

https://www.unixuser.org/~euske/doc/cl/loop.html