• 追加された行はこの色です。
  • 削除された行はこの色です。
[[module system]]
- ちょっときれいにしました。 -- [[けいご]] &new{2006-08-04 (金) 02:44:28};

#comment

モジュールシステムとオブジェクト指向機能
*モジュールシステムとオブジェクト指向機能 [#effea7af]
 Objective Caml にはプログラムを構造化する仕組みが2種類用意されている。
 モジュールシステムとオブジェクト指向機能である。それぞれ異なった考え
 方を背景としているので、それらを理解し、適材適所を心がける必要がある。


モジュールシステム
*モジュールシステム [#g99bf7ce]

モジュール
 モジュールは、module キーワードの後に大文字から始まるモジュール名を与
 えて宣言する。
**モジュール [#ib108db2]
モジュールは、module キーワードの後に大文字から始まるモジュール名を与えて宣言する。

# module Counter = struct 
    type t = int
    let make () = 0
    let inc c = c + 1
    let dec c = c - 1
  end;;
module Counter :
  sig
    type t = int
    val make : unit -> int
    val inc : int -> int
    val dec : int -> int
  end
# 
 # module Counter = struct 
     type t = int
     let make () = 0
     let inc c = c + 1
     let dec c = c - 1
   end;;
 module Counter :
   sig
     type t = int
     val make : unit -> int
     val inc : int -> int
     val dec : int -> int
   end
 # 
 
最初の例では、型 Counter.t とその型の値を生成・操作する関数を構成するCounter モジュールを定義している。
この例のように、モジュールは型宣言や複数の式を一つのまとまりとして扱うことが出来る。

 最初の例では、型 Counter.t とその型の値を生成・操作する関数を構成する
 Counter モジュールを定義している。
 この例のように、モジュールは型宣言や複数の式を一つのまとまりとして扱
 うことが出来る。

 Counter モジュールを使用するには、Counter の後に . を付けて式の名前を
 指定する。
Counter モジュールを使用するには、Counter の後に . を付けて式の名前を指定する。
 
# let c = Counter.make ();;
val c : int = 0
# let next = Counter.inc c;;
val next : int = 1
# 
 # let c = Counter.make ();;
 val c : int = 0
 # let next = Counter.inc c;;
 val next : int = 1
 # 
 
 モジュールは open する事により、そのモジュール名を指定する必要が無く
 なる。
モジュールは open する事により、そのモジュール名を指定する必要が無くなる。

# open Counter;;
# make ();;
- : int = 0
# 
 # open Counter;;
 # make ();;
 - : int = 0
 # 
 
ただし、異なるモジュールに同じ関数名がある場合、その両方のモジュールを open してしまうと、混乱の元なので注意。

 ただし、異なるモジュールに同じ関数名がある場合、その両方のモジュール
 を open してしまうと、混乱の元なので注意。
 # open Array;;
 # make ();;
 This expression has type unit but is here used with type int
 #

# open Array;;
# make ();;
This expression has type unit but is here used with type int
#
また、モジュールの中にモジュールを定義することも出来る。例えば、TODO管理モジュール内部でキュー構造を利用したい場合、下のように定義できる。

 また、モジュールの中にモジュールを定義することも出来る。例えば、TODO
 管理モジュール内部でキュー構造を利用したい場合、下のように定義できる。

# module Todo = struct
   module Queue = struct
     type 'a t = (int * 'a) list
     let make () = []
     let add q v =  v :: q
   end
   type 'a t = 'a Queue.t
   let add todo priori work = Queue.add todo (priori, work)
 end;;
module Todo :
  sig
    module Queue :
      sig
        type 'a t = (int * 'a) list
        val make : unit -> 'a list
        val add : 'a list -> 'a -> 'a list
      end
 # module Todo = struct
    module Queue = struct
      type 'a t = (int * 'a) list
      let make () = []
      let add q v =  v :: q
    end
    type 'a t = 'a Queue.t
    val add : ('a * 'b) list -> 'a -> 'b -> ('a * 'b) list
  end
# 

シグネチャー
 最初の Counter モジュールを定義した時、

module Counter :
  sig
    type t = int
    val make : unit -> int
    val inc : int -> int
    val dec : int -> int
  end

 という応答があった。これはモジュールのシグネチャー(モジュールの型)を
 表現している。シグネチャーは module type の後に大文字から始まるシグネ
 チャー名を付けて宣言する。

# module type SigCounter = sig
    type t
    val make : unit -> t
    val inc : t -> t
    val dec : t -> t
    let add todo priori work = Queue.add todo (priori, work)
  end;;
module type SigCounter = sig type t val make : unit -> t val inc : t -> t val dec : t -> t  end
#
 module Todo :
   sig
     module Queue :
       sig
         type 'a t = (int * 'a) list
         val make : unit -> 'a list
         val add : 'a list -> 'a -> 'a list
       end
     type 'a t = 'a Queue.t
     val add : ('a * 'b) list -> 'a -> 'b -> ('a * 'b) list
   end
 # 
 
*シグネチャー [#e70cad11]
最初の Counter モジュールを定義した時、

 このように宣言したシグネチャーは、モジュール定義の際に適用できる。
 module Counter :
   sig
     type t = int
     val make : unit -> int
     val inc : int -> int
     val dec : int -> int
   end

# module Counter' : SigCounter = Counter;;
module Counter' : SigCounter
という応答があった。これはモジュールのシグネチャー(モジュールの型)を表現している。シグネチャーは module type の後に大文字から始まるシグネチャー名を付けて宣言する。

 Counter' モジュールの型が SigCounter になっていることが分かる。
 # module type SigCounter = sig
     type t
     val make : unit -> t
     val inc : t -> t
     val dec : t -> t
   end;;
 module type SigCounter = sig type t val make : unit -> t val inc : t -> t val dec : t -> t  end
 #

 シグネチャーには、情報隠蔽に関する非常に重要な以下の二つの特徴がある。
このように宣言したシグネチャーは、モジュール定義の際に適用できる。

 - 型宣言の抽象化
 - 式の隠蔽
 # module Counter' : SigCounter = Counter;;
 module Counter' : SigCounter

 最初の特徴は型宣言の詳細を外部に公開しないことを可能にしている。例え
 ば、シグネチャーを指定していない Counter モジュールは Counter.t が実
 は int 型である事がモジュール外部に公開されているため、
Counter' モジュールの型が SigCounter になっていることが分かる。

# Counter.inc 2;;
- : int = 3
#
シグネチャーには、情報隠蔽に関する非常に重要な以下の二つの特徴がある。

 のような実行が可能である。本来 Counter.t の操作を意図した inc 関数が
 任意の int を対象に実行可能になってしまっては、せっかくの型チェック機
 能が台無しである。
- 型宣言の抽象化
- 式の隠蔽

 一方で、SigCounter を適用した Counter' モジュールでは型 t の詳細は隠
 蔽されているため、Counter'.t 以外の演算は出来ない。
最初の特徴は型宣言の詳細を外部に公開しないことを可能にしている。例えば、シグネチャーを指定していない Counter モジュールは Counter.t が実は int 型である事がモジュール外部に公開されているため、

# Counter'.inc 2;;
This expression has type int but is here used with type Counter'.t
# 
 # Counter.inc 2;;
 - : int = 3
 #

 もう一つの特徴は、公開する式の限定である。もし値の増加のみ外部に公開
 するカウンターを作りたい場合、SigIncCounter を次のように定義する。
のような実行が可能である。本来 Counter.t の操作を意図した inc 関数が任意の int を対象に実行可能になってしまっては、せっかくの型チェック機能が台無しである。

# module type SigIncCounter = sig
    type t
    val make : unit -> t
    val inc : t -> t
 end;;
module type SigIncCounter =
  sig type t val make : unit -> t val inc : t -> t end
# module IncCounter : SigIncCounter = Counter;;
module IncCounter : SigIncCounter
# let id = IncCounter.make ();;
val id : IncCounter.t = <abstr>
# IncCounter.dec id;;
Unbound value IncCounter.dec
# 
一方で、SigCounter を適用した Counter' モジュールでは型 t の詳細は隠蔽されているため、Counter'.t 以外の演算は出来ない。

 Counter.dec 関数は外部から参照できないため、どうやっても IncCounter
 の値を減少させることは出来ない。
 # Counter'.inc 2;;
 This expression has type int but is here used with type Counter'.t
 # 

ファンクター
 Counter モジュールを使って受注を生成するモジュールを作ってみよう。
もう一つの特徴は、公開する式の限定である。もし値の増加のみ外部に公開するカウンターを作りたい場合、SigIncCounter を次のように定義する。

 module Order = struct
   type t = { id : IncCounter.t; name : string }
 # module type SigIncCounter = sig
     type t
     val make : unit -> t
     val inc : t -> t
  end;;
 module type SigIncCounter =
   sig type t val make : unit -> t val inc : t -> t end
 # module IncCounter : SigIncCounter = Counter;;
 module IncCounter : SigIncCounter
 # let id = IncCounter.make ();;
 val id : IncCounter.t = <abstr>
 # IncCounter.dec id;;
 Unbound value IncCounter.dec
 # 
 
Counter.dec 関数は外部から参照できないため、どうやっても IncCounterの値を減少させることは出来ない。

   let new_order last_order_id name =
     { id = IncCounter.inc last_order_id; name = name }
**ファンクター [#ud2e61d6]
Counter モジュールを使って受注を生成するモジュールを作ってみよう。

 end;;
module Order :
  sig
    type t = { id : IncCounter.t; name : string; }
    val new_order : IncCounter.t -> string -> t
  end
# 
  module Order = struct
    type t = { id : IncCounter.t; name : string }
 
    let new_order last_order_id name =
      { id = IncCounter.inc last_order_id; name = name }
 
  end;;
 module Order :
   sig
     type t = { id : IncCounter.t; name : string; }
     val new_order : IncCounter.t -> string -> t
   end
 # 
 
Order モジュールは受注番号と商品名を持っており、受注番号には増加のみ許される IncCounter を使用している。

 Order モジュールは受注番号と商品名を持っており、受注番号には増加のみ
 許される IncCounter を使用している。
これはこれで何の問題もないのだが、番号を - で区切る必要が出きたり、1000 を法とする必要が出てきた場合、IncCounter モジュールを別のモジュールに書き換えなければならない。

 これはこれで何の問題もないのだが、番号を - で区切る必要が出きたり、
 1000 を法とする必要が出てきた場合、IncCounter モジュールを別のモジュー
 ルに書き換えなければならない。
このような困難は、IncCounter モジュールが「順に増えていく何か」という性質以上の実装を持ってしまっているために発生している。

 このような困難は、IncCounter モジュールが「順に増えていく何か」という
 性質以上の実装を持ってしまっているために発生している。
そこで、IncCounter を直接使用するのでは無く、SigIncCounter というシグネチャーを持つモジュールを輸入して Order モジュールを作成すると良い。

 そこで、IncCounter を直接使用するのでは無く、SigIncCounter というシグ
 ネチャーを持つモジュールを輸入して Order モジュールを作成すると良い。
  module OrderMaker (C : SigIncCounter) = struct
    type t = { id : C.t; name : string }
 
    let new_order last_order_id name =
      { id = C.inc last_order_id; name = name }
 
  end;;
 module OrderMaker :
   functor (C : SigIncCounter) ->
     sig
       type t = { id : C.t; name : string; }
       val new_order : C.t -> string -> t
     end

 module OrderMaker (C : SigIncCounter) = struct
   type t = { id : C.t; name : string }
このようなパラメータ付きのモジュールはファンクターと呼ばれ、他の言語には無い Objective Caml の一つ特徴である。

   let new_order last_order_id name =
     { id = C.inc last_order_id; name = name }
このファンクターに IncCounter を適用すれば、前述の Order と同じモジュールを定義できる。

 end;;
module OrderMaker :
  functor (C : SigIncCounter) ->
    sig
      type t = { id : C.t; name : string; }
      val new_order : C.t -> string -> t
    end
 # module Order' = OrderMaker(IncCounter);;
 module Order' :
   sig
     type t = OrderMaker(IncCounter).t = { id : IncCounter.t; name : string; }
     val new_order : IncCounter.t -> string -> t
   end
 # 

 このようなパラメータ付きのモジュールはファンクターと呼ばれ、他の言語
 には無い Objective Caml の一つ特徴である。
また、もし 1000 を法とする受注番号を生成したいなら、以下のようにすれば良い。

 このファンクターに IncCounter を適用すれば、前述の Order と同じモジュー
 ルを定義できる。
 # module TCounter : SigIncCounter = struct
    type t = int
    let make () = 0
    let inc c = (c + 1) mod 1000
  end;;
 module TCounter : SigIncCounter
 # module TOrder = OrderMaker(TCounter);;
 module TOrder :
   sig
     type t = OrderMaker(TCounter).t = { id : TCounter.t; name : string; }
     val new_order : TCounter.t -> string -> t
   end
 # 
 
**Exercise [#v6fef488]

# module Order' = OrderMaker(IncCounter);;
module Order' :
  sig
    type t = OrderMaker(IncCounter).t = { id : IncCounter.t; name : string; }
    val new_order : IncCounter.t -> string -> t
  end
# 

 また、もし 1000 を法とする受注番号を生成したいなら、以下のようにすれ
 ば良い。

# module TCounter : SigIncCounter = struct
   type t = int
   let make () = 0
   let inc c = (c + 1) mod 1000
 end;;
module TCounter : SigIncCounter
# module TOrder = OrderMaker(TCounter);;
module TOrder :
  sig
    type t = OrderMaker(TCounter).t = { id : TCounter.t; name : string; }
    val new_order : TCounter.t -> string -> t
  end
# 


Exercise ? 相互依存したモジュールは、再帰的なモジュールによって定義で
きる。例を挙げよ。

Exercise ? OrderMaker の型を module type によって定義せよ。

Exercise ? ファンクターのパラメータにはファンクターを取ることも出来る。
例を挙げよ。

Exercise ? 通常はシグネチャーによる型の隠蔽は、隠蔽するかしないかのど
ちらかになってしまうが、private row によってコンストラクタの一部のみ公
開するといった事が可能になる。例を挙げよ。

トップ   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS