読者です 読者をやめる 読者になる 読者になる
ようこそ。睡眠不足なプログラマのチラ裏です。

printf系の "%A" 書式指定子における型の表示レイアウトのカスタマイズ

プログラミング F#

判別共用体を文字列として出力する際に、ケース識別子を宣言する型(判別共用体)の名前を含めたフルネームで文字列化したくなったときのお話。

たとえば、以下を実行すると

次の出力結果を得られる。


それを、以下のような感じに出力するようにしたい。というのが今回のお題。


StructuredFormatDisplay属性を使う

Core.StructuredFormatDisplayAttribute クラス (F#) - MSDNライブラリ

この属性は、%A printf 書式設定やその他の 2 次元のテキストベースの表示レイアウトを使用する場合に、型を表示する既定の方法を指定するために使用されます。 このバージョンの F# で有効な値は、PreText {PropertyName} PostText 形式の値のみです。 プロパティ名は、オブジェクトそのものの代わりに評価および表示するプロパティを表します。

とある。これを使えば、型を表示する際のレイアウトを自由にカスタマイズすることができる。なお、"このバージョンの F# で有効な値は"とあるが、F#2.0からF#3.1までは変更はない(F#2.0より前は仕様が異なる)。


StructuredFormatDisplayAttribute。自作ライブラリをせっせとこさえていたり、某有名F# ライブラリのソースコード等を読んでたり、コンパイラのソースを見ていたりする人なら見覚えがあるかもしれないが、"%A"書式指定子の表示レイアウトをカスタマイズしたい場面はそんなに多くはないだろうし、そこそこマニアックな(割とどーでもいい)話題かもしれない。こちら、「実践F#」に載っていないというか、「Expert F#3.0」にも載ってなかったと思うし、いまのところ最新の言語仕様書であるところの「The F# 3.0 Language Specification」にも記載されていないようなので、言語仕様書熟読勢も把握できていない可能性がある。でも、「プログラミングF#」にはサラりと載っていたりする(!)。


単純な例

StructuredFormatDisplay属性を使った単純な例は以下のようになる(ここでは例として判別共用体を対象としているが、その限りではない)。

出力結果は以下のようになる。

使い方めちゃ簡単。

FizzBuzzしてみる

意味もなくFizzBuzzしてみます。

出力結果


この結果から、StructuredFormatDisplay属性を使って型の表示方法をカスタマイズしても、"%s"および"%O"書式指定子に影響を及ぼしていないことが確認できる。"%s"および"%O"書式指定子を指定した場合、いずれも結果的に対象オブジェクトについて Object.ToString仮想メソッドが呼び出されるかたちになるので、判別共用体の場合は既定では上記のように型名が出力される。 override this.ToString () = sprintf "%A" this.Displayというように、ToStringメソッドをオーバーライドする実装を追加すれば、いずれも "%A"書式指定子を指定した場合と同じ結果が得られるようになる。F#2.0より前のバージョンでは ToStringを経由して表示する際にStructuredFormatDisplay属性を参照していたようだが、F#2.0以降はToStringメソッドを経由する場合にはこれを参照しないよう仕様が変更された。

StructuredFormatDisplay属性で指定した{PropertyName}を実装していない場合

ちょっと例を変えて、レコード型にしてみる。

出力結果

とまあ、StructuredFormatDisplay属性で指定した{PropertyName}を実装していない場合は、 コンパイルエラーとなるわけでなく例外となるわけでなく、割と残念な感じの出力結果を得ることになる。コンパイルエラーにしてくれてもいいのにー。

判別共用体(discriminated unions)について、型名も含めて文字列化する

さて、本題。

まずは愚直に書いてみよう

StructuredFormatDisplay属性でマークし、表示をカスタマイズする実装を愚直に書き加える。

以下の出力結果が得られる。

おいおい。PreText使おうぜ

あっ。ケース識別子を宣言する型(判別共用体)の名前は固定なので、この場合StructuredFormatDisplay属性のPreTextに集約できるんだったね。

出力結果変わらず。

このTree<'T>判別共用体の場合だけに関して言えば、とりあえずこれで良さそうに見えるし、この方法を取れば他の判別共用体についても都度対応できそうだ。 でも、毎回個別に対応するなんてダルすぎる。汎用的にしたいよねー。

リフレクションで汎用的に実装しよう

Microsoft.FSharp.Reflectionを利用する。

出力結果

ヽ(*´∀`)ノ ワーイ、できたよー

と、思ったけど、待って。違うやん。本当は以下のようなレイアウトで表示したかったんだった(だった!)。


んー、内容的には同じなのでそんなに大きな問題ではないんだけど、若干モヤッとする。 "%A" 書式指定子の表示レイアウトをいい感じに制御するにはどうすればよいのだろう? また、既存の型(例えばOption<'T>型など)の、表示をカスタマイズしたい場合はどうすればよいのだろう?

F#er諸兄、何かご存じであればアドバイス頂きたい。