シグネチャ

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

という手順を踏む。

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

 # 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の方はデータ型か になっている。これは抽象データ型を表している。

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