7.9 private な型

7.9.1. private なバリアントとレコード型
7.9.2. private な型略記
7.9.3. private な列型

モジュールシグネチャ内で type t = private ... 形式で private な型を宣言すると、ライブラリの利用者に型の実装の一部だけを公開することができるようになります。この点から見ると、 private な型は抽象型の宣言やデータ型の定義、型略記の間を取ったものになります(抽象型の宣言では型の実装についての情報はまったく公開されませんし、データ型の定義や型略記では型の実装がすべて公開されます)。 private な型の宣言は、バリアントとレコード型( 7.9.1 節「private なバリアントとレコード型」)、型略記( 7.9.2 節「private な型略記」)、列型( 7.9.3 節「private な列型」)のみっつに分かれます。

7.9.1 private なバリアントとレコード型

(OCaml 3.07 〜)

type-representation ::= ...
| private constr-decl { | constr-decl }
| private field-decl { | field-decl }

private と宣言されたバリアントやレコード型の値は、通常通りパターンマッチングで分解したり expr.field 記法でレコードの要素にアクセスしたりできますが、構成子の適用やレコードの構成により直接これらの値を構成することはできません。さらに、 private なレコード型の変更可能なフィールドへの代入は認められません。

private な型の典型的な使用法は、モジュールのシグネチャで使うもので、 private な型の値は常にそのモジュールで提供される関数で構成されるようにし、かつその一方で、定義したモジュールの外部でもその値にパターンマッチングできるようにする、というものです。例を挙げます。

module M : sig
             type t = private A | B of int
             val a : t
             val b : int -> t
           end
         = struct
             type t = A | B of int
             let a = A
             let b n = assert (n > 0); B n
           end
      

ここでは、 private 宣言により、 M.t の値について、構成子 B の引数は常に正の整数になることが保証されます。

パラメータの変位については、 private な型は抽象型と同様に扱われます。すなわち、 private な型にパラメータがあった場合、変位を指定する場合にはそのパラメータに +- 指定することができ、指定しなかった場合には不変になります。

7.9.2 private な型略記

(OCaml 3.11 〜)

type-equation ::= ...
| private typexpr

通常の型略記とは異なり、 private な型略記は実装の型 typexpr とは区別される型を宣言します。ただし、宣言した型から typexpr へ型変換することはできます。さらに、コンパイラは実装の型が何であるか「知っている」ので、そのことを使って型情報を利用した最適化を行なうこともできます。曖昧さのため、 typexpr としてオブジェクトや多相バリアント型を指定することはできませんが、同様のことは private な列型を使えば実現できます。

以下の例では private な型略記を使って非負整数のモジュールを定義しています。

module N : sig
             type t = private int
             val of_int: int -> t
             val to_int: t -> int
           end
       = struct
           type t = int
           let of_int n = assert (n >= 0); n
           let to_int n = n
         end
      

N.tint とは互換性がなく、これにより非負整数と通常の整数を混同しないことが保証されます。ただし、 x の型が N.t であるとき、型変換 (x :> int) は合法で、 N.to_int x の場合と同じように、内部表現として使われている整数が返ります。また、深い型変換もサポートされています。 l の型が N.t list であるとき、型変換 (l :> int list) は、 List.map N.to_int とした場合と同じように、内部で使われている整数をリストにしたものが返ります(ただし、 List.map N.to_int の場合とは異なり、リスト l はコピーされません)。

型変換 (expr :> typexpr) は略記形で、 private な型略記の存在下ではexpr の型と typexpr のどちらにも型変数が含まれない場合にだけ動作することに注意してください。それ以外の場合には、非省略形の (expr : typ_e :> typexpr) を使わなければなりません(typ_eexpr に期待される型です)。具体的に言えば、上の例では (x : N.t :> int)(l : N.t list :> int list) のようになります。

7.9.3 private な列型

(OCaml 3.09 〜)

type-equation ::= ...
| private typexpr

private な列型は型構造の一部分が抽象的になる型略記です。具体的には、 typexpr は、オブジェクト型か多相バリアント型で、詳細化の余地のあるものでなければなりません。インタフェース中で private 宣言が使われた場合、対応する実装は、基底となる実装か private な型の詳細化されたものでなければなりません。

module M : sig type c = private < x : int; .. > val o : c end =
  struct
    class c = object method x = 3 method y = 2 end
    let o = new c
  end
      

この宣言により、メソッド y が隠蔽され、さらに型 c が、既存の、どの閉じたオブジェクト型とも非互換になります。これは、 o だけが c 型になるということです。この意味では、 private な列型の振る舞いは private なレコード型と同じです。しかし private な列型は段階的な詳細化に関してより柔軟です。この機能はファンクタと組み合わせて使うことができます。

module F(X : sig type c = private < x : int; .. > end) =
  struct
    let get_x (o : X.c) = o#x
  end
module G(X : sig type c = private < x : int; y : int; .. > end) =
  struct
    include F(X)
    let get_y (o : X.c) = o#y
  end
      

多相バリアント型は、新たな構成子を追加するか、宣言された構成子を削除できるようにするかのふた通りの方法で詳細化することができます。後者は private なバリアント型に対応し(private な型の値を作成することができない)、前者では、追加された構成子を扱うためにパターンマッチに default case を持たせておく必要があります。

type t = [ `A of int | `B of bool ]
type u = private [< t > `A ]
type v = private [> t ]
      

u では (`A n) として値を作成することができますが、 (`B b) とすることはできません。型 v では値の作成に制限はありませんが、パターンマッチに default case がなければなりません。

抽象型や private な型と同じく、型パラメータの変位は推論されず、明示しなければなりません。