#006 禁断の Obj
先日,フランスの XML 関係の研究者と共著する機会があり, 「こっちの方が早いだろ」「いやココを変えればこっちの方が」 「いやいやまだまだ改善できる」だの OCaml のコードで文通をしたりしていたが, そんな中,相手のこの一行に愕然としてしまった.
external set_field : 'a -> int -> 'b -> unit = "%obj_set_field"確かに,効率的な実装を実現するうえで
Obj
モジュールを使うことはたまにあるが,
ここまでやってしまうとは.
というわけで,今日は Obj
モジュールについて少しだけ触れる.
ただし,この記事は Obj
モジュールの利用を推奨するものではない.むしろ禁止したい.
また,その性質からバージョンによって動作しない可能性も高い.
ちなみに,以下のプログラムは 3.09.2 での動作を確認している.
「Not for the casual user.」とマニュアルにもある通り,Obj
モジュールを使うことは推奨されていない.
Obj
モジュールは OCaml が扱う全てのデータ構造をいじる関数を提供しており,
このモジュールを使うと関数型っぽくない,いわゆる『汚い』プログラムがいくらでも書けてしまう.
型を任意に変換できる関数 Obj.magic : 'a -> 'b
を代表としてよく引き合いに出されるが,
ここでは他の関数についても採りあげることにする.
Obj
モジュールの扱う Obj.t
型は,
OCaml が扱う全ての値(関数も含む)を表現できる構造体のようなものを表す型である.
OCaml における任意の値は Obj.repr
によって Obj.t
型に変換される.
型は変換されているが,恒等関数なので実際には何もしていない.
逆に,Obj.t
型を元の型に戻すのが Obj.obj
である.
ただし,文脈によっては型を明示する必要があるかも知れない.
つまり,Obj.magic
は fun x -> Obj.obj (Obj.repr x)
と等価である.
(結局どれも恒等関数なのだが…)
OCaml で扱われる値は実装上はタグといくつかのフィールドから成り,
それぞれ Obj.tag : Obj.t -> int
と Obj.field : Obj.t -> int -> Obj.t
で取得できる.
Obj.field
の二つ目の引数は 0 から始まるフィールド番号である.
例えば,リストは先頭と残りのリストの二つのフィールドを持っているので,
(Obj.obj (Obj.field (Obj.repr [1;2;3]) 1) : int list)は,
[2;3]
を返す.
また,Obj.set_field
によって,あるフィールドの値を変更することも可能である.
OCaml では,公にはレコードのみ mutable なフィールドを許しているが,
Obj.set_field
を使うとレコード以外でもフィールドの更新が可能になる.
このため,mutable なヴァリアントを作りたい場合でも引数をわざわざレコードにする必要がなくなり,少しだけ効率が良くなることもある.
ちなみに,冒頭の関数 set_field
は,fun x i y -> Obj.set_field (Obj.repr x) i (Obj.repr y)
と等価である.
無駄な関数呼び出しを減らすために external
宣言したと思われるが,
余程の理由がない限りあまり推奨しない.(コード自体はすっきりするかもしれないが…)
ここで,Obj
を使った簡単なプログラム例を一つ示す.
OCaml ではリング状のデータを let rec ring = 1::2::3::ring
などとリスト型のデータとして作ることが可能であるが,
let rec
の文法の制約上,
与えられた任意のリストをリング状に変更するような関数を記述することはできない.そこで,Obj
を使うと以下のように書ける.
let list2ring = function [] -> failwith "list2ring" | _::rest as top -> let rec loop prev = function [] -> Obj.set_field (Obj.repr prev) 1 (Obj.repr top) | _::xs as cur -> loop cur xs in loop top rest; topただ,この例に関しては
Obj
に頼らず Camlp4 を使う方が安全である.
また,Obj
を使った簡単な実験から OCaml の実装を垣間見ることができる.
例えば,現行の OCaml の内部では文字列とフォーマットを区別していないので,
let str = "%%%%%%" in str.[1] <- 'd'; str.[5] <- 's'; Printf.printf (Obj.magic str) 80 "zenkai!"は,「
80%zenkai!
」と出力する.
最後に,しつこいかも知れないが Obj
を使うことは絶対に推奨しない.
どうしても使う場合は自己責任で.