Common Lispには「プリンター」という概念があり、Lispのデータをテキストとして表現するためのすべての機能が含まれています。そのひとつがformatで、これを使うとプログラマーはデータを思いどおりに整形できます。
formatの機能、そしてLispのプリンター全般はとても大きなトピックです。ここでは、formatの基本的な使い方を説明します。
format関数format関数は、少なくとも2つの引数を取ります。出力先のストリームと、何をどのように出力するかを定義する制御文字列です。
ストリームはそれ自体がひとつの概念で、別の場所で説明します。ここでは、出力はストリームに対して行われ、この引数にはtとnilという2つの特別な値があることだけ知っておけば十分です。tは標準出力(正確には、変数*standard-output*が束縛されているストリーム)に出力することを意味し、nilは文字列を作ってそれを返すことを意味します。
formatは、ストリームとしてnilを指定した後者の場合を除いて、常にnilに評価されることに注意してください。これは、文字列を手軽に補間したり組み立てたりするときによく使われる方法です。
(format nil "hello world") ; => "hello world"
(format t "hello world") ; => NIL (but "hello world" is printed to standard output)
制御文字列には「フォーマット指示子」を含めることができます。これは、何を出力するか、formatへの他の引数をどう解釈するかをformatに指示するものです。ある意味では、制御文字列はformatが解釈して実行するプログラムのようなものです。指示子はすべて~で始まり、そのあとに文字が続きます(例:~A、~D、~&)。文字の大文字・小文字は区別しません。
ほとんどのフォーマット指示子は、formatへの引数を出力に埋め込むだけです。これは引数を「消費する」と言います。引数を消費しない指示子もあれば、2つ以上消費する指示子もあります。引数リストの中をあちこちに飛んだり、引数を飛ばしたりできる指示子さえあります。
とても汎用的なフォーマット指示子が~Aです。これはデータを「人間が読める」形で出力します。
(format nil "Value = ~a" 10) ; => "10"
(format nil "hello ~a" "world") ; => "hello world"
(format nil "The list is: ~A" (list 1 2 3)) ; => "The list is: (1 2 3)"
もうひとつ便利なフォーマット指示子が~Sです。これはデータを「機械が読める」形で出力します。出力をLispのデータとして読み直したいときに、とても役に立ちます。多くの場合は~Aと同じに見えますが、たとえば文字列では、出力に引用符が含まれる(別の文字列の中に入れるときはエスケープされます)ので、その値を文字列の引数としてそのまま読み戻すことができます。
(format nil "~s" 10) ; => "10"
(format nil "~s" "hello world") ; => "\"hello world\""
(format nil "~s" (list 1 2 3)) ; => "(1 2 3)"
さらに便利な指示子が~%と~&です。前者は改行文字を出力し、後者は「フレッシュライン」を出力します。違いは、~%が常に改行を出力するのに対し、~&は現在行頭にいない場合にのみ改行を出力する点です。どちらも引数は消費しません。
(format nil "~%new~%lines") ; => "
new
lines"
(format nil "~&new~&lines" ; => "new
lines"
最後に~~は、文字としての~を出力するだけのシンプルな指示子です。
仕事を持っている人なら誰でもそうであるように、LispエイリアンのLaylaも、ときどき他の人に情報を伝えるためのレポートを作る必要があります。そこで、そうしたレポートに使うデータの書式を整えるのを手伝ってくれないかと頼まれました。
Laylaがまず必要としているのは、format-quarter-valueという関数です。この関数は、四半期と値という2つの入力を受け取り、レポート用にフォーマットします。文字列に評価される必要があります。
(format-quarter-value "last" 3.14) ; => "The value last quarter: 3.14"
(format-quarter-value "this" 0) ; => "The value this quarter: 0"
Laylaはformat-quarter-value関数をありがとうと言い、次に必要になる関数でとても役に立つと話しています。
次のレポートでは、2つの四半期の四半期と値を2行に分けて表示する必要があります。
また、出力は最初の引数として渡されたストリームに書き出す必要があります。
例として、(format-two-quarters t "last" 3.14 "this" 0)とすると、標準出力に次のように出力されます:
The value last quarter: 3.14
The value this quarter: 0
前後に空行があることに注意してください。
Laylaは、LispエイリアンはLispデータとして読める形で値を見るのが好きだということを伝え忘れていました。そこで、Lispデータとして読み取れる新しい関数が必要です(これも指定されたストリームに出力します):
(format-two-quarters-for-reading nil "last" 3.14 "this" 0) ; =>
"(\"The value last quarter: 3.14\" \"The value this quarter: 0\")"
Exercismに登録すれば、27個のコンセプト92個の演習、そして本物の人間によるメンタリングとともに、Common Lispを学んでマスターできます。すべて無料です。