What is it, naokirin?

OCamlのファンクター

ちょっとかじってみたので、OCamlのファンクターについて書いてみます。

ファンクターと言えば、C++の関数オブジェクトを思い出しますが、OCamlの場合のファンクターはどちらかというとC++のテンプレートに近い感じがします。

ファンクターは次のような構文をしています。

module ファンクター名 (引数 : 引数のシグネチャの指定) :
  (* ファンクターのシグネチャの指定 *)
=
  (* モジュール式 *)

ただし、これは略記で、

module ファンクター名 
  functor(引数 : 引数のシグネチャ) -> (* ファンクターのシグネチャの指定 *)
=
  functor(引数 : 引数のシグネチャ) -> (* モジュール式 *)

が正式な書き方のようです。ちなみにどちらの場合もファンクターのシグネチャの指定は不要な場合は省略できますが、ファンクターの引数のシグネチャは省略できず、明示的に指定する必要があります。

例として最も簡単そうな、指定した型のデータを持ち、与える比較関数によって要素の並べる順番を変えるスタックのようなモジュールを生成するファンクターを作ってみます。

# module type OrderedType =
    sig
      type t
      val compare : t -> t-> bool
    end;;

まず、ファンクターの引数となるモジュールのシグネチャを定義しておきます。

# module MakeStack (Order : OrderedType) :
    sig
      type elt = Order.t
      type t  (* 情報隠蔽 *)
      val empty : t
      val add : elt -> t -> t
      val pop : t -> elt * t
    end
  =
    struct
      type elt = Order.t  (* リスト内のデータ型(引数Orderのtと一致) *)
      type t = elt list   (* リストのデータ型 *)
      let empty = []      (* t型のリスト *)

      let rec add x = function     (* add操作 *)
          [] -> [x]
        | (y :: rest as s) ->
            if Order.compare x y then x :: s
            else y :: (add x rest)

      let pop v =         (* pop操作 *)
        match v with
          [] -> failwith "do not exsit a data in the stack!" (* 例外 *)
        | x::rest -> (x, rest)
    end;;

ファンクターを定義します。(empty:空のリスト、add:第一引数の値を第二引数のリストに与えられた比較関数に基づく順序になるように追加する、pop:引数で受け取ったリストの先頭と残りのリストをタプルにして返す)。

あまり大したことはしていませんが、シグネチャの指定で型 t を情報隠ぺいしています。これは後でファンクターを使って生成したモジュールを使うときに説明します。

さて、ファンクターは定義できましたが、これを実際に使ってみます。

# module IntLessStack = MakeStack (struct
                               type t = int
                               let compare i j = i <= j
                              end);;

今回はint型のデータを持つスタックのモジュールIntStackを生成しました。
さっそくこのモジュールを使ってみましょう。

# open IntLessStack;;
# let s = add 1 empty;;

まずは「1」が格納されたリストを作ってみます。しかし上のようにすると

val s : IntLessStack.t = <abstr>

と表示されます。これはIntLessStack.tが情報隠ぺいされているためです。つぎにpopの操作を行います。

# let v = pop s;;

つぎはどのように表示されるかというと

val v : IntLessStack.elt * IntLessStack.t = (1, <abstr>)

のように表示されます。タプルが返ってきて、ちゃんとpopされたデータである「1」が見えています。理由はpop関数が返すタプルの第一要素はelt型で、eltは情報隠ぺいされていないからです。

ファンクターはこれがすべてというわけではありませんが、基本的にはこのようなことができるものです。

追記(2011/3/15):ファンクターの例としては適切でないとのご指摘があったので、比較関数を持たせた形にしました。