11
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

LispAdvent Calendar 2020

Day 6

Common Lispのformat関数で遊ぶ

Last updated at Posted at 2020-12-05

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関数
  1. Paul Graham, "ANSI Common Lisp", 7.3 出力

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

11
1
1

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
  3. You can use dark theme
What you can do with signing up
11
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?