8.3 Private types

Private type declarations in module signatures, of the formtype t = private …, enable libraries toreveal some, but not all aspects of the implementation of a type toclients of the library. In this respect, they strike a middle groundbetween abstract type declarations, where no information is revealedon the type implementation, and data type definitions and typeabbreviations, where all aspects of the type implementation arepublicized. Private type declarations come in three flavors: forvariant and record types (section 8.3.1),for type abbreviations (section 8.3.2),and for row types (section 8.3.3).

8.3.1 Private variant and record types

(Introduced in Objective Caml 3.07)

type-representation::=
= private [ | ] constr-decl { | constr-decl }
= private record-decl

Values of a variant or record type declared privatecan be de-structured normally in pattern-matching or viathe expr. field notation for record accesses. However, values ofthese types cannot be constructed directly by constructor applicationor record construction. Moreover, assignment on a mutable field of aprivate record type is not allowed.

The typical use of private types is in the export signature of amodule, to ensure that construction of values of the private type alwaysgo through the functions provided by the module, while still allowingpattern-matching outside the defining module. For example:

  1. module M : sig
  2. type t = private A | B of int
  3. val a : t
  4. val b : int -> t
  5. end = struct
  6. type t = A | B of int
  7. let a = A
  8. let b n = assert (n > 0); B n
  9. end
  10.  

Here, the private declaration ensures that in any value of typeM.t, the argument to the B constructor is always a positive integer.

With respect to the variance of their parameters, private types arehandled like abstract types. That is, if a private type hasparameters, their variance is the one explicitly given by prefixingthe parameter by a ‘+’ or a ‘-’, it is invariant otherwise.

8.3.2 Private type abbreviations

(Introduced in Objective Caml 3.11)

type-equation::=
= private typexpr

Unlike a regular type abbreviation, a private type abbreviationdeclares a type that is distinct from its implementation type typexpr.However, coercions from the type to typexpr are permitted.Moreover, the compiler “knows” the implementation type and can takeadvantage of this knowledge to perform type-directed optimizations.

The following example uses a private type abbreviation to define amodule of nonnegative integers:

  1. module N : sig
  2. type t = private int
  3. val of_int: int -> t
  4. val to_int: t -> int
  5. end = struct
  6. type t = int
  7. let of_int n = assert (n >= 0); n
  8. let to_int n = n
  9. end
  10.  

The type N.t is incompatible with int, ensuring that nonnegativeintegers and regular integers are not confused. However, if x hastype N.t, the coercion (x :> int) is legal and returns theunderlying integer, just like N.to_int x. Deep coercions are alsosupported: if l has type N.t list, the coercion (l :> int list)returns the list of underlying integers, like List.map N.to_int lbut without copying the list l.

Note that the coercion (expr:> typexpr) is actually an abbreviatedform,and will only work in presence of private abbreviations if neither thetype of expr nor typexpr contain any type variables. If they do,you must use the full form (expr: typexpr1:> typexpr2) wheretypexpr1 is the expected type of expr. Concretely, this would be (x : N.t :> int) and (l : N.t list :> int list) for the above examples.

8.3.3 Private row types

(Introduced in Objective Caml 3.09)

type-equation::=
= private typexpr

Private row types are type abbreviations where part of thestructure of the type is left abstract. Concretely typexpr in theabove should denote either an object type or a polymorphic varianttype, with some possibility of refinement left. If the privatedeclaration is used in an interface, the corresponding implementationmay either provide a ground instance, or a refined private type.

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

This declaration does more than hiding the y method, it also makesthe type c incompatible with any other closed object type, meaningthat only o will be of type c. In that respect it behavessimilarly to private record types. But private row types aremore flexible with respect to incremental refinement. This feature canbe used in combination with functors.

  1. module F(X : sig type c = private < x : int; .. > end) =
  2. struct
  3. let get_x (o : X.c) = o#x
  4. end
  5. module G(X : sig type c = private < x : int; y : int; .. > end) =
  6. struct
  7. include F(X)
  8. let get_y (o : X.c) = o#y
  9. end
  10.  

A polymorphic variant type [t], for example

  1. type t = [ `A of int | `B of bool ]
  2.  

can be refined in two ways. A definition [u] may add new field to [t],and the declaration

  1. type u = private [> t]
  2.  

will keep those new fields abstract. Construction of values of type[u] is possible using the known variants of [t], but anypattern-matching will require a default case to handle the potentialextra fields. Dually, a declaration [u] may restrict the fields of [t]through abstraction: the declaration

  1. type v = private [< t > `A]
  2.  

corresponds to private variant types. One cannot create a value of theprivate type [v], except using the constructors that are explicitlylisted as present, (`A n) in this example; yet, whenpatter-matching on a [v], one should assume that any of theconstructors of [t] could be present.

Similarly to abstract types, the variance of type parametersis not inferred, and must be given explicitly.