2013年8月25日日曜日

F#の%a書式指定はとても扱いづらい

F#のPrintfモジュールはある程度の書式指定ができるようになっていますが、基になっているOCamlが貧弱なせいもあってString.Format()ほどの表現力はありません。 何か手はないかと考えたところ %a 書式指定を思い出しました。Core.Printf モジュールには

2 つの引数を必要とする一般的な書式指定子。 1 つ目の引数は、2 つの引数を受け取る関数です。この関数の 1 つ目の引数は、指定した書式設定関数 (TextWriter など) に対応する型のコンテキスト パラメーターです。2 つ目の引数は出力する値であり、この値によって、該当するテキストを出力するか、返すかを指定します。
2 つ目の引数は、出力する特定の値です。

とあります。いまいちわかりづらいので英語版ドキュメントも確認しましたが同じ内容で意味がよくわかりません。念のためOCamlのmodule Printfも確認します。

user-defined printer. Take two arguments and apply the first one to outchan (the current output channel) and to the second argument. The first argument must therefore have type out_channel -> 'b -> unit and the second 'b. The output produced by the function is inserted in the output of fprintf at the current point.

なるほどわかりません。

とりあえずF#で書いてみたところ、非常に面倒くさいことが判明しました。1つ目の引数の説明「TextWriter など」が罠です。 なんとprintfとsprintfとで要求される関数が違いました。具体的に関数を挙げた方がわかりやすいので、現在時刻をコンソール出力してみます。

let formatter_for_printf (textWriter : TextWriter) (dateTime : DateTime) = textWriter.Write("{0:MM/dd HH:mm}", dateTime) let formatter_for_sprintf () (dateTime : DateTime) = String.Format("{0:MM/dd HH:mm}", dateTime) printfn "%a" formatter_for_printf DateTime.Now sprintf "%a" formatter_for_sprintf DateTime.Now |> Console.WriteLine

つまり同じ %a でも

  • printfで出力する時は(TextWriter -> 'T -> unit) 'Tが必要
  • sprintfで出力する時は(unit -> 'T -> string) 'Tが必要

ということのようです。

OCaml側は確認していませんがドキュメントからはそう読み取れないため、F#の(限りなくバグに近い)仕様かなと思います。