これってどう使うの?(2)


これってどう使うの?(2)

この記事は関西Lispユーザ会アドベントカレンダー4日目です。

「これってどう使うの?」の二回目は、format~^指定子(Escape Upward) の前置パラメータです。重箱の隅をつつくような話で恐縮です。

formatはC言語のprintfみたいなものです。%の代わりに~を使います (どうでもいいことですが、formatの中でついつい%\nを書いてしまった ことってありませんか)。

CL-USER> (setq x 5)
5
CL-USER> (format nil "The answer is ~d." x)
"The answer is 5."

formatprintfと比べてはるかに高機能です。全機能を解説したら、一冊の本が出来上る くらいなのではと思えます。その中でお気に入りは~{~}指定子(繰返し)です。リストを引数に 取り、リストの要素に対して繰返し出力をします。

CL-USER> (setq x '(1 2 3 4 5))
(1 2 3 4 5)
CL-USER> (format nil "~{~d,~}" x)
"1,2,3,4,5,"

最後のコンマが邪魔です。こういう場合のために(本当にそうか?)、~^指定子 (Escape Upward) が用意されています。(この例程度なら、perl等ではjoinが使えるぞ〜、という声が聞こえてきそうですが)

CL-USER> (format nil "~{~d~^,~}" x)
"1,2,3,4,5"

~^(Escape Upward)は処理する引数がなくなると、その時点で処理を終了します。 ~{~}の中だけで使うという制約がある訳ではありません。わざとらしい例を書くと (Common Lisp HeyperSpecの例 を少し手直し)、

CL-USER> (format nil "Done.~^ warning[~d].~^ error[~d].")
"Done."
CL-USER> (format nil "Done.~^ warning[~d].~^ error[~d]." 3)
"Done. warning[3]."
CL-USER> (format nil "Done.~^ warning[~d].~^ error[~d]." 3 5)
"Done. warning[3]. error[5]."

と、こんな具合に使えます。

formatの指定子では、指定子そのものの動きをコントロールするために前置パラメータ というものが使えます。数字の出力では出力カラムを指定できます(もちろん他にもいろいろあります)。

CL-USER> (setq x 5)
5
CL-USER> (format nil "~3d" x)
"  5"

特殊な前置パラメータの指定方法として、~v~#があります。~vformat の引数を消費して前置パラメータの値とします。~#formatの引数の残り数を 前置パラメータの値とします。

CL-USER> (format nil "~vd" 10 x)  ; 出力カラム数として10を指定
"         5"
CL-USER> (format nil "~#d" x 10)  ; 引数(xと10)の数である2が出力カラム数
" 5"

やっと本題です。~^指定子も前置パラメータが使えます。書式としては [x [,y [,z]]]となっており、最大三つのパラメータが使えるということなります。 パラメータが一つの時は、パラメータ値が0の場合に処理を終了するという動きになります。 では、パラメータ値が0になるのはどんな時でしょうか?それは~v~#を使った時です。 実際、~^~~#^と同値です。~{~}の中では、~#は引数の残りではなく、処理している 引数(リスト)の未処理の要素の数となります。前置パラメータを使った例を~{~}指定子で 無理やり作ると、

CL-USER> (setq x '(3 2 1 0 -1 -2))
(3 2 1 0 -1 -2)
CL-USER> (format nil "~{~d~v^,~}" x)
"3,1"

というところでしょうか。パラメータが二つ場合は両者が等しい場合に処理を終了します。 次の例ではリストの最後の二つを出力しません。

CL-USER> (setq x '(3 2 1 0 -1 -2))
(3 2 1 0 -1 -2)
CL-USER> (format nil "~{~d~2,#^,~}" x)
"3,2,1,0"

さて、パラメータが三つの場合(~x,y,z^と指定したとする)ですが、 x <= y <= zとなる場合に処理を終了します。このパラメータ三つが、 「これってどう使うの?」というものです。~^の前置パラメータは ~v~#と組み合わせることになりますが、~#は基本的に減少していくので、 上下限の設定は意味がなさそうに思えます。 (もしかすると、~{~}の中で~*を使うと~#は増加することもあるのかな。でもどう使う?) formatの外で計算した結果を~vformatに渡すということはできるでしょうが、 どこかイケテない気がします。 パラメータ三つはどういうことを想定しているのでしょうね? ~{~}では使い道はなそさう。~{~}以外では案外自然な使い方があったりするのでしょうか?

余談: 上の例を動かしている時に間違えて次のようにしてしまいました。

CL-USER> (setq x '(3 2 1 0 -1 -2))
(3 2 1 0 -1 -2)
CL-USER> (format nil "~{~2,#^,~}" x)

結果は無限ループ。printfみたいなもので無限ループしてしまうとは 「common lisp恐るべし」ですね。

明日はmyao_s_mokingさんの 「Common Lispのwrite関数のオプショナル引数について」 です。