Common Lispのformat関数で遊ぶ


Common Lispのformat関数は「驚くほど強力」「無闇に複雑」であること[^1]が知られています。ポール・グレアムのエッセイでも

> ある非常に優れたLispハッカーが、彼のCLtLはformatのところで開く癖がついてしまったと私に語ったことがある。実は私のもそうだ。多分これは改善すべき箇所を暗示している。また、プログラムはI/Oをたくさんするものだということも意味している。
> - [人気の言語を作るには ---Being Popular---](http://practical-scheme.net/trans/being-popular-j.html)

と書かれるほどです。

本記事ではそんなformat関数の魅力をご紹介します。

## format関数について

format関数はCommon Lispにおける書式指定の文字列出力関数で、Cにおけるprintf関数のようなものです。以下のように使います。

```lisp
CL-USER> (format t "Hello ~a!~%" "Arthur")
Hello Arthur!
NIL
```

第1引数は出力先のストリームですが、`t`を指定すると標準出力に出力します。第2引数は書式化文字列です。ここで出力する文字列の書式を指定します。書式化文字列の指示子として使う文字は`~`で、`~a`は対応するパラメータを文字列として出力することを指示します。第3引数以降はオプショナルで、書式化文字列内で利用する分だけパラメータを取ります。

もちろん数値の書式化出力も可能です。

```lisp
CL-USER> (dotimes (n 10)
           (format t "~10,'0d~%" (expt 10 n)))
0000000001
0000000010
0000000100
0000001000
0000010000
0000100000
0001000000
0010000000
0100000000
1000000000
NIL
```

format関数の指示子にどんなものがあるかは[こちらのページ](http://super.para.media.kyoto-u.ac.jp/~tasuku/format-func.html)を見てみてください。

## 英語でおk

ここまでの例だとふつうなのですが、format関数は数字をなんと英語で出力することができます。

```lisp
CL-USER> (format t "~R~%" 10)
ten
NIL
```

Oh。そうです。Common Lispのformat関数なら**数を英語で出力できます**。大きめの数を入力してみるとこうなります。

```lisp
CL-USER> (format t "~R~%" 123981)
one hundred twenty-three thousand nine hundred eighty-one
NIL
```

[ANSI Common Lisp仕様書にもさらっとですが記述がある](http://www.lispworks.com/documentation/lw50/CLHS/Body/22_cba.htm)のです。

## loopなんていらねえ

format関数のパラメータがリストであったとき、`~{`と`~}`を用いてリスト内を繰り返すことができます。

```lisp
CL-USER> (format t "~{~10,'0d~%~}" '(1 2 3 4 5))
0000000001
0000000002
0000000003
0000000004
0000000005
NIL
```

この程度ならloopマクロやdotimesは不要なのであります。

## 再帰的に

`~?`は「format関数のパラメータを書式化文字列だと思って処理する」という、ちょっとなに言ってるのかわからない機能を持ちます。おもしろみのない例ですがこのような感じです。

```lisp
CL-USER> (format t "~?" "(~a ~a)" '(1 2))
(1 2)
NIL
```

`~?`に対応するパラメータ`"(~a ~a)"`を書式化文字列として、`'(1 2)`をパラメータとした書式化出力が実行されています。

以下の例はフロー制御と再帰処理を用いて、[シェル芸界隈で一時期流行っていた響け！ユーフォニアム](https://togetter.com/li/1041621)をやったものです。

```lisp
;; https://twitter.com/sin_clav/status/794148578784444417
CL-USER> (dotimes (n 10)
             (format t "~?~%" (format nil "~~~a*~~@{~~a~~}~~~a:*~~~2:*~a@{~~a~~}" n 10)
                     (map 'list #'identity "響け！ユーフォニアム")))
響け！ユーフォニアム
け！ユーフォニアム響
！ユーフォニアム響け
ユーフォニアム響け！
ーフォニアム響け！ユ
フォニアム響け！ユー
ォニアム響け！ユーフ
ニアム響け！ユーフォ
アム響け！ユーフォニ
ム響け！ユーフォニア
NIL
```

核となるアイデアは、リストを切り出すformat文字列をパラメトリックに生成することです。format関数が二段になっています。内側のformatの行の出力内容を決定するのを担当し、外側のformatは行の実際の出力を担当しています。formatの出力をformatに食わせているので、メタプログラミングのような趣がありますね。もうちょっと短かくならないかしら。

## クワインする

クワインとは「自分自身のソースコードを出力するプログラム」のことです。Common Lispでのクワインはよく以下のコードが挙げられるようです ([Wikipedia調べ](https://ja.wikipedia.org/wiki/%E3%82%AF%E3%83%AF%E3%82%A4%E3%83%B3_(%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0)#Scheme))。

```lisp
((lambda (x) (list x (list 'quote x)))
 '(lambda (x) (list x (list 'quote x))))
```

クワインを実現する基本的なアイデアは、プログラムを「出力を行う部分」と「出力されるデータ」にうまく分け、自己言及の無限の再帰を回避することです。これをなんとかformat関数でできないでしょうか。

**できます！**

`~*`でformat関数がいま見ているパラメータを進めることができますが、`~:*`のように`:`を付けると見ているパラメータを1つ前のものに戻すことができます。これでパラメータを何度でも参照することができます。[^2]それを利用したのがこちら。

```lisp
;; https://twitter.com/sin_clav/status/798765604471644161
CL-USER> (format t "(format t ~s~:* ~s)" "(format t ~s~:* ~s)")
(format t "(format t ~s~:* ~s)" "(format t ~s~:* ~s)")
NIL
```


## おわりに

format関数の持つの可能性は無限大です。今冬はぜひみなさんもREPLでformat関数と戯れてみては？

## 参考文献

- Paul Graham, "ANSI Common Lisp"
- Peter Seibel, "Practical Common Lisp"
- [format関数](http://super.para.media.kyoto-u.ac.jp/~tasuku/format-func.html)

[^1]: Paul Graham, "ANSI Common Lisp", 7.3 出力
[^2]: 無限ループもできます。