* * *
* *"> *
* * *
Akihito Nagata's Page
|
|
OcamlのModuleシステムrakuda

分割コンパイルすることにより、モジュールを作成する場合もあるが、ここではそれ以外の方法として直接モジュールを作る方法を3つのキーワード:structures,signatures,functorsを中心に説明する。

分割コンパイルした場合と比較すると、signatureは.mliに、structureは.mlに相当する。分割コンパイルとの違いは、一つのファイルに複数のモジュールが定義できること、一つのsignatureを複数のmoduleに適用できることである。signatureを共有することによりコードの再利用性を高めることができる。

Ocamlではこの他のfunctorとパラメータ化されたmoduleを作ることができる。functorは引数として与えられたstructureからstructureを作る。functorにより、より一般的なstructureを作ることができる。

モジュールのsignature

signatureはmodule type宣下元により定義される。

module type Name = sig signature end
      
signature名は大文字から始まらなくてはならない。signatureに含めることができるのは
  • type 宣言
  • exception 定義
  • メソッドの型定義(val)
  • openステートメントによる他のsignatureの名前空間のopen
  • includeにより他のsignatureのインクルード
  • ネストしたsigunatureの宣言。
である。

signatureはインタフェース中でも、その内部の実装中でもOcamlのtopでも定義できる。

例として有限集合を考える。signatureはsetが持つべき型宣言を定義する。Ocamlのtoploopで次を宣言する。

# module type FsetSig =
  sig
     type 'a t
     val empty : 'a t
     val mem : 'a -> 'a t -> bool
     val insert : 'a -> 'a t -> bool
  end;;
module type FsetSig =
  sig
     type 'a t
     val empty : 'a t
     val mem : 'a -> 'a t -> bool
     val insert : 'a -> 'a t -> bool
  end
      
もし既存のsignatureがあるならばincludeを使って以下のように書ける。
# module type FsetDSig =
  sig
      include FsetSig
      val delete : 'a -> 'a t -> 'a t
  end;;
module type FsetSig =
  sig
     type 'a t
     val empty : 'a t
     val mem : 'a -> 'a t -> bool
     val insert : 'a -> 'a t -> bool
     val delete : 'a -> 'a t -> 'a t
  end
      

モジュールのstructure

structureはmoduleキーワードを使って定義される。

モジュール Name = struct implementation end
      
モジュール名も大文字から始まらなくてはならない。モジュールの定義に含めることができるのは
  • type 定義
  • exception定義
  • method定義(let)
  • open宣言による他のモジュールの名前空間のオープン
  • include宣言による他のモジュールの内容のインクルード
  • signature定義
  • ネストしたstructureの定義
である。モジュールの例として平行二分探索木を定義してみると、
#module Fset =
   struct
      type color = 
         Red
       | Black

      type 'a t = 
         Node of color * 'a t * 'a * 'a t
       | Leaf
  
      let empty = Leaf
   
      let rec mem x = function
         Leaf -> false
       | Node (_, a, y, b) ->
            if x < y then mem x a
            else if x > y then mem x b
            else true

      let balance =  ... 

      let insert x s = ...
   end;;
module Fset : 
  sig 
    type color = | Red | Black
    and 'a t = | Node of color * 'a t * 'a * 'a t | Leaf
    val empty : 'a t
    val mem : 'a -> 'a t -> bool
    val balance : color * 'a t * 'a * 'a t -> 'a t
    val insert : 'a -> 'a t -> 'a t
  end;;
# Fset.empty;;
- : 'a Fset.t = Fset.Leaf
# Fset.balance;;
- : Fset.color * 'a Fset.t * 'a * 'a Fset.t -> 'a Fset.t = <fun>
      
となる。

signatureの適用

structureにsignatureを明示的に適用しない場合はデフォルトのsignatureが使われ、その場合はstructure内の全ての定義が外から見えるようになってしまう。

structureにsignatureを適用するには":"を使う。

module Name : SigName = struct implementation end
先程定義したsignature FsetSig をFsetに適用する例をしめす。
module Fset : FsetSig =  
   struct
      type color = 
         Red
       | Black

      type 'a t = 
         Node of color * 'a t * 'a * 'a t
       | Leaf
  
      let empty = Leaf
   
      let rec mem x = function
         Leaf -> false
       | Node (_, a, y, b) ->
            if x < y then mem x a
            else if x > y then mem x b
            else true

      let balance =  ... 

      let insert x s = ...
   end;;
module Fset : FsetSig
# Fset.empty;;
- : 'a Fset.t = <abstr>
# Fset.balance;;
Unbound value Fset.balance
      
signatureを適用することにより、'a tの定義が抽象化され、balanceは外部から見られなくなった。

Functor

有限集合を表すモジュールを実装しようとした場合問題となるのは、元から用意されている "<" 演算子を使って比較を行っているところである。<演算子は実装依存であり、常に望んでいる順序付けを行ってくれるとは限らない。

この問題に対処するために、独自の比較関数を定義することにする。しかし、そうすると集合の中身の型に付いて異なる実装が必要となる。これに対処するためにfunctorを用いる。functorとはモジュール上の関数であり、モジュールを引数にとりモジュールを返す。functorはfunctorキーワードを用いて定義するか、それに変わる構文を用いて定義することができる。

module Name = functor (ArgName : ArgSig) ->
         struct implementation end
        module Name(Arg : ArgSig) = 
         struct implementation end
      
有限集合のモジュールの例だと要素の型eltを持ち、比較関数compareを持つstructureを定義する必要があるだろう。compare関数は以下のどれかを返すとする。
  1. 第1引数が第2引数より小さければ負の整数を返す。
  2. 二つの引数が等しければ0を返す。
  3. 第1引数が第2引数より大きければ正の整数を返す。
これのsignatureは以下のように書ける。
module type EltSig =
sig
   type elt
   val compare : elt -> elt -> int
end
      

これに伴ってFsetSigもある特定のeltが扱えるように書き換える必要がある。ここで注意すべきは この有限集合はそれ自体polymorphicではない点である。

module type FsetSig =
sig
   type elt
   type t

   val empty : t
   val mem : elt -> t -> bool
   val insert : elt -> t -> t
end

次に集合の定義をfunctorとして定義し直す。実装はeltの定義を導入し、memとinsertは比較関数を使うように書き直される。

# module MakeFset (Elt : EltSig) =
  struct
     type elt = Elt.elt
     type color : = ...
     type t =
        Node of color * t * elt * t
      | Leaf
   
     let empty = Leaf
     
     let rec mem x = function
        Leaf -> false
      | Node (_, a, y, b) ->
          let i = Elt.compare x y in
             if i < 0 then mem x a
             else if i > 0 then mem x b
             else true

     let balance = ...
    
     let insert x s = ...
  end;;
module MakeFset :
  functor(Elt : EltSig) ->
    sig
      type elt = Elt.elt
      and color = | Red | Black
      and t = | Node of color * t * elt * t | Leaf
      val empty : t
      val mem : Elt.elt -> t -> bool
      val balance : color * t * elt * t -> t
      val insert : elt -> t -> t
    end
      

このfunctorは引数としてsignature EltSigを持つモジュールEltを受け取り、最後に表示されているようなsignatureを持つモジュールを返す。最後に返されるモジュールは中身が全て見えてしまっているので、これが適当でない場合には

# module MakeFset (Elt : EltSig) : FsetSig =
  struct 
      ...
  end;;
module MakeFset : functor(Elt : EltSig) -> FsetSig
      
のように返り値のモジュールのsignatureを指定する。

functorの使用

functorで定義されたモジュールを使うにはある特定のEltSigのsignatureを持つモジュールの実装を適用する必要がある。例として整数の型と比較関数を持つモジュールを定義する。この場合は比較関数は整数の差で実装できる。

# module Int = 
  struct 
     type elt = int
     let compare = (-)
  end;;
module Int : 
  sig 
     type elt = int 
     val compare : int -> int -> int 
  end
# Int.compare 3 5;;
- : int = -2
      

signature EltSigではelt型はabstractとなる。IntモジュールはEltSigを満たす必要があるが、eltの定義が見える状態にしておきたいのでsignatureは適用しない。

 
# module Int' = (Int : EltSig);;
module Int' : EltSig
# Int'.compare 3 5;;
Characters 13-14:
This expression has type int but is here used with type Int'.elt
      

functorはfunctor_name(arg_name)という構文で適用される。整数の有限集合のモジュールを作るにはMakeFset functorにIntモジュールを適用する。

# module IntSet = MakeFset (Int);;
  module IntSet : 
    sig
      type elt = MakeFset(Int).elt
      and t = MakeFset(Int).t
      val empty : t
      val mem : elt -> t -> bool
      val inset : elt -> t -> t
    end
# IntSet.empty;;
- : IntSet.t = <abstr>
# IntSet.insert 1 IntSet.empty;;
- : IntSet.t = IntSet.Node (IntSet.Leaf, 1, IntSet.Leaf)
      
# module IntSet' = MakeFset(Int');;
module IntSet' :
  sig
    type elt = Int'.elt
    and t = MakeFset(Int').t = Node of t * elt * t | Leaf
    val empty : t
    val mem : Int'.elt -> t -> bool
    val insert : elt -> t -> t
  end
# IntSet'.insert 1 IntSet'.empty;;
                 ^
This expression has type int but is here used with type
  IntSet'.elt = Int'.elt

|
前のページはこちら