モジュールを「拡張」するテクニック

すとっどりぶ

ご存知のように OCaml の stdlib は「標準ライブラリ」ではありません。「すとっどりぶ」です。大事な事などでもう二度言いますってやつです。これは OCaml コンパイラコンパイルできる様にするための最小限のライブラリ+ INRIA の人たちの気分で加えた関数から成っています。当然、OCaml をバリバァリ使う人には全く力不足です。「すとっどりぶ」で満足できない人は Jane Street Core や OCaml Batteries Included 等を使うと良いです。

でも、なんだか自分の書いているソフトに外部のライブラリを使って変な依存性を入れるのはどうかなあ、とか、わざわざ巨大な「代替標準ライブラリ」を入れるの面倒だ、という人もいるでしょう。そういう時は自分で「すとっどりぶ」を拡張すればよい。その時に便利な方法をお教えします。

すとっどりぶを拡張する

たとえば、すとっどりぶの List には List.iteri という関数が…当然ありません。欲しい。だから書く。

let list_iteri f list =
  let rec iteri f n = function
    | [] -> ()
    | x::xs -> f n x; iteri f (n+1) xs
  in
  iteri f 0 list

これで list_iteri が書けました。終わり。

じゃあないや。折角だからこれを List モジュールの中に入れて、 list_iteri とかいうダサい名前じゃなくて、 List.iteri でアクセスしたいですよね。でも、「すとっどりぶ」の List モジュールを変更するのもなんだし、どうしましょう、ということになります。

こういう時は、自分で List モジュールを作って、既存(すとっどりぶ)の List モジュールを拡張した物を作ってしまいましょう。mylist.ml というファイルを作ります:

(* mylist.ml *)
include List

let iteri f list =
  let rec iteri f n = function
    | [] -> ()
    | x::xs -> f n x; iteri f (n+1) xs
  in
  iteri f 0 list

さあ、これで、Mylist というモジュールは、すとっどりぶの List の関数と、あなたの作った iteri と両方入ったモジュールになりました。iteri を使いたければ Mylist.iteri と書けば良いですね!すとっどりぶに入っていた length 等の関数も Mylist.length と書くことができます。

でも、Mylist.ほげほげ ってダサいよね?できたら、List.ほげほげ でアクセスしたいよね?

mylist.ml というファイルでは Mylist というモジュールを作ってしまいますから、list.ml だったらよいか、というと、そうもいきません。すとっどりぶの List と自分の List の名前が被ってしまうので、プログラムのリンクで失敗します。そこで、こんな風に書いてみましょう:

(* mystdlib.ml *)
module List = struct
  include List (* include the original *)

  let iteri f list =
    let rec iteri f n = function
      | [] -> ()
      | x::xs -> f n x; iteri f (n+1) xs
    in
    iteri f 0 list
end

ファイル名を mystdlib.ml にして、その中で、子モジュール List を定義しています。その中で、オリジナルのすとっどりぶ List を「継承」している。これで上手くいきますよ。iteri を使いたいときは、

open Mystdlib (* 俺はせっかくだから、俺の強まった「すとっどりぶ」を使うぜぇ *)

   ...
   List.iteri (* List.iteri が使えるぜぇ *)
   List.length (* List.length も使えるぜぇ *)
   ...

と書けば良いのです。Mystdlib がカッコ悪いと思うなら、何かあなたのカッコいいライブラリの名前をつけてやればよろしい。Crimson でも Echizen でも。

これで終わり。ではない。まだ続く。

拡張部分のドキュメントも書こう

iteri という超使える関数を書いたし、せっかくだから、俺はドキュメントを書くぜぇ、と普通は思いますよね? OCaml でドキュメントと言えば .mli です。書きましょう!! ソースは mystdlib.ml だから、 mystdlib.mli ですね!!

(* mystdlib.mli *)
module List : sig

  .... (* ??? *)

  val iteri : (int -> 'a -> unit) -> 'a list -> unit
    (** List version of iteri: it is an iteration but with the position of the element in the list *)
end

あれー、困りました。Mystdlib.List は、オリジナルのすとっどりぶ List の関数も含むので、それも .mli に書いてあげないといけません。それを書かないと、open Mystdlib してしまったら、List.length とかが使えなくなります。だからと言って、オリジナルのすとっどりぶにある、list.mli の内容を全部コピペするのも業腹です。どうしたらいいでしょうか。

僕はこうします。

(* xmystdlib.ml *)
module List = struct
  let iteri f list =
    let rec iteri f n = function
      | [] -> ()
      | x::xs -> f n x; iteri f (n+1) xs
    in
    iteri f 0 list
end
(* xmystdlib.mli *)

(** Extends Stdlib's List *)
module List : sig
  val iteri : (int -> 'a -> unit) -> 'a list -> unit
    (** List version of iteri: it is an iteration but with the position of the element in the list *)
end
(* mystdlib.ml *)

module X = Xmystdlib

module List = struct
  include List (* the original *)
  include X.List
end

残念ながら、オリジナルの List のシグナチャを簡単に書く方法が OCaml 3.11.2 には無い (明日、書きますけれど、3.12 には、在りそうで、これまた無い) ので、二段構えにします。モジュール Xmystdlib では、拡張部分だけを書いて、すとっどりぶの include はせず、拡張部分についてのみ関数シグナチャとドキュメントを書きます。そして、その後、Mystdlib で、オリジナルの List と拡張した List (Xmystdlib.List) を include して一つの Mystdlib.List に纏めるのです。mystdlib.mli は書きません。(書いちゃうとまたオリジナルの List のシグナチャをどうしよう、ってことになるので。) こうする事で、ちょっと間接的になりますが、既存のモジュールを拡張しつつ、その拡張部分だけのドキュメントを .mli に書くことができます。これなら、 Mystdlib.List.iteri をよく知らない人がドキュメントを調べたい時でも、まず、 mystdlib.ml を見て、そして、ああ、List はオリジナル List と Xmystdlib.List の和だな、とすぐわかりますね。後は、もし iteri が、すとっどりぶに無いと知っていれば、すぐに xmystdlib.mli を調べる事が出来ます。

もちろんこれは、すとっどりぶのモジュールを拡張するだけでなく、どんなモジュールの拡張にも使える技です。

明日は、これが 3.12 の module type of でどうなるか、話しますね。といっても、上手くいきそうで、上手くいかーん、プンプン、という話なのですけれど。