これからのOCamlについての噂 #2: 判りきったモジュール名を省く

OCaml では名前(変数、モジュールや、ヴァリアント、コンストラクタ名)はモジュールの名前空間によって管理されている。別モジュールで定義されている名前を使ってヴァリアントやレコードを作成したり、参照したりする時にはそのモジュール名を介してアクセスする必要がある:

(* long_named_module.ml *)
type point = { x : int; y : int }

type 'a result = 
  | Ok 'a
  | Error of string
(* m.ml *)
let make_point x y = 
  { Long_named_module.x = x;
    Long_named_module.y = y }

let string_of_result f = function
  | Long_named_module.Ok v -> Printf.sprintf "Ok(%s)" (f v)
  | Long_named_module.Error s -> Printf.sprintf "Error %S" s

で、当然、一々モジュール名なんか書いていられないわけ。なので、普通は open を使うよね:

open Printf
open Long_named_module

let string_of_result f = function
  | Ok v -> sprintf "Ok(%s)" (f v)
  | Error s -> sprintf "Error %S" s

let make_point x y = 
  { x = x;
    y = y }

open と唱えると、モジュール名を介さなくてもそのモジュール内の名前にアクセスできるようになるので、一々 Long_named_module と書く必要が無くなる。
Printf も open してあるけど、個人的に私は Format.fprintf をよく使うので、混乱を避けるために open Printf は Printf 関数が何十個も出てくるようなモジュールでしか使わない。まあ、ここではいいや。

じゃあ、いつでも open が良いかというと、そうでもない。A.x と B.x という名前が二つあって、open A;; open B とすると、x は後で open した方を指す(ここでは B.x)。この名前の override のためにプログラムが読みづらくなったり、場合によっては上手く書けなかったりする:

open Printf
open Long_named_module

type my_point = { x : float; y : float } 

let make_point x y = 
  { x = x;
    y = y }

こうなるともうダメだ。x と y は my_point のになってしまう。open Long_named_module を my_point の後にずらしても、今度は my_point の x, y が使えなくなる。こういう場合 open 出来ない。my_point をモジュールに別けたりとか、local open(http://d.hatena.ne.jp/camlspotter/20081222/1229940214) を使ったりとか、いろいろやりようはあるけど、ここでは触れない。

そこで、確か我師匠である Pierre Weis がやったんだと思うんだけど、open したくない、でも長いモジュール名をうだうだ書きたくない時、レコード内のラベルのモジュール名は一回書けば他は省略できるように最近の OCaml は改良されている:

let make_point x y = 
  { Long_named_module.x = x; 
    y = y (* y は Long_named_module.y *) }

そりゃ、同じレコード内に出てくるんだから、y は Long_named_module.y に決まっている。書く必要ないわな。

じゃあ、バリアントではどうでしょう?

let string_of_result f = function
  | Long_named_module.Ok v -> sprintf "Ok(%s)" (f v)
  | Error s -> sprintf "Error %S" s

これは残念ながら今の OCaml では出来ない。Error s は Long_named_module.Ok v のパターンと同じ型を持つはずだから、 Long_named_module.Error s に決まっているんだけど、コンパイラはそこまで調べてくれない。レコードの場合はラベルが固まって出現するから省略しても簡単に残りのモジュール名を推測できたが、バリアントではパターンマッチ内とはいえ、分散しているから難しい。

ただ、これを何とかしようと言う話を私が提案しといてと頼んでおいたら、それはいいねえ、という事になったらしい。というわけで、もしかしたら、将来の OCaml には入るかもしれない。

上のようなパターン中の例は簡単なんだけど、でも実は、一般的にやるのはちょっと大変だ:

let l = [ Long_named_module.Ok 1; Error "error" ];;
let _ = (fun point -> point.x + point.y) { Long_named_module.x = 1; y = 2 }

とか、できるといいのだけれど。
まあ、モジュール名が不明のバリアントに関しては、型チェックを遅らせておいて、後で他の unification からの結果で推測する、ということが出来るだろう。でも、Polymorphism との兼ね合いがあるから型チェックを遅らせるといってもいつまででも遅らせることはできなさそうだ(少なくとも generalization の直前で関係しているヤツは解決しないといけない)。難しくはないが、結構コンパイラの型チェック部分のコードをいじる必要があるだろー。