可変長引数の書式付バイナリ出力関数 [OCaml]
OCaml の printf 関係で検索していたら Caml-list の過去ログ [1] で紹介されていた論文 [2] に ML で printf っぽいものを作る一つのやり方が書いてあった。(ちなみにそのスレッドのフォローアップに OCaml には special
typechecking rules for format strings があるようなことが書いてあるのでやはり printf は特別ということらしい)
さて、[2] を参考に見よう見まねで当初の目的であった書式化バイナリ出力関数を作ってみた。fout というのが作りたかった関数だ。
# let ($) f g x = f (g x);; val ( $ ) : ('a -> 'b) -> ('c -> 'a) -> 'c -> 'b = <fun> # let a k s x = k (output_string s x; s);; val a : (out_channel -> 'a) -> out_channel -> string -> 'a = <fun> # let c k s x = k (output_char s x; s);; val c : (out_channel -> 'a) -> out_channel -> char -> 'a = <fun> # let b k s x = k (output_byte s x; s);; val b : (out_channel -> 'a) -> out_channel -> int -> 'a = <fun> # let i k s x = k (output_binary_int s x; s);; val i : (out_channel -> 'a) -> out_channel -> int -> 'a = <fun> # let fout out p = p (fun s -> s) out;; val fout : 'a -> (('b -> 'b) -> 'a -> 'c) -> 'c = <fun> # let out = open_out_bin "test.dat";; val out : out_channel = <abstr> # fout out (c$b$c$b$c$i$a) 'H' 0x65 'l' 0x6c 'o' 542601074 "ld";; - : out_channel = <abstr> # ether@KURO-BOX:~$ od -h -c test.dat 0000000 4865 6c6c 6f20 576f 726c 6400 H e l l o W o r l d \0 0000013 ether@KURO-BOX:~$
Obj.magic を使うのとは違って、この仕組みは型チェックがちゃんとなされる安全なものである。
# fout out (c$b$c$b$c$i$a) ;; - : char -> int -> char -> int -> char -> int -> string -> out_channel = <fun> # fout out (c$b$c$b$c$i$a) 'H' 0x65 'l' 0x6c 'o' 542601074 'l';; This expression has type char but is here used with type string
これはかなりいいと思う。少なくとも output_xxx を何行も書くよりはずっといい。
でもちょっと難点もある。それは書式指定を表現するのが文字(列)ではなく関数であるということで、当該名前空間の貴重な1文字識別子を消費してしまう。これは何か嫌だ。あと OCaml 固有の問題として大文字で始まる識別子を関数名に使えないので1文字で表現できる能力が限定される。だからといってあまり書式指定を長い識別子にしたり、モジュールにして例えば Fout.c$Fout.b$... なんて書きたくはない。
[1] http://caml.inria.fr/pub/ml-archives/caml-list/1999/03/f29ccf690732fd68d7c69079a7dc5a92.en.html
[2] http://www.brics.dk/RS/98/12/index.html
コメント 0