前のエントリの例のようにシグネチャをコンパイラに推論させるのではなく,自分で書くこともできる。そのとき,モジュールの外部には公開したくない関数や,定義した型の詳細を隠蔽することもできる。一般には:
- シグネチャを定義する
- そのシグネチャをモジュールに与える
という手順を踏む。
たとえば次のようにモジュールを定義したとする。
# module Table = struct type ('a, 'b) t = Empty | Entry of 'a * 'b * ('a, 'b) t let empty = Empty let add key datum table = Entry (key, datum, table) let rec retrieve key = function Empty -> None | Entry (key', datum, rest) -> if key = key' then Some datum else retrieve key rest let rec delete key = function Empty -> Empty | Entry (key', datum, rest) -> if key = key' then delete key rest else Entry (key', datum, delete key rest) let rec dump = function Empty -> [] | Entry (key, datum, rest) -> (key, datum) :: (dump (delete key rest)) end;; module Table : sig type ('a, 'b) t = Empty | Entry of 'a * 'b * ('a, 'b) t val empty : ('a, 'b) t val add : 'a -> 'b -> ('a, 'b) t -> ('a, 'b) t val retrieve : 'a -> ('a, 'b) t -> 'b option val delete : 'a -> ('a, 'b) t -> ('a, 'b) t val dump : ('a, 'b) t -> ('a * 'b) list end
見てわかるとおり,データ型ひとつと関数を4つ定義している。
さて,ここで Table.t型の詳細とdelete関数を隠すことにする(ついでに書いておくとこのように詳細の隠されたデータ型を抽象データ型という)。隠すには単にシグネチャに書かなければいい。具体的にはデータ型定義の = 以降の部分と,delete関数の定義部分だ。
シグネチャを定義するには module type宣言を使う。
# module type TABLE1 = sig type ('a, 'b) t val empty : ('a, 'b) t val add : 'a -> 'b -> ('a, 'b) t -> ('a, 'b) t val retrieve : 'a -> ('a, 'b) t -> 'b option val dump : ('a, 'b) t -> ('a * 'b) list end;; module type TABLE1 = sig type ('a, 'b) t val empty : ('a, 'b) t val add : 'a -> 'b -> ('a, 'b) t -> ('a, 'b) t val retrieve : 'a -> ('a, 'b) t -> 'b option val dump : ('a, 'b) t -> ('a * 'b) list end
このシグネチャを与えて新しい Table1モジュールを定義する。といっても実態はTableモジュールとおなじだ。
# module Table1 : TABLE1 = Table;; module Table1 : TABLE1
これで実体は同じだけどシグネチャの違うモジュールができた。2つを比べてみよう。
# Table.empty;; - : ('a, 'b) Table.t = Table.Empty # Table1.empty;; - : ('a, 'b) Table1.t = <abstr>
Table1の方はデータ型か <abstr> になっている。これは抽象データ型を表している。
# Table.retrieve;; - : 'a -> ('a, 'b) Table.t -> 'b option = <fun> # Table1.retrieve;; - : 'a -> ('a, 'b) Table1.t -> 'b option = <fun>
retrieve関数には両方ともアクセスできる。
# Table.delete;; - : 'a -> ('a, 'b) Table.t -> ('a, 'b) Table.t = <fun> # Table1.delete;; Characters 0-13: Table1.delete;; ^^^^^^^^^^^^^ Unbound value Table1.delete
Table1.delete はダメ。