第22章「出力(format関数)」

概要

ANSI Common Lispの第22章「出力」を説明します。

万能の出力関数: format

第22章は「プリンター」すなわち出力に関する情報が定義されています。しかし、多くは日常的なプログラミングで使うことは稀で、処理系の実装者向けに定義されています。

Common Lispには何種類も出力を行うための関数が定められていますが、中でも万能と言えるのがformat関数です。この関数はフォーマット文字列と呼ばれる独自の言語を持っています。
format関数は使いこなせれば極めて強力であり、このサイトの様々な箇所で暗黙的に使ってきました。ここでは、format関数の機能だけに特化して、丁寧に紹介していきます。ただ、仕様を網羅して説明すると分かりにくいので、機能は抜粋する代わりにサンプルを付けて紹介します。

第1引数の扱い

format関数の第1引数は destination 、すなわち「目的地」です。その値は4種類です。

意味 返り値
t 標準出力ストリーム(*standard-output*)に出力します nil
nil 文字列を返り値として返します フォーマット後の文字列
stream ストリームに出力します nil
string フィルポインタ付の文字列に出力します nil

フィルポインタ付の文字列を目的地とする場合のみ、サンプルを示します。
(defparameter *str* (make-array 0 :element-type 'base-char 
                                  :adjustable t 
                                  :fill-pointer 0))
; => *STR*

(format *str* "abc")
; => NIL

*str*
; => "abc"

基本フォーマット

この節では最も基本的なフォーマットを説明します。

文字: ~c

文字オブジェクトを表示するには~cを使用します。このフォーマットには2つの亜種が存在します。文字を読み取り可能な形(#\付)で表示する~@cと、制御文字の名前を表示する~@cです。

フォーマット 意味
~c 文字をそのまま出力します
~@c 文字を読み込み可能な形式で付で出力します
~:c 制御文字については文字の名前(char-name)を出力します
;; 文字をそのまま表示する
(format t "~c" #\newline)
; 
; => NIL

;; 文字を読み込み可能な形で表示する
(format t "~@c" #\newline)
; #\Newline
; => NIL

;; 文字の名前を表示する
(format t "~:c" #\newline)
; Newline
; => NIL

改行: ~%

改行文字は~%で出力できます。複数の改行を出力する場合、~N%を使います。
(format t "a~2%b~3%c")
; a
; 
; b
; 
; 
; c
; => NIL

チルダ: ~~

チルダはformat関数においてフォーマットに使用されるため、チルダそのものを表示する場合は2つを重ねてエスケープします。複数出力する場合は、~N~を使います。
(format t "~~")
; ~
; => NIL

(format t "~10~")
; ~~~~~~~~~~
; => NIL

汎用: ~a, ~s

format関数のフォーマットは非常に多様ですが、特に複雑な処理を行うわけではなく、「いい感じ」で処理したい時は~a~sが便利です。
;; 数は同じ
(format t "~a ~s" 10 10)
; 10 10
; => NIL

;; ~a は「いい感じ」、~s は読み込み可能
(format t "~a ~s" #\a #\a)
; a #\a
; => NIL

(format t "~a ~s" "abc" "abc")
; abc "abc"
; => NIL
~sは多くの場合、再度読み込みが可能な形式でオブジェクトを表示します。~aは一般的に人間が読んで分かりやすいように表示します。

これらの汎用フォーマットはいくつかのオプションが使えます。その中でもよく使う3種類を紹介します。~a~sで共通して使えるので、~aのみで示します。

フォーマット 意味
~Na N 文字に満たない場合、空白で埋めます
~N,,,'Ca 空白ではなく文字 C で埋めます
~N@a 左ではなく右に配置します
~:a nil()と表示します
;; 数をつけると空白で埋める
(format t "~5a ~5a" #\a #\b)
; a     b    
; => NIL

;; 埋める文字を指定することができる
(format t "~5,,,'0a ~5,,,'Xa" #\a #\b)
; a0000 bXXXX
; => NIL

;; @ を付けると右寄せになる
(format t "~5,,,'0@a ~5,,,'X@a" #\a #\b)
; 0000a XXXXb
; => NIL

;; : を付けると NIL が () になる
(format t "~a ~:a" nil nil)
; NIL ()
; => NIL

整数フォーマット

この節では、整数のフォーマットを紹介します。

整数は表示したい進数によってフォーマットの種類が異なりますが、オプションは基本的にどれも同じです。

10進数: ~d

整数を10進数で表示したい場合は~dを使います。この~dをはじめとして、整数を表示するフォーマットには共通したオプションが存在しますので、紹介します。

フォーマット 意味
~Nd N 文字に満たない場合、空白で埋めます
~N,'Cd 空白ではなく文字 C で埋めます
~:d 3桁毎に区切って表示します
~@:d +または-の符号を必ず表示します
(format t "~d" 10000)
; 10000
; => NIL

;; 空白で埋める
(format t "~10d" 10000)
;      10000
; => NIL

;; 埋める文字を指定する
(format t "~10,'Xd" 10000)
; XXXXX10000
; => NIL

;; 符号を必ず表示する
(format t "~@d" 10000)
; +10000
; => NIL

;; カンマで区切る
(format t "~:d" 10000)
; 10,000
; => NIL

2進数: ~b

format関数は整数の進数を変換することができ、~bを指定すると2進数になります。
2進数で表示する場合、0埋めのフォーマットと区切りのフォーマットが役に立ちます。

フォーマット 意味
~4,'0b 4bit単位で表示し、0で埋めます
~9,'0,' ,4:b 8bit(1オクテット)の数を4bit単位で表示し、0で埋めます
(format t "~b" 5)
; 101
; => NIL

;; 4bit 単位で表示する
(format t "~4,'0b" 5)
; 0101
; => NIL

;; 4bit 単位で表示すると間にスペースが入るので、全部で9文字
;; オプションは
;;   全部で9文字
;;   0で埋める
;;   スペースで区切る
;;   4桁で区切る
;;   区切りを入れる(:)
;; という順番
(format t "~9,'0,' ,4:b" 125)
; 0111 1101
; => NIL

2進数を綺麗に表示するのはformat関数の得意とするところです。このような何気ない処理も、自分でプログラムしようと思えば結構難しいのです。

8進数: ~o

8進数は3bitに対応するので、あまり使うことはないかもしれませんが、私はUnixのパーミッションを調べるときにサクッと使ったりします。整数を8進数で表示するには~oフォーマットを使います。
(format t "~o" #b111101101)
; 755
; => NIL

ファイルのパーミッションは所有者・グループ・その他という順番で、それぞれ読み・書き・実行というビットで構成されるので、全ての権限を与える場合は#b111で、読みと実行だけを与える場合は#b101です。これを並べると上の例の#b111101101となり、これを8進数に変換すると755になります。

16進数: ~x

16進数は4bitに対応しており、16進数を2つ並べることで1オクテットを表現できるので、頻繁に利用されます。

16進数を表示するには~xを使いますが、16進数は一般に2文字並べて表示するため、以下のような使い方が便利です。

フォーマット 意味
~2,'0x 8bit(1オクテット)の数を常に2文字で表示します
~5,'0,' ,2:x 16bit(2オクテット)の数を2文字区切りで表示します
(format t "~x" 255)
; FF
; => NIL

;; 8bit(= 15)以下の数も2文字で表示する
(format t "~2,'0x" 10)
; 0A
; => NIL

;; 9bit以上16bit以下の数を2文字区切りで表示する
(format t "~5,'0,' ,2:x" 12345)
; 30 39
; => NIL

N進数・英語・ローマ数字: ~r

整数に関して最後に紹介する~rフォーマットは、5種類の使い方があります。

フォーマット 意味
~Nr N 進数で表示します
~r 英語で数を表示します
~:r 英語の序数で数を表示します
~@r ローマ数字で数を表示します
~:@r 旧式のローマ数字で数を表示します
;; N進数
(format t "~2r" 9)
; 1001
; => NIL

;; 英語
(format t "~r" 9)
; nine
; => NIL

;; 英語の序数(-th)
(format t "~:r" 9)
; ninth
; => NIL

;; ローマ数字
(format t "~@r" 9)
; IX
; => NIL

;; 旧式のローマ数字
(format t "~:@r" 9)
; VIIII
; => NIL

10くらいまでの数しか表示できないのではないか、と思った方はCommon Lispの力を過小評価しているでしょう。
(format t "~r" 123)
; one hundred and twenty-three
; => NIL

(format t "~r" 123456789)
; one hundred and twenty-three million, 
; four hundred and fifty-six thousand, 
; seven hundred and eighty-nine
; => NIL

小数点数フォーマット

この節では小数点を含む数のフォーマットについて説明します。

小数: ~f, ~$

小数点数をシンプルに表示するには~fを使用します。小数点数のフォーマットで最も利用するのは、小数点第 N 位まで表示する、というコントロールだと思います。

フォーマット 意味
~,Nf 小数点第 N 位まで表示します
~@f 符号を必ず表示します
(format t "~f" 3.1415)
; 3.1415
; => NIL

;; 小数点第 2 位まで表示する
(format t "~,2f" 3.1415)
; 3.14
; => NIL

;; 符号を必ず表示する
(format t "~@f" 3.1415)
; +3.1415
; => NIL

ただし、小数点第2位まで表示する場合は、~$を使うことができます。
(format t "~$" 3.1415)
; 3.14
; => NIL

指数: ~e

大きな数などの場合は指数で表示するのが便利な場合もあります。指数で表示するには~eを使います。オプションは~fと同じです。
(format t "~e" 3.1415)
; 3.1415E+0
; => NIL

;; 小数点第 2 位まで表示する
(format t "~,2e" 3.1415)
; 3.14E+0
; => NIL

;; 10^3 = 1000
(format t "~,2e" 3141.5)
; 3.14E+3
; => NIL

;; 符号を必ず表示する
(format t "~,2@e" 3141.5)
; +3.14E+3
; => NIL

レイアウトフォーマット

この節では表示のレイアウトに関するフォーマットを紹介します。

空白: ~t

空白を複数出力するには~tフォーマットが便利です。このフォーマットはシンプルに使うとただスベースを1文字出力するだけなので、そのままではほとんど使いません。おそらく、~N@tという指定が一番便利なのではないでしょうか。

フォーマット 意味
~Nt 行頭から数えて N 個の空白を出力する
~N@f 手前の出力結果から数えて N 個の空白を出力する
;; シンプルに使用した場合、1文字の空白を出力する
(format t "~tA~tB~tC")
;  A B C
; => NIL

;; N を指定すると行頭から数えて空白を出力する
;; 2回目も行頭から数えるので、指定は意味がなくなる
(format t "~5tA~5tB~5tC")
;      A B C
; => NIL

;; @ をつけると手前の出力結果から数える
(format t "~5@tA~5@tB~5@tC")
;      A     B     C
; => NIL

等間隔: ~<, ~>, ~;

文字列を等間隔で出力するには、~<~>を組み合わせます。これらは文字幅を指定しなければ意味がないので、必ず文字幅を指定します。また、これらの中で用いる区切りフォーマットが~;です。

フォーマット 意味
~N<全部で N 文字の幅の中で左端と右端に文字列を配置し、間を等間隔のスペースで区切ります
~N:<全部で N 文字の幅の中で右に詰めて文字列を配置し、左側も含めて等間隔のスペースで区切ります
~N@<全部で N 文字の幅の中で左に詰めて文字列を配置し、右側も含めて等間隔のスペースで区切ります

;; 左右に詰めて文字列を配置するので、区切りスペースは2つ
;; 全部で12文字のうち、文字6文字を除く6文字を2つのスペースに分けるので、
;;   3スペース + 3スペース
;; になる
(format t "~12<ab~;cd~;ef~>")
; ab   cd   ef
; => NIL

;; 右寄せにして左側にも空白を配置するので、
;;   2スペース + 2スペース + 2スペース
;; になる
(format t "~12:<ab~;cd~;ef~>")
;   ab  cd  ef
; => NIL

;; 左寄せにして右側にも空白を配置するので、
;;   2スペース + 2スペース + 2スペース
;; になる
(format t "~12@<ab~;cd~;ef~>")
; ab  cd  ef  ; ここまでスペースが出力されている
; => NIL

制御フォーマット

前小節で紹介したレイアウトフォーマットは制御に関わるものですが、Common Lispのformat関数はもっと様々な制御を行うことができます。ここでは、いくつかの制御フォーマットを紹介します。

消費: ~*

最も原始的な制御フォーマットは~*です。このフォーマットもオプションによって動作が大きく異なります。

フォーマット 意味
~* 1個分の引数を何もせずに消費します
~N* N 個分の引数を何もせずに消費します
~N:* N 個分の引数を戻して、次に進みます
~@* 最初の引数を戻して、次に進みます
~N@* N 番目の引数を取り出して、次に進みます
;; シンプルに使うと、1つの引数を消費して何もしない
(format t "~a ~* ~a" 1 2 3)
; 1  3
; => NIL

;; N を指定すると、N個の引数を消費する
(format t "~a ~2* ~a" 1 2 3 4)
; 1  4
; => NIL

;; : を付けると消費した引数を元に戻す
(format t "~a ~:* ~a" 1)
; 1  1
; => NIL
(format t "~a ~a ~a ~3:* ~a ~a ~a" 1 2 3)
; 1 2 3  1 2 3
; => NIL

;; @ を付けると、指定した引数にジャンプして取り出してくる
(format t "~a ~@* ~a" 1 2 3)
; 1  1
; => NIL
(format t "~a ~1@* ~a" 1 2 3)
; 1  2
; => NIL

条件: ~[ ~]

format関数には簡単な条件分岐を行うフォーマット文字列が用意されています。それが~[~]です。

このフォーマット文字列もオプションによって大きく動作が異なります。

フォーマット 意味
~[N0~;N1~;N2~] 引数をインデックスとして N 番目の要素を表示します
~[N0~;N1~;N2~:;Otherwise~] 引数に対応する要素が存在しない場合、Otherwiseを表示します
~:[FALSE~;TRUE~] 引数の真偽に応じて表示する要素を変えます
~@[TRUE~] 引数が真の場合、引数を消費せず要素を表示します。偽の場合、要素は表示しませんが引数は消費します
~@[TRUE~*~] 引数が真の場合のみ表示します。真でも偽でも引数は消費されます
;; 引数をインデックスに見立て、対応する要素を表示する
(format t "~[none~;one~;two~;three~]" 0)
; none
; => NIL
(format t "~[none~;one~;two~;three~]" 2)
; two
; => NIL

;; 引数に対応する要素がない場合、~:; の直後の要素を表示する
(format t "~[none~;one~;two~;three~:;something~]" 10)
; something
; => NIL

;; 真偽に対応する要素を表示する
(format t "~:[odd~;even~]" (evenp 11))
; odd
; => NIL
(format t "~:[odd~;even~]" (evenp 12))
; even
; => NIL
~@[フォーマットはそのまま使うと、引数を消費するかどうかが真偽で異なります。先に、~*を使って真の場合も消費するようにしたパターンを示します。一般的には~a~sなど、引数を消費するフォーマットを入れるか、引数の値を使わないなら~*で消費するようにした方が普通の使い方には合っていると思います。
(format t "~@[Opt1-ON~*~] ~@[Opt2-ON~*~]" t t)
; Opt1-ON Opt2-ON
; => NIL

(format t "~@[Opt1-ON~*~] ~@[Opt2-ON~*~]" t nil)
; Opt1-ON 
; => NIL

(format t "~@[Opt1-ON~*~] ~@[Opt2-ON~*~]" nil t)
;  Opt2-ON
; => NIL

(format t "~@[Opt1-ON~*~] ~@[Opt2-ON~*~]" nil nil)
; 
; => NIL

もし引数を消費するようにコントロールしなければ、1つ目がtの場合にその引数が残るので、2つ目の条件分岐に失敗します。
(format t "~@[Opt1-ON~]~@[ Opt2-ON~]" t nil)
; Opt1-ON Opt2-ON
; => NIL

反復: ~{, ~}

反復は最も便利なフォーマットの一つで、このサイトの他のページでも頻繁に利用してきました。反復を行う~{フォーマットも、オプションによって動作が異なります。

フォーマット 意味
~{LOOP~} 引数(リスト)の要素に対して反復的に表示します
~:{LOOP~} 引数(リストのリスト)の要素に対して反復的に表示します
~@{LOOP~} 以降の引数を可変長引数と同様にリストにして反復的に表示します
~:@{LOOP~} 以降の引数(リスト)をリストのリストにして反復的に表示します
;; シンプルに使う場合はリストの要素に対して反復する
(format t "~{~a, ~}" '(1 2 3))
; 1, 2, 3, 
; => NIL

;; ~:{ はリストのリストを分解して処理できる
(format t "~:{~a, ~a, ~a~%~}" '((1 2 3) (4 5 6) (7 8 9)))
; 1, 2, 3
; 4, 5, 6
; 7, 8, 9
; => NIL

;; ~@{ は引数を可変長引数のようにリストと見なして処理できる
(format t "~@{~a, ~}" 1 2 3)
; 1, 2, 3, 
; => NIL

;; ~:@{ はリストを可変長引数のように扱いながら分解して処理できる
(format t "~:@{~a, ~a, ~a~%~}" '(1 2 3) '(4 5 6) '(7 8 9))
; 1, 2, 3
; 4, 5, 6
; 7, 8, 9
; => NIL

なお、繰り返しでは中断を行う~^を合わせて使うと便利です。中断は、最後の要素を処理したところで処理を止めることができるので、上の例の最後のカンマの出力を止めることができます。
(format t "~{~a~^, ~}" '(1 2 3))
; 1, 2, 3
; => NIL

再帰: ~?

フォーマット文字列自体を引数として消費するフォーマットも存在します。~?です。

このフォーマットは、見た目はシンプルですが動作は複雑です。~?は2つの引数を消費しますが、その形式は決まっており、一つ目がフォーマット文字列、二つ目がリストです。リストを引数に見立ててフォーマット文字列でフォーマットした文字列が、~?の部分に使われます。
(format t "~?" "(~a, ~a)" '(1 2))
; (1, 2)
; => NIL

(format t "~?" "(~a, ~a, ~a)" '(1 2 3))
; (1, 2, 3)
; => NIL

フォーマット文字列自体をデータとしてやりとりしたい場合は便利です。

中断: ~^

反復フォーマットの中でも紹介した通り、~^は最後の要素を消費した段階で反復を中断できます。
(format t "~{~a~^, ~}" '(1 2 3))
; 1, 2, 3
; => NIL

その他のフォーマット

あまり使うことはないかもしれませんが、こんなのもあるよ、ということであと少しだけ紹介します。

大文字・小文字: ~(, ~)

大文字と小文字を変換するには~(~)を使用します。このフォーマットは、小文字と大文字の相互変換だけでなく、文頭の大文字化やワードの大文字化にも対応しています。

フォーマット 意味
~(SENTENCE~) 文字列を全て小文字に変換します
~:@(sentence~) 文字列を全て大文字に変換します
~:(sentence~) 文字列に含まれるワードの最初の文字を全て大文字にします
~@(sentence~) 文字列の最初の文字を大文字にします
;; 何もオプションを付けないと小文字に変換する
(format t "~(~a~)" "THIS IS A PEN.")
; this is a pen.
; => NIL

;; ~:@( を付けると大文字に変換する
(format t "~:@(~a~)" "this is apen.")
; THIS IS APEN.
; => NIL

;; ~:( を付けると文中のワードの先頭の文字だけを大文字にする
;; 英文のタイトルはこの形式で表現される
(format t "~:(~a~)" "THIS IS A PEN.")
; This Is A Pen.
; => NIL

;; ~@( を付けると文頭の文字だけを大文字にする
;; 英文の本文はこの形式で表現される
(format t "~@(~a~)" "THIS IS A PEN.")
; This is a pen.
; => NIL

単数形・複数形: ~p

私は日本語圏で生活しているのであまりありがたみはないですが、英語圏でプログラミングしているなら~pフォーマットは地味に便利かもしれません。
~pフォーマットは単数形と複数形の表記を使い分けることができます。

フォーマット 1に等しい場合 1に等しくない場合
~p "" "s"
~@p "y" "ies"
;; 共に単数形
(format t "bo~@p and girl~p" 1 1)
; boy and girl
; => NIL

;; ~p のみ複数形 ("s"を出力)
(format t "bo~@p and girl~p" 1 2)
; boy and girls
; => NIL

;; ~@p も複数形 ("ies"を出力)
(format t "bo~@p and girl~p" 2 2)
; boies and girls
; => NIL

0 件のコメント :

コメントを投稿