Common Lispのformat関数は「驚くほど強力」「無闇に複雑」であること1が知られています。ポール・グレアムのエッセイでも
ある非常に優れたLispハッカーが、彼のCLtLはformatのところで開く癖がついてしまったと私に語ったことがある。実は私のもそうだ。多分これは改善すべき箇所を暗示している。また、プログラムはI/Oをたくさんするものだということも意味している。
と書かれるほどです。
本記事ではそんなformat関数の魅力をご紹介します。
format関数について
format関数はCommon Lispにおける書式指定の文字列出力関数で、Cにおけるprintf関数のようなものです。以下のように使います。
CL-USER> (format t "Hello ~a!~%" "Arthur")
Hello Arthur!
NIL
第1引数は出力先のストリームですが、t
を指定すると標準出力に出力します。第2引数は書式化文字列です。ここで出力する文字列の書式を指定します。書式化文字列の指示子として使う文字は~
で、~a
は対応するパラメータを文字列として出力することを指示します。第3引数以降はオプショナルで、書式化文字列内で利用する分だけパラメータを取ります。
もちろん数値の書式化出力も可能です。
CL-USER> (dotimes (n 10)
(format t "~10,'0d~%" (expt 10 n)))
0000000001
0000000010
0000000100
0000001000
0000010000
0000100000
0001000000
0010000000
0100000000
1000000000
NIL
format関数の指示子にどんなものがあるかはこちらのページを見てみてください。
英語でおk
ここまでの例だとふつうなのですが、format関数は数字をなんと英語で出力することができます。
CL-USER> (format t "~R~%" 10)
ten
NIL
Oh。そうです。Common Lispのformat関数なら数を英語で出力できます。大きめの数を入力してみるとこうなります。
CL-USER> (format t "~R~%" 123981)
one hundred twenty-three thousand nine hundred eighty-one
NIL
ANSI Common Lisp仕様書にもさらっとですが記述があるのです。
loopなんていらねえ
format関数のパラメータがリストであったとき、~{
と~}
を用いてリスト内を繰り返すことができます。
CL-USER> (format t "~{~10,'0d~%~}" '(1 2 3 4 5))
0000000001
0000000002
0000000003
0000000004
0000000005
NIL
この程度ならloopマクロやdotimesは不要なのであります。
再帰的に
~?
は「format関数のパラメータを書式化文字列だと思って処理する」という、ちょっとなに言ってるのかわからない機能を持ちます。おもしろみのない例ですがこのような感じです。
CL-USER> (format t "~?" "(~a ~a)" '(1 2))
(1 2)
NIL
~?
に対応するパラメータ"(~a ~a)"
を書式化文字列として、'(1 2)
をパラメータとした書式化出力が実行されています。
以下の例はフロー制御と再帰処理を用いて、シェル芸界隈で一時期流行っていた響け!ユーフォニアムをやったものです。
;; 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調べ)。
((lambda (x) (list x (list 'quote x)))
'(lambda (x) (list x (list 'quote x))))
クワインを実現する基本的なアイデアは、プログラムを「出力を行う部分」と「出力されるデータ」にうまく分け、自己言及の無限の再帰を回避することです。これをなんとかformat関数でできないでしょうか。
できます!
~*
でformat関数がいま見ているパラメータを進めることができますが、~:*
のように:
を付けると見ているパラメータを1つ前のものに戻すことができます。これでパラメータを何度でも参照することができます。2それを利用したのがこちら。
;; 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関数