search
LoginSignup
1

More than 1 year has passed since last update.

posted at

updated at

Common Lispのformat関数で遊ぶ

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

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

と書かれるほどです。

本記事ではそんな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関数

  1. Paul Graham, "ANSI Common Lisp", 7.3 出力 

  2. 無限ループもできます。 

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
1