module system?
モジュールシステムとオブジェクト指向機能
Objective Caml にはプログラムを構造化する仕組みが2種類用意されている。 モジュールシステムとオブジェクト指向機能である。それぞれ異なった考え 方を背景としているので、それらを理解し、適材適所を心がける必要がある。
モジュールシステム
モジュール
モジュールは、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
#
最初の例では、型 Counter.t とその型の値を生成・操作する関数を構成する Counter モジュールを定義している。 この例のように、モジュールは型宣言や複数の式を一つのまとまりとして扱 うことが出来る。
Counter モジュールを使用するには、Counter の後に . を付けて式の名前を 指定する。
# let c = Counter.make ();; val c : int = 0
# let next = Counter.inc c;; val next : int = 1
#
モジュールは open する事により、そのモジュール名を指定する必要が無く なる。
# open Counter;;
# make ();;
#
ただし、異なるモジュールに同じ関数名がある場合、その両方のモジュール を open してしまうと、混乱の元なので注意。
# open Array;;
# make ();; This expression has type unit but is here used with type int
#
また、モジュールの中にモジュールを定義することも出来る。例えば、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 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 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' モジュールの型が SigCounter になっていることが分かる。
シグネチャーには、情報隠蔽に関する非常に重要な以下の二つの特徴がある。
- 型宣言の抽象化 - 式の隠蔽
最初の特徴は型宣言の詳細を外部に公開しないことを可能にしている。例え ば、シグネチャーを指定していない Counter モジュールは Counter.t が実 は int 型である事がモジュール外部に公開されているため、
# Counter.inc 2;;
#
のような実行が可能である。本来 Counter.t の操作を意図した inc 関数が 任意の int を対象に実行可能になってしまっては、せっかくの型チェック機 能が台無しである。
一方で、SigCounter を適用した Counter' モジュールでは型 t の詳細は隠 蔽されているため、Counter'.t 以外の演算は出来ない。
# Counter'.inc 2;; This expression has type int but is here used with type Counter'.t
#
もう一つの特徴は、公開する式の限定である。もし値の増加のみ外部に公開 するカウンターを作りたい場合、SigIncCounter を次のように定義する。
# 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 の値を減少させることは出来ない。
ファンクター
Counter モジュールを使って受注を生成するモジュールを作ってみよう。
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 を使用している。
これはこれで何の問題もないのだが、番号を - で区切る必要が出きたり、 1000 を法とする必要が出てきた場合、IncCounter モジュールを別のモジュー ルに書き換えなければならない。
このような困難は、IncCounter モジュールが「順に増えていく何か」という 性質以上の実装を持ってしまっているために発生している。
そこで、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
このようなパラメータ付きのモジュールはファンクターと呼ばれ、他の言語 には無い Objective Caml の一つ特徴である。
このファンクターに IncCounter を適用すれば、前述の Order と同じモジュー ルを定義できる。
# 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 によってコンストラクタの一部のみ公 開するといった事が可能になる。例を挙げよ。