4.02 予習その2 新しい module type、 (module M)

OCaml 4.02 に追加された新しいモジュール型、 (module M) が結構いけているのです。

OCaml である既存のモジュールの型は?というと、 module type of M というのがあって、 module type of Unix とやると Unix モジュールの signature を貰えるというものでした、が、これがあまり使いたいときに使えないものでした。

OCaml で既存モジュールに何か関数を足したいことがあります。特に元から付いている標準のライブラリは足りないものが多すぎる。たとえば Unix.usleep が欲しい:

module Unix = struct
  include Unix
  let usleep x = ignore (select [] [] [] x)
end

はい、簡単にできました。ではこのオレオレ Unix の型を書きたい。これは元の Unix モジュールのシグナチャに usleep を足したものですが、どう書いたらいいだろうか:

module Unix : sig
  (* 何を書いたらいいだろう? *)
  val usleep : float -> unit
  (** [usleep n] sleeps for [n] seconds! Cool! *)
end

unix.mli の内容をコピペするのはあまりに悲しすぎますね、結論から言うと、 include (module Unix) で嬉しい!なのです。

が、 4.02 以前にはこれはなかったんだよね。 3.12.0 で module type of M が入ったとき、おっ、これは module type of Unix と書けばよいのではないか、そう思いました:

module Unix : sig
  include module type of Unix
  val sleep : float -> unit
  (** [usleep n] sleeps for [n] seconds! Cool! *)
end

いやーこれですっきり解決!と思ったら駄目だったんです。なぜかというと元の Unix にある Unix.file_descr とオレオレ Unix にある Unix.file_descr が違う型という扱いになっている。なので、たとえばオレオレ UnixUnix.open した file_descr を元 Unix.read で使う、とかその逆ができません。これは、オレオレライブラリだけで住んでいれば問題ないのですが、いろんな Unix を使うライブラリと組み合わせることができなくなり、つらい。これを解決するには、この二つの file_descr が同じであることを明示してやる必要があります:

module Unix : sig
  include module type of Unix with type file_descr = Unix.file_descr
  val sleep : float -> unit
  (** [usleep n] sleeps for [n] seconds! Cool! *)
end

ああ、なるほど、これで一件落着、かと思いきや、 Unix にあるデータ型は file_descr だけじゃない。 error もあるし process_status もある。これみんな with type t = Unix.t て書かなきゃいけないのです。

クソですね。

なので、オレオレで既存モジュールを拡張したいときはこうしなければいけなかった:

module XUnix : sig
  val sleep : float -> unit
  (** [usleep n] sleeps for [n] seconds! Cool! *)
end = struct
  let usleep x = ignore (select [] [] [] x)
end

module Unix = struct
  include Unix
  include XUnix
end

つまり、拡張部分だけのモジュール XUnix を書いてそこで拡張部分だけに関するシグナチャを書き、元の Unix と拡張 XUnix の統合はシグナチャ書かないでやると。この部分の sig や mli は提供しないが空気を読めよと、そういうわけだ。

で、なんかさっきから頭に引っかかってたんだけど、同じことを三年前に書いてるわけ:

ヤンナルネ…

まあ、これが問題だったんだけど、 (module M) というので解決、というか、なんでまずこれを入れなかったんだ…まあこうやって標準ライブラリをガンガン拡張する立場にあるインダストリィですか、エンタープライズですか、そういう経験のある人がまだ OCaml の内部にやいやい口を出す時代ではなかったのですね…

で、 (module M) というのはモジュール M のシグナチャ。型は元と全部同じ!というナイスなモジュール型なので、

module Unix : sig
  include (module Unix)
  val sleep : float -> unit
  (** [usleep n] sleeps for [n] seconds! Cool! *)
end = struct
  include Unix
  let usleep x = ignore (select [] [] [] x)
end

と書けるようになりました。よかった!