Next: Type classes, Previous: Higher-order, Up: Top [Contents]
The Mercury module system is relatively simple and straightforward.
Each module must start with a ‘:- module ModuleName’ declaration, specifying the name of the module.
An ‘:- interface.’ declaration indicates the start of the module’s interface section: this section specifies the entities that are exported by this module. Mercury provides support for abstract data types, by allowing the definition of a type to be kept hidden, with the interface only exporting the type name. The interface section may contain definitions of types, type classes, data constructors, instantiation states, and modes, and declarations for abstract data types, abstract type class instances, functions, predicates, and (sub-)modules. The interface section may not contain definitions for functions or predicates (i.e. clauses), or definitions of (sub-)modules.
An ‘:- implementation.’ declaration indicates the start of the module’s implementation section. Any entities declared in this section are local to the module (and its submodules, if any) and cannot be used by other modules. The implementation section must contain definitions for all abstract data types, abstract instance declarations, functions, predicates, and submodules exported by the module, as well as for all local types, type class instances, functions, predicates, and submodules. The implementation section can be omitted if it is empty.
The module may optionally end with a ‘:- end_module ModuleName’ declaration; the name specified in the ‘end_module’ must be the same as that in the corresponding ‘module’ declaration.
In order to constrain which entity a name refers to, functor terms representing predicates, functions, expressions, constructor fields, types, modes, insts, type classes, instances and sub-modules can be explicitly module qualified using the ‘.’ operator, e.g. ‘module.name’ or ‘module.name(Args)’. Operator terms (that is, functor terms written using operator syntax) may also be module qualified, though this requires putting parentheses around the operator and its arguments, e.g. ‘module.(A + B)’. The module name used in a module qualified term may itself be module qualified if it is a sub-module, e.g. ‘module.submodule.name(Args)’. A functor is fully qualified if every name that is not a builtin or top-level module is module qualified.
Currently we also support ‘__’ as an alternative module qualifier,
so you can write module__name
instead of module.name
.
The principal functor of a module qualified term combines the name and qualifiers with the arity. For example, the principal functor of ‘foo.bar.baz(Xs)’ is ‘foo.bar.baz/1’, and the principal functor of ‘quux.(A + B)’ is ‘quux.'+'/2’.
If a module wishes to make use of entities exported by other modules,
then it must explicitly import those modules
using one or more ‘:- import_module Modules’
or ‘:- use_module Modules’ declarations,
in order to make those declarations visible.
In both cases, Modules
is a comma-separated list of fully qualified module names.
These declarations may occur
either in the interface or the implementation section.
If the imported entities are used in the interface section,
then the corresponding import_module
or use_module
declaration must also be in the interface section.
If the imported entities are only used in the implementation section,
the import_module
or use_module
declaration
should be in the implementation section.
You may need to module qualify a name if that name has
several applicable definitions,
and the context of its occurrence does not resolve this ambiguity.
Module qualifiers are also useful for readability.
Uses of entities imported using use_module
declarations
must be fully qualified.
Certain optimizations require information or source code for predicates defined in other modules to be as effective as possible. At the moment, inlining and higher-order specialization are the only optimizations that the Mercury compiler can perform across module boundaries.
Exactly one module of the program must export a predicate ‘main/2’, which must be declared as either
:- pred main(io.state::di, io.state::uo) is det.
or
:- pred main(io.state::di, io.state::uo) is cc_multi.
(or any declaration equivalent to one of the two above).
Mercury has a standard library which includes over 100 modules, including modules for lists, stacks, queues, priority queues, sets, bags (multi-sets), maps (dictionaries), random number generation, input/output, and filename and directory handling. See the Mercury Library Reference Manual for a list of the available modules, and for the documentation of each module.
For illustrative purposes, here is the definition of a simple module for managing queues:
:- module queue. :- interface. % Declare an abstract data type. :- type queue(T). % Declare some predicates which operate on the abstract data type. :- pred empty_queue(queue(T)). :- mode empty_queue(out) is det. :- mode empty_queue(in) is semidet. :- pred put(queue(T), T, queue(T)). :- mode put(in, in, out) is det. :- pred get(queue(T), T, queue(T)). :- mode get(in, out, out) is semidet. :- implementation. % Queues are implemented as lists. We need the `list' module % for the declaration of the type list(T), with its constructors % '[]'/0 % and '.'/2, and for the declaration of the predicate % list.append/3. :- import_module list. % Define the queue ADT. :- type queue(T) == list(T). % Define the exported predicates. empty_queue([]). put(Queue0, Elem, Queue) :- list.append(Queue0, [Elem], Queue). get([Elem | Queue], Elem, Queue). :- end_module queue.
As mentioned above, modules may contain submodules. There are two kinds of submodules, called nested submodules and separate submodules; the difference is that nested submodules are defined in the same source file as the containing module, whereas separate submodules are defined in separate source files. Implementations should support separate compilation of separate submodules.
A module may not contain more than one submodule with the same name.
Nested submodules within a module are delimited by matching ‘:- module’ and ‘:- end_module’ declarations. (Note that ‘:- end_module’ for nested submodules are mandatory, not optional, even if the nested submodule is the last thing in the source file. The module name in a ‘:- module’ or ‘:- end_module’ declaration for a nested submodule need not be fully qualified.) The sequence of items thus delimited is known as a submodule item sequence.
The interface and implementation parts of a nested submodule may be specified in two different submodule declarations. If a submodule item sequence includes an interface section, then it is a declaration of that submodule; if it includes an implementation section, then it is a definition of that submodule; and if includes both, then it is both declaration and definition.
It is an error to declare a submodule twice, or to define it twice. It is an error to define a submodule without declaring it. As mentioned earlier, it is an error to define a submodule in the interface section of its parent module.
If a submodule is declared but not explicitly defined, then there is an implicit definition with an empty implementation section for that submodule. This empty implementation section will result in an error if the interface section of a submodule contains any of the following:
Separate submodules are declared using ‘:- include_module Modules’ declarations. Each ‘:- include_module’ declaration specifies a comma-separated list of submodules.
:- include_module Module1, Module2, …, ModuleN.
The module names need not be fully qualified.
Each of the named submodules in an ‘:- include_module’ declaration must be defined in a separate source file. The mapping between module names and source file names is implementation-defined. The Melbourne Mercury implementation requires that
The source file of a separate submodule must contain the declaration (interface) and definition (implementation) of the submodule. It must start with a ‘:- module’ declaration containing the fully qualified module name, followed by the interface and (if necessary) implementation sections, and it may optionally end with a ‘:- end_module’ declaration. (The module name in the ‘:- end_module’ declaration need not be fully qualified.)
The semantics of separate submodules are identical to those of nested submodules. The procedure to transform a separate submodule into a nested submodule is as follows:
For example
:- module x. :- interface. :- include_module y. :- end_module x.
is equivalent to
:- module x. :- interface. :- module y. % interface section of module ‘y’ :- end_module y. :- implementation. :- module y. % implementation section of module ‘y’ :- end_module y. :- end_module x.
Any declarations in the parent module, including those in the parent module’s implementation section, are visible in the parent’s submodules, including indirect submodules (i.e. sub-submodules, etc.). Similarly, declarations in the interfaces of any modules imported using an ‘:- import_module’ or a ‘:- use_module’ in the parent module are visible in the parent’s submodules, including indirect submodules.
Declarations in a child module are not visible in the parent module, or in “sibling” modules (other children of the same parent), or in other unrelated modules unless the child is explicitly imported using an ‘:- import_module’ or ‘:- use_module’ declaration. It is an error to import a module without importing all of its parent modules.
Note that a submodule for which the ‘:- module’ or ‘:- include_module’ declaration occurs only in the implementation section of the parent module may only be imported or used by its parent module or by submodules of its parent module.
As mentioned previously, all ‘:- import_module’ and ‘:- use_module’ declarations must use fully qualified module names.
The current implementation of submodules has a couple of minor limitations.
Modules that interact with foreign libraries or services may require special initialisation before use. Such modules may include any number of ‘initialise’ directives in their implementation sections. An ‘initialise’ directive has the following form:
:- initialise initpredname/arity.
where the predicate initpredname must be declared with one of the following signatures:
:- pred initpredname(io::di, io::uo) is Det. :- impure pred initpredname is Det.
Det must be either det
or cc_multi
.
The effect of the ‘initialise’ declaration is to ensure that ‘initpredname/arity’ is invoked before the program’s ‘main/2’ predicate. Initialisation predicates within a module are executed in the order in which they are specified, although no order may be assumed between different modules or submodules. Initialisation predicates are only invoked after any initialisation required by the Mercury standard library.
If ‘initpredname/arity’ terminates with an uncaught exception then the program will immediately abort execution. In this circumstance, those predicates specified by other ‘initialise’ directives that have not yet been executed will not be executed, ‘main/2’ will not be executed, and no predicate specified in a ‘finalise’ directive will be executed.
‘initialize’ is also allowed as a synonym for ‘initialise’.
Modules that require special finalisation at program termination may include any number of ‘finalise’ directives in their implementation sections.
A ‘finalise’ directive has the following form:
:- finalise finalpredname/arity.
where the predicate ‘finalpredname/arity’ must be declared with one of the following signatures:
:- pred finalpredname(io::di, io::uo) is Det. :- impure pred finalpredname is Det
Det must be either det
or cc_multi
.
The effect of the ‘finalise’ declaration is to ensure that ‘finalpredname/arity’ is invoked after the program’s ‘main’ predicate. Finalisation predicates within a module are executed in the order in which they are specified, although no order may be assumed between different modules or submodules. Any finalisation required by the Mercury standard library will always occur after any finalisation predicates have been invoked.
If ‘finalpredname/arity’ terminates with an uncaught exception, then the program will immediately abort execution. No predicates specified by other ‘finalise’ directives that have not yet been executed will be executed. If the program’s ‘main/2’ predicate terminates with an uncaught exception, then no finalisation predicates will be executed.
‘finalize’ is also allowed as a synonym for ‘finalise’.
Certain special cases require a module to have one or more mutable (i.e. destructively updateable) variables, for example to hold the constraint store for a solver type.
A mutable variable is declared using the ‘mutable’ directive:
:- mutable(varname, vartype, initial_value, varinst, [attribute, …]).
This constructs a new mutable variable with access predicates that have the following signatures:
:- semipure pred get_varname(vartype::out(varinst)) is det. :- impure pred set_varname(vartype::in(varinst)) is det.
The initial value of varname is initial_value, which is set before the program’s ‘main/2’ predicate is executed.
The type vartype is not allowed to contain any type variables or have any type class constraints.
The inst varinst is not allowed to contain any inst variables.
It is also not allowed to be equivalent to,
or contain components that are equivalent to,
the builtin insts free
, unique
, mostly_unique
,
dead
(clobbered
)
or mostly_dead
(mostly_clobbered
).
The initial value of a mutable, initial_value, may be any Mercury expression with type vartype and inst varinst subject to the above restrictions. It may be impure or semipure.
The following attributes are supported:
This attribute specifies whether the implementation should generate code to undo the effects of ‘set_varname/1’ on backtracking (‘trailed’) or not (‘untrailed’). The default, in case none is specified, is ‘trailed’.
This attribute causes the compiler to also construct access predicates that have the following signatures:
:- pred get_varname(vartype::out(varinst), io::di, io::uo) is det. :- pred set_varname(vartype::in(varinst), io::di, io::uo) is det.
This attribute causes the compiler to construct only a ‘get’ access predicate, but not a ‘set’ access predicate. Since varname will always have the initial value given to it, the ‘get’ access predicate is pure; its signature will be:
:- pred get_varname(vartype::out(varinst)) is det.
The ‘constant’ attribute cannot be specified together with the ‘attach_to_io_state’ attribute (since they disagree on this signature). It also cannot be specified together with an explicit ‘trailed’ attribute.
The Melbourne Mercury compiler also supports the following attributes:
Allow foreign code to access the mutable variable in some implementation dependent manner. Lang must be a valid target language for this Mercury implementation. Name must be a valid identifier in that language. It is an error to specify more than one foreign name attribute for each language.
For the C backends,
this attribute allows foreign code to access the mutable variable
as an external variable called Name.
For the low-level C backend, e.g. the asm_fast grades,
the type of this variable will be MR_Word
.
For the high-level C backend, e.g. the hlc grades,
the type of this variable depends upon the Mercury type of the mutable.
For mutables of a Mercury primitive type,
the corresponding C type is given
by the mapping in C data passing conventions.
For mutables of any other type,
the corresponding C type will be MR_Word
.
This attribute is not currently implemented for the non-C backends.
This attribute allows a mutable to take on different values in each thread. When a child thread is spawned, it inherits all the values of thread-local mutables of the parent thread. Changing the value of a thread-local mutable does not affect its value in any other threads.
The ‘thread_local’ attribute cannot be specified together with either of the ‘trailed’ or ‘constant’ attributes.
It is an error for a ‘mutable’ directive to appear in the interface section of a module. The usual visibility rules for submodules apply to the mutable variable access predicates.
For the purposes of determining when mutables are assigned their initial values, the expression initial_value behaves as though it were a predicate specified in an ‘initialise’ directive.
:- initialise foo/2. :- mutable(bar, int, 561, ground, [untrailed]). :- initialise baz/2.
In the above example,
The effect of a mutable initial value expression terminating with an uncaught exception is also the same as though it were a predicate specified in an ‘initialise’ directive.
Previous: Module finalisation, Up: Modules [Contents]