モジュールの定義

モジュール(正確にはストラクチャ)の定義は次のようにして,struct と endo のあいだに各種定義の書く。

# module Tree =
struct
type 'a t = Lf | Br of 'a * 'a t * 'a t
let rec size = function
Lf -> 0
| Br (_, left, right) -> 1 + size left + size right
let rec depth = function
Lf -> 0
| Br (_, left, right) -> 1 + max (depth left) (depth right)
end
;;

モジュールの中に書けるのは:

  • 変数束縛
  • 関数宣言
  • type による型宣言
  • exception宣言
  • open宣言
  • module定義(入れ子)

さて,対話環境で上のようにモジュール定義をすると次のような応答が返ってくる。

module Tree :
sig
type 'a t = Lf | Br of 'a * 'a t * 'a t
val size : 'a t -> int
val depth : 'a t -> int
end

これはシグネチャといって,いってみればモジュールの型というべきもの。これについてはまた後で書く。

シグネチャ

前のエントリの例のようにシグネチャをコンパイラに推論させるのではなく,自分で書くこともできる。そのとき,モジュールの外部には公開したくない関数や,定義した型の詳細を隠蔽することもできる。一般には:

  • シグネチャを定義する
  • そのシグネチャをモジュールに与える

という手順を踏む。

たとえば次のようにモジュールを定義したとする。

# 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 はダメ。