8.8 Substituting inside a signature

8.8.1 Destructive substitutions

(Introduced in OCaml 3.12, generalized in 4.06)

mod-constraint::=
type [type-params] typeconstr-name := typexpr
module module-path := extended-module-path

A “destructive” substitution (with … := …) behaves essentially likenormal signature constraints (with … = …), but it additionally removesthe redefined type or module from the signature.

Prior to OCaml 4.06, there were a number of restrictions: one could only removetypes and modules at the outermost level (not inside submodules), and in thecase of with type the definition had to be another type constructor with thesame type parameters.

A natural application of destructive substitution is merging twosignatures sharing a type name.

  1. module type Printable = sig
  2. type t
  3. val print : Format.formatter -> t -> unit
  4. end
  5. module type Comparable = sig
  6. type t
  7. val compare : t -> t -> int
  8. end
  9. module type PrintableComparable = sig
  10. include Printable
  11. include Comparable with type t := t
  12. end
  13.  

One can also use this to completely remove a field:

  1. module type S = Comparable with type t := int
  2. module type S = sig val compare : int -> int -> int end

or to rename one:

  1. module type S = sig
  2. type u
  3. include Comparable with type t := u
  4. end
  5. module type S = sig type u val compare : u -> u -> int end

Note that you can also remove manifest types, by substituting with thesame type.

  1. module type ComparableInt = Comparable with type t = int ;;
  2. module type ComparableInt = sig type t = int val compare : t -> t -> int end
  1. module type CompareInt = ComparableInt with type t := int
  2. module type CompareInt = sig val compare : int -> int -> int end

8.8.2 Local substitution declarations

(Introduced in OCaml 4.08)

specification::=
type type-subst { and type-subst }
module module-name := extended-module-path
type-subst::=[type-params] typeconstr-name := typexpr { type-constraint }

Local substitutions behave like destructive substitutions (with … := …)but instead of being applied to a whole signature after the fact, they areintroduced during the specification of the signature, and will apply to all theitems that follow.

This provides a convenient way to introduce local names for types and moduleswhen defining a signature:

  1. module type S = sig
  2. type t
  3. module Sub : sig
  4. type outer := t
  5. type t
  6. val to_outer : t -> outer
  7. end
  8. end
  9. module type S =
  10. sig type t module Sub : sig type t val to_outer : t/1 -> t/2 end end

Note that, unlike type declarations, type substitution declarations are notrecursive, so substitutions like the following are rejected:

  1. module type S = sig
  2. type 'a poly_list := [ `Cons of 'a * 'a poly_list | `Nil ]
  3. end ;;
  4. Error: Unbound type constructor poly_list