Hatena::Groupcadr

kozima の日記

2009-02-01

format の痒いのに手が届かないところ

| 22:42

format で directive に渡す前置引数のところに V を書くと format に渡された引数を使うことができます。

(format t "|~VT~A~%|~VT~A~%|~VT~A~%" 3 'foo 5 'bar 7 'baz)
;-> |  FOO
;   |    BAR
;   |      BAZ

3, 5, 7 が ~VT の V に食われていますね。今回はこの V についてのお話です。

やりたいこと

ときどき繰り返し ~{~} の中にある V を最初に埋めたいことがあります。

例えば HTML のリストを出力することを考えてみます。

(format t "<ul>~%~{~2T<li>~A</li>~%~}</ul>" '(aaa bbb ccc))
;-> <ul>
;     <li>AAA</li>
;     <li>BBB</li>
;     <li>CCC</li>
;   </ul>


(format t "<ul>~%~{~4T<li>~A</li>~%~}</ul>" '(aaa bbb ccc))
;-> <ul>
;       <li>AAA</li>
;       <li>BBB</li>
;       <li>CCC</li>
;   </ul>

この例だと ~T に数字を直接前置していますが、似たような状況でインデント幅を動的に変えたいこともあります。

こういうのをどうやればいいかこれまで何度か考えているんですが、あまりきれいな方法が見つかりません。

一案

一つのやり方として、control string を format で生成して渡すということが考えられます。

(format t "<ul>~%~{~}</ul>"
        (format nil "~~~DT<li>~~A</li>~~%" 2)
        '(aaa bbb ccc))
;-> <ul>
;     <li>AAA</li>
;     <li>BBB</li>
;     <li>CCC</li>
;   </ul>

しかしエスケープのせいで ~ だらけになってがあってあまりきれいではないし、可読性も低くなります。書いていて分かりにくくてミスもしやすく、こんなコードを書くのはできれば避けたいところです。

代替案

今回、もう少し真っ当なやり方として ~/name/ directive を使う方法があることに気がつきました。

(defun format-items (stream arg colon-p at-sign-p &optional (n 0))
  (dolist (item arg)
    (format stream "~VT<li>~A</li>~%" n item)))

(format t "<ul>~%~V/format-items/</ul>" 2 '(aaa bbb ccc))
;-> <ul>
;     <li>AAA</li>
;     <li>BBB</li>
;     <li>CCC</li>
;   </ul>

(format t "<ul>~%~V/format-items/</ul>" 4 '(aaa bbb ccc))
;-> <ul>
;       <li>AAA</li>
;       <li>BBB</li>
;       <li>CCC</li>
;   </ul>

インデント幅を前置で渡し、繰り返しは関数の中で dolist により実現しています。こうすることで control string を複雑にすることなく要求を満たすことができました。

結局どうなの

ただ、この方法だと ~/name/ directive の仕様上、グローバルに関数を定義する必要があります。そのためコード量の観点からも、また名前空間を汚すという意味でも、あまりお手軽には使えない感があり、不満の残るところです。

そもそも control string であるのが間違いで、文字列じゃなくて S 式にしてしまえばいいんだという意見も見かけます。もしそうなった日には、こういうことも簡単にできるようになるのでしょうか。どうなのでしょう。