Chapter 6  The OCaml language

Foreword

This document is intended as a reference manual for the OCaml language. It lists the language constructs, and gives their precise syntax and informal semantics. It is by no means a tutorial introduction to the language: there is not a single example. A good working knowledge of OCaml is assumed.

No attempt has been made at mathematical rigor: words are employed with their intuitive meaning, without further definition. As a consequence, the typing rules have been left out, by lack of the mathematical framework required to express them, while they are definitely part of a full formal definition of the language.

Notations

The syntax of the language is given in BNF-like notation. Terminal symbols are set in typewriter font (like this). Non-terminal symbols are set in italic font (like  that). Square brackets […] denote optional components. Curly brackets {…} denotes zero, one or several repetitions of the enclosed components. Curly brackets with a trailing plus sign {…}+ denote one or several repetitions of the enclosed components. Parentheses (…) denote grouping.

10  Module types (module specifications)

Module types are the module-level equivalent of type expressions: they specify the general shape and type properties of modules.

module-type::= modtype-path  
  sig { specification  [;;] } end  
  functor ( module-name :  module-type ) ->  module-type  
  module-type ->  module-type  
  module-type with  mod-constraint  { and mod-constraint }  
  ( module-type )  
 
mod-constraint::= type [type-params]  typeconstr  type-equation  { type-constraint }  
  module module-path =  extended-module-path  
 
specification::= val value-name :  typexpr  
  external value-name :  typexpr =  external-declaration  
  type-definition  
  exception constr-decl  
  class-specification  
  classtype-definition  
  module module-name :  module-type  
  module module-name  { ( module-name :  module-type ) } :  module-type  
  module type modtype-name  
  module type modtype-name =  module-type  
  open module-path  
  include module-type

See also the following language extensions: recovering the type of a module, substitution inside a signature, type-level module aliases, attributes, extension nodes and generative functors.

10.1  Simple module types

The expression modtype-path is equivalent to the module type bound to the name modtype-path. The expression ( module-type ) denotes the same type as module-type.

10.2  Signatures

Signatures are type specifications for structures. Signatures sigend are collections of type specifications for value names, type names, exceptions, module names and module type names. A structure will match a signature if the structure provides definitions (implementations) for all the names specified in the signature (and possibly more), and these definitions meet the type requirements given in the signature.

An optional ;; is allowed after each specification in a signature. It serves as a syntactic separator with no semantic meaning.

Value specifications

A specification of a value component in a signature is written val value-name :  typexpr, where value-name is the name of the value and typexpr its expected type.

The form external value-name :  typexpr =  external-declaration is similar, except that it requires in addition the name to be implemented as the external function specified in external-declaration (see chapter 19).

Type specifications

A specification of one or several type components in a signature is written type typedef  { and typedef } and consists of a sequence of mutually recursive definitions of type names.

Each type definition in the signature specifies an optional type equation = typexpr and an optional type representation = constr-decl … or = { field-decl}. The implementation of the type name in a matching structure must be compatible with the type expression specified in the equation (if given), and have the specified representation (if given). Conversely, users of that signature will be able to rely on the type equation or type representation, if given. More precisely, we have the following four situations:

Abstract type: no equation, no representation.
 
Names that are defined as abstract types in a signature can be implemented in a matching structure by any kind of type definition (provided it has the same number of type parameters). The exact implementation of the type will be hidden to the users of the structure. In particular, if the type is implemented as a variant type or record type, the associated constructors and fields will not be accessible to the users; if the type is implemented as an abbreviation, the type equality between the type name and the right-hand side of the abbreviation will be hidden from the users of the structure. Users of the structure consider that type as incompatible with any other type: a fresh type has been generated.
Type abbreviation: an equation = typexpr, no representation.
 
The type name must be implemented by a type compatible with typexpr. All users of the structure know that the type name is compatible with typexpr.
New variant type or record type: no equation, a representation.
 
The type name must be implemented by a variant type or record type with exactly the constructors or fields specified. All users of the structure have access to the constructors or fields, and can use them to create or inspect values of that type. However, users of the structure consider that type as incompatible with any other type: a fresh type has been generated.
Re-exported variant type or record type: an equation, a representation.
 
This case combines the previous two: the representation of the type is made visible to all users, and no fresh type is generated.

Exception specification

The specification exception constr-decl in a signature requires the matching structure to provide an exception with the name and arguments specified in the definition, and makes the exception available to all users of the structure.

Class specifications

A specification of one or several classes in a signature is written class class-spec  { and class-spec } and consists of a sequence of mutually recursive definitions of class names.

Class specifications are described more precisely in section 6.9.4.

Class type specifications

A specification of one or several classe types in a signature is written class type classtype-def { and classtype-def } and consists of a sequence of mutually recursive definitions of class type names. Class type specifications are described more precisely in section 6.9.5.

Module specifications

A specification of a module component in a signature is written module module-name :  module-type, where module-name is the name of the module component and module-type its expected type. Modules can be nested arbitrarily; in particular, functors can appear as components of structures and functor types as components of signatures.

For specifying a module component that is a functor, one may write

module module-name (  name1 :  module-type1 )(  namen :  module-typen ) :  module-type

instead of

module module-name : functor (  name1 :  module-type1 ) ->->  module-type

Module type specifications

A module type component of a signature can be specified either as a manifest module type or as an abstract module type.

An abstract module type specification module type modtype-name allows the name modtype-name to be implemented by any module type in a matching signature, but hides the implementation of the module type to all users of the signature.

A manifest module type specification module type modtype-name =  module-type requires the name modtype-name to be implemented by the module type module-type in a matching signature, but makes the equality between modtype-name and module-type apparent to all users of the signature.

Opening a module path

The expression open module-path in a signature does not specify any components. It simply affects the parsing of the following items of the signature, allowing components of the module denoted by module-path to be referred to by their simple names name instead of path accesses module-path .  name. The scope of the open stops at the end of the signature expression.

Including a signature

The expression include module-type in a signature performs textual inclusion of the components of the signature denoted by module-type. It behaves as if the components of the included signature were copied at the location of the include. The module-type argument must refer to a module type that is a signature, not a functor type.

10.3  Functor types

The module type expression functor ( module-name :  module-type1 ) ->  module-type2 is the type of functors (functions from modules to modules) that take as argument a module of type module-type1 and return as result a module of type module-type2. The module type module-type2 can use the name module-name to refer to type components of the actual argument of the functor. If the type module-type2 does not depend on type components of module-name, the module type expression can be simplified with the alternative short syntax module-type1 ->  module-type2 . No restrictions are placed on the type of the functor argument; in particular, a functor may take another functor as argument (“higher-order” functor).

10.4  The with operator

Assuming module-type denotes a signature, the expression module-type with  mod-constraint { and mod-constraint } denotes the same signature where type equations have been added to some of the type specifications, as described by the constraints following the with keyword. The constraint type [type-parameters]  typeconstr =  typexpr adds the type equation = typexpr to the specification of the type component named typeconstr of the constrained signature. The constraint module module-path =  extended-module-path adds type equations to all type components of the sub-structure denoted by module-path, making them equivalent to the corresponding type components of the structure denoted by extended-module-path.

For instance, if the module type name S is bound to the signature

        sig type t module M: (sig type u end) end

then S with type t=int denotes the signature

        sig type t=int module M: (sig type u end) end

and S with module M = N denotes the signature

        sig type t module M: (sig type u=N.u end) end

A functor taking two arguments of type S that share their t component is written

        functor (A: S) (B: S with type t = A.t) ...

Constraints are added left to right. After each constraint has been applied, the resulting signature must be a subtype of the signature before the constraint was applied. Thus, the with operator can only add information on the type components of a signature, but never remove information.