どうしてOCamlのオブジェクトにはcoersionが必要だったのか

let x = object
  method f x = x+1
  method g x = x-1
end;;

let y = object
  method f x = x*1
  method h x = x/2
end;;

List.map (fun x -> x#f 3) [x; y];;

上のようなプログラムは、絶対にエラーが発生しないにもかかわらず、肩推論で弾かれる。これを通るようにするには、明示的なcoersionを入れてやる必要がある。

List.map (fun x -> x#f 3) [(x :> < f : int -> int >); (y :> < f : int -> int >)];;

これって、めんどくさいような気がしていたのだ。[x; y]のようなリストに対して、< f : int -> int >のように共通部分のみを推論してやれれば、便利だと思う。この話は、一度JG先生に尋ねたことがあって、そのときは「型が読めなくなるから」と言う設計方針を聞いたことがあった。

その理由には、微妙になっとくできなかったんだけど、それがやっとわかった気がする、という話。

let z = object
  method f x = x+1
  method g x = x ^ ":";;

のようなオブジェクトがあったとき、明示的なcoersionを入れて< f : int -> int >へと変換する場合はなにも問題が生じない。しかし、このcoersionに相当する操作を自動でやろうとすると、[x; z]とかしたときに、メソッドgの型が一致しないことから、全体が型エラーになってしまうことになる。さらに、もっと悪いことに、x :: [y; z]ではエラーにならない(多分)。こっちだと、まず[y; z]が片付けされて、< f : int -> int > listになる。これにxを::しても、エラーにはならない。(一応soundにはなるけど。意味とかtyping ruleとか定義してないので直感的な意味で、ですが。)

ほとんど同じ意味のプログラムなのに、これでは混乱しそうだ。


ちなみにpolymorphic variantの場合は、こういう問題は生じない。objectのcoersionに相当する操作では、メソッドが減るobjectと逆にラベルが増える。

let f() = `A;;
let g() = `B;;
let list = [f(); g()];; (* ここで自動的にcoersionが発生する *)

こうすると型エラーになる。

let f b = if b then `A 1 else `B true;;
let g b = if b then `B "a" else `C (1.3);;
let list = [f(true); g(false)];;  (* これはエラー *)

この場合は、プログラムの意味的にもエラーとなるのが正しい。