8.6 First-class modules

(Introduced in OCaml 3.12; pattern syntax and package type inferenceintroduced in 4.00; structural comparison of package types introduced in 4.02.;fewer parens required starting from 4.05)

typexpr::=
(module package-type)
module-expr::=
(val expr [: package-type])
expr::=
(module module-expr [: package-type])
pattern::=
(module module-name [: package-type])
package-type::=modtype-path
modtype-path with package-constraint { and package-constraint }
package-constraint::=type typeconstr = typexpr

Modules are typically thought of as static components. This extensionmakes it possible to pack a module as a first-class value, which canlater be dynamically unpacked into a module.

The expression (modulemodule-expr: package-type) converts themodule (structure or functor) denoted by module expression module-exprto a value of the core language that encapsulates this module. Thetype of this core language value is (modulepackage-type).The package-type annotation can be omitted if it can be inferredfrom the context.

Conversely, the module expression (valexpr: package-type)evaluates the core language expression expr to a value, which musthave type modulepackage-type, and extracts the module that wasencapsulated in this value. Again package-type can be omitted if thetype of expr is known.If the module expression is already parenthesized, like the argumentsof functors are, no additional parens are needed: Map.Make(val key).

The pattern (modulemodule-name: package-type) matches apackage with type package-type and binds it to module-name.It is not allowed in toplevel let bindings.Again package-type can be omitted if it can be inferred from theenclosing pattern.

The package-type syntactic class appearing in the (modulepackage-type) type expression and in the annotated forms represents asubset of module types.This subset consists of named module types with optional constraintsof a limited form: only non-parametrized types can be specified.

For type-checking purposes (and starting from OCaml 4.02), package typesare compared using the structural comparison of module types.

In general, the module expression (valexpr: package-type) cannot be used in the body of a functor, because this could causeunsoundness in conjunction with applicative functors.Since OCaml 4.02, this is relaxed in two ways:if package-type does not contain nominal type declarations (i.e. types that are created with a proper identity), then thisexpression can be used anywhere, and even if it contains such typesit can be used inside the body of a generativefunctor, described in section 8.16.It can also be used anywhere in the context of a local module bindingletmodulemodule-name=(val expr1: package-type)in expr2.

Basic example

A typical use of first-class modules is toselect at run-time among several implementations of a signature.Each implementation is a structure that we can encapsulate as afirst-class module, then store in a data structure such as a hashtable:

  1. type picture =
  2. module type DEVICE = sig
  3. val draw : picture -> unit
  4. end
  5. let devices : (string, (module DEVICE)) Hashtbl.t = Hashtbl.create 17
  6. module SVG = struct end
  7. let _ = Hashtbl.add devices "SVG" (module SVG : DEVICE)
  8. module PDF = struct end
  9. let _ = Hashtbl.add devices "PDF" (module PDF: DEVICE)
  10.  

We can then select one implementation based on command-linearguments, for instance:

  1. let parse_cmdline () =
  2. module Device =
  3. (val (let device_name = parse_cmdline () in
  4. try Hashtbl.find devices device_name
  5. with Not_found ->
  6. Printf.eprintf "Unknown device %s\n" device_name;
  7. exit 2)
  8. : DEVICE)
  9.  

Alternatively, the selection can be performed within a function:

  1. let draw_using_device device_name picture =
  2. let module Device =
  3. (val (Hashtbl.find devices device_name) : DEVICE)
  4. in
  5. Device.draw picture
  6.  
Advanced examples

With first-class modules, it is possible to parametrize some code over theimplementation of a module without using a functor.

  1. let sort (type s) (module Set : Set.S with type elt = s) l =
  2. Set.elements (List.fold_right Set.add l Set.empty)
  3. val sort : (module Set.S with type elt = 'a) -> 'a list -> 'a list = <fun>

To use this function, one can wrap the Set.Make functor:

  1. let make_set (type s) cmp =
  2. let module S = Set.Make(struct
  3. type t = s
  4. let compare = cmp
  5. end) in
  6. (module S : Set.S with type elt = s)
  7. val make_set : ('a -> 'a -> int) -> (module Set.S with type elt = 'a) = <fun>