C言語でのOCamlのヴァリアント型の扱い [OCaml]
OCamlからC言語で作った関数を呼ぶ場合はOCalの型はすべてCではvalueという型になっていて、それをCのネイティブ型に変換したりするマクロが用意されている。
その辺は入門記事やマニュアルを読めば分かるのだが、OCamlのヴァリアント型をCのコード内でどう扱えばいいのかというのが Interfacing C with Objective Caml [1] を読んでもさっぱり分からなかった。
それで、いろいろ調べてみて分かったのはどうやら次のようなことらしい。
OCamlコードから関数の引数としてヴァリアント型を渡された場合:
-引数なしコンストラクタは単なる整数であり、Long_valマクロで取り出せる。その値はヴァリアント型の中での引数なしコンストラクタの定義順に0,1,2,3...となる。
-引数ありコンストラクタはブロックであり、そのタグの値がヴァリアント型の中での引数ありコンストラクタの定義順に0,1,2,3...となる。本体の値はField(var,0))でvalue型を取り出せる(varは引数とする)。複数の引数を持つコンストラクタの場合、2つ目の引数はField(var,1))で取り出せる。
例として次のようなmliファイルがあるとしよう。
type figure = Point | Circle of int | Rectangle of int * int | Square of int
external print_figure : figure -> unit = "print_figure"
これに対応するC言語の print_figure 関数は以下のように書けばよい。
CAMLprim value print_figure(value var)
{
CAMLparam1 (var);
if (Is_long(var)) {
switch (Long_val(var)) {
case 0: /* Point */
printf("Point\n");
break;
}
} else {
switch (Tag_val(var)) {
case 0: /* Circle */
printf("Circle: %d\n", Int_val(Field(var,0)));
break;
case 1: /* Rectangle */
printf("Rectangle: %d * %d\n", Int_val(Field(var,0)), Int_val(Field(var,1)));
break;
case 2: /* Square */
printf("Square: %d\n", Int_val(Field(var,0)));
break;
}
}
CAMLreturn(Val_unit);
}
まずIs_longマクロを使って与えられた引数が単なる整数かどうかを判別する。そうであれば引数なしコンストラクタなのでそのLong_valで値を取り出している。
ここでは引数なしコンストラクタはひとつだけなのでswitchする必要はないが、複数ある場合は定義順に0,1,2,...とする。
与えられた引数が整数値でなくブロックだった場合、Tag_valマクロを使ってタグの値を参照する。引数ありコンストラクタの定義順に0=Circle,1=Rectangle,2=Squareなのでそのとおりに分岐させる。引数が1つのコンストラクタの場合はField(var,0)で、2つの場合はそれぞれField(var,0)とField(var,1)で値を取り出し、Int_valでCの型に変換する。
次のコードを実行しよう。
let _ =
print_figure Point;
print_figure (Circle 77);
print_figure (Rectangle (555, 888));
print_figure (Square 999)
;;
実行結果は以下のとおり。
Point Circle: 77 Rectangle: 555 * 888 Square: 999
だがマニュアルの Interfacing C with Objective Caml [1] のVariantsの項を読んでみても上記のようなことが書いてあるようには読み取れない。これで合ってるのだろうか(実行結果は間違いないのだが)。
[1] http://caml.inria.fr/pub/docs/manual-ocaml/manual032.html
コメント 0