(可変長の)書式付バイナリ入力関数 [OCaml]
前回 [1] はバイナリ出力関数を作ったけど入力のほうを作っていなかった。
入力のほうは読み込んだ値をどのように取り出すかで何通りかの実装が考えられると思う。思いついた順に3種類のコードを載せる。
まずは参照を使う方法。これは出力版をひっくり返しただけでシンプルだ。
let ($) f g x = f (g x)
let a len k s x =
let buf = String.make len ' ' in
really_input s buf 0 len;
x := buf;
k s
let c k s x = k (x := input_char s; s)
let b k s x = k (x := input_byte s; s)
let i k s x = k (x := input_binary_int s; s)
let fin ich p = p (fun s -> s) ich
let _ =
let x = ref 0 in
let y = ref ' ' in
let z = ref "" in
let ich = open_in_bin "test.dat" in
ignore (fin ich (i$c$a(5)) x y z);
print_int !x;
print_char !y;
print_string !z
しかし使う段になって変数名に ! をつけなければいけないのが煩わしかったり、そもそも副作用はできるだけ持ち込みたくないかもしれない。変数の宣言もちょっとまどろっこしい。
次に関数がリストを返せばいいのではないかと考えた。でも異なる型の要素をひとつのリストに入れることはできないのでヴァリアント型を導入する。
let ($) f g x = f (g x)
type vt = A of string | C of char | B of int | I of int
let a len k s =
let (l,s) = s in
let buf = String.make len ' ' in
really_input s buf 0 len;
k ((A buf)::l,s)
let c k s = let (l,s) = s in k ((C(input_char s))::l, s)
let b k s = let (l,s) = s in k ((B(input_byte s))::l, s)
let i k s = let (l,s) = s in k ((I(input_binary_int s))::l, s)
let fin ich p = p (fun s -> s) ([], ich)
かなり強引ではあるけれどもパターンマッチで値を取り出すことはできる(マッチが exhaustive でないという警告が出る)。
let _ =
let ich = open_in_bin "test.dat" in
let (A z::C y::I x::[], _) = fin ich (i$c$a(5)) in
print_int x;
print_char y;
print_string z
しかしこの方法はやはり本当の値が構造の奥深くに入り込んでしまうことと、リストの構築が読み込み順になるので、出来上がったリストがファイルと逆の順序になってしまうという点がいまいちだ。
最後にカリー化関数を与えて読み込んだ順に部分適用していって、最終的に与えた関数の戻り値が返ってくるという方法。
let ($) f g x = f (g x)
let a len k s =
let (f,s) = s in
let buf = String.make len ' ' in
really_input s buf 0 len;
let f' = f buf in
k (f',s)
let c k s =
let (f,s) = s in
let f' = f (input_char s) in
k (f',s)
let b k s =
let (f,s) = s in
let f' = f (input_byte s) in
k (f',s)
let i k s =
let (f,s) = s in
let f' = f (input_binary_int s) in
k (f',s)
let fin ich p f = p (fun s -> s) (f, ich)
let _ =
let ich = open_in_bin "test.dat" in
let maketuple x y z = (x,y,z) in
let ((x,y,z), _) = fin ich (i$c$a(5)) maketuple in
print_int x;
print_char y;
print_string z
これはかっこいいし、与える関数の中で前出の2つの方法をカバーできるので柔軟である。これが一番いいな。…というか、ちゃんと確認していなかったけど OCaml の scanf も同じようなインターフェイスだった。
さて、この(もともと私ではなくて Olivier Danvy さん [2] が考えた)仕組みはよくできているけど、やはり OCaml に適用する場合の最大のネックは書式表現の識別子問題だと思う。
上記のコードでは出力関数を作ったときと同じ a, b, c, i を使ったけど、現実のコード中では両方同時に使うのが普通だから名前を分けなければいけない(これは書式表現が関数ではなくて文字列の場合は考えなくてよい制約だ)。
バリエーションはそれだけではなくて、ビッグエンディアン用とリトルエンディアン用の区別もしなければならない。それに本当は int だけじゃなくて Int32 とか Int64 とか、符号の有無も追加したい。
それらの区別をすべて小文字で始まる識別子で表現しなければならない(これが OCaml 特有の制限だ)となると、書式表現としてはもう結構な長さになる。それでまったく使えないというわけではないけど…
ところでこのやり方の printf の OCaml 実装をまとめた Cpsio [3] というライブラリも検索で見つけたのでリンク。
[1] http://blog.so-net.ne.jp/rainyday/2006-08-15
[2] http://www.brics.dk/RS/98/12/index.html
[3] http://tkb.mpl.com/~tkb/software.html
コメント 0