Skip to content
wangp edited this page Aug 6, 2017 · 1 revision

Mercury has a simple module system for organising code. The module system is hierarchical, so modules may have sub-modules, which in turn have sub-sub-modules, and so on.

We have seen that the source file of a module has this form:

:- module MODULENAME.

:- interface.

/* stuff goes here */

:- implementation.

/* stuff goes here */

:- end_module MODULENAME.

An :- interface. declaration indicates the start of the module's interface section: this section specifies the entities that are exported by this module, including types, predicate and function declarations, type classes, and so on. 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 sub-modules) and cannot be used by other modules. 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.

There are two ways to import entities from other modules into the current module:

:- import_module MODULES.

:- use_module MODULES.

where MODULES is a comma-separated list of fully-qualified module names. We recommend using a separate import declaration for each module.

Names of predicates, functions, constructors, etc., can be explicitly module qualified using the . operator, e.g. module.name or module.submodule.name. This is useful both for readability and for resolving name conflicts. Mercury's module system does not support renaming of entities so this is the only way of resolving otherwise ambiguous names.

Uses of entities imported using use_module must be [fully] module qualified. Entities imported using import_module may be used with or without module qualification.

Sub-modules

There are two kinds of sub-modules. Nested sub-modules are defined in the same source file as the containing module, whereas separate sub-modules are defined in separate source files.

Separate sub-modules

Separate sub-modules are declared in a containing module using a :- include_module MODULES declaration, where MODULES is a comma-separated list of modules. (Here, a module name does not need to be fully module qualified if the file name used for the module includes all its module qualifiers.)

If the :- include_module declaration occurs in the interface section of the containing module then the declared sub-modules may be imported by any module that can also import the containing module. That is, the sub-modules are part of the parent module's interface.

If the :- include_module declaration occurs in the implementation section of the containing module then the declared sub-modules may only be imported by the containing module or its sub-modules. This is useful for hiding implementation details.

Each declared sub-module is then defined in a separate source file. The source file should normally be named after the qualified or unqualified sub-module name, plus the .m suffix. For example, a module named 'foo.bar.baz' may be located in a source file named 'foo.bar.baz.m', 'bar.baz.m', or 'baz.m'.

The source file for a separate sub-module is the same as for top-level modules. It begins a :- module declaration, followed by interface and implementation sections, and optionally ends with a :- end_module declaration.

Nested sub-modules

Nested sub-modules behave identically to separate sub-modules but are defined in the same source file as the containing module, between matching :- module and :- end_module declarations. The :- end_module declarations are mandatory.

:- module parent.
:- interface.

/* interface section of parent module */

:- implementation.

:- module sub.
:- interface.

/* interface section of sub-module */

:- implementation.

/* implementation section of sub-module */

:- end_module sub.

/* resume implementation section of parent module */

:- end_module parent.

You may move the interface section of a nested sub-module into the interface section of the parent module, allowing the sub-module to be imported by any module that also imports the parent module. The implementation section of a nested sub-module remains in the implementation section of its parent module.

:- module parent.
:- interface.

:- module sub.
:- interface.

/* interface section of sub-module */

:- end_module sub.

:- implementation.

:- module sub.
:- implementation.

/* implementation section of sub-module */

:- end_module sub.

:- end_module parent.

Visibility rules for sub-modules

Any declarations in the parent module, including those in the parent module's implementation section, are visible in the parent's sub-modules, including indirect sub-modules (i.e. sub-sub-modules, etc.). Similarly, declarations in the interfaces of modules imported in the parent module are visible in the parent's sub-modules, including indirect sub-modules.

Declarations in a child module are not automatically visible in the parent module nor in other child modules of the same parent. The child module must be imported using an :- import_module or :- use_module declaration, as usual. To import a child module, you must also import its parent module.

Building multi-module programs

To build a multi-module program, you should use mmc --make. The basic usage is mmc --make program or mmc -m program where program is the name of the module containing the main/2 predicate. The result of the build process is an executable file or shell script called program or program.exe. Most intermediate files will be placed in a directory named Mercury, though .mh and .err files will be dumped in the current directory.

If you have named your source files in a way not expected by the compiler, or if the source files are not located in the current directory, you should first create a source file mapping using mmc -f SOURCE-FILES where SOURCE-FILES is a list of *.m files. This produces a file Mercury.modules listing the source file corresponding to each module name. Then you can use mmc --make as before.

You can pass -j NUM to perform up to NUM jobs concurrently in the build process. Typically you would set NUM to match the number of processor threads on your system, or thereabouts. (-j is not yet supported on Windows.)

You can pass --rebuild or -r to force mmc --make to recreate target files even if they already seem to be up to date. This can be useful when changing compilation options or changing compiler versions. You can also delete the Mercury directory.

Example of separate sub-modules

Build this with mmc --make sep.

%
% Place this in a file sep.m
%
:- module sep.
:- interface.

:- import_module io.

:- pred main(io::di, io::uo) is det.

:- implementation.

% Declare a separate sub-module.
:- include_module sep.sub.

% Sub-modules must be explicitly imported like any other module.
:- import_module sep.sub.

:- pred in_parent(io::di, io::uo) is det.

in_parent(!IO) :-
    write_string("Hello from the parent module!\n", !IO).

main(!IO) :-
    % Call a predicate declared in the sub-module.
    % Since we imported the sub-module using `import_module`
    % we can choose to module qualify the name, or not.
    sub.in_sub(!IO).
%
% Place this in a file sep.sub.m
%
:- module sep.sub.
:- interface.

% The 'io' module was imported in the parent module
% so it is not necessary to import it here.
% :- import_module io.

:- pred in_sub(io::di, io::uo) is det.

:- implementation.

in_sub(!IO) :-
    % Call a predicate declared in the parent module.
    in_parent(!IO),

    write_string("Hello from the separate sub-module!\n", !IO).

:- end_module sep.sub.

Example of nested sub-modules

Build this with mmc --make nested.

%
% Place this in a file nested.m
%
:- module nested.
:- interface.

:- import_module io.

:- pred main(io::di, io::uo) is det.

:- implementation.

%
% Here is a nested sub-module:
%
:- module sub.
:- interface.

% The 'io' module was imported in the parent module
% so it is not necessary to import it here.
% :- import_module io.

:- pred in_sub(io::di, io::uo) is det.

:- implementation.

in_sub(!IO) :-
    % Call a predicate declared in the parent module.
    in_parent(!IO),

    write_string("Hello from the nested sub-module!\n", !IO).

:- end_module sub.

%
% Back in the parent module:
%

% Sub-modules must be explicitly imported like any other module.
:- import_module nested.sub.

:- pred in_parent(io::di, io::uo) is det.

in_parent(!IO) :-
    write_string("Hello from the parent module!\n", !IO).

main(!IO) :-
    % Call a predicate declared in the sub-module.
    sub.in_sub(!IO).

:- end_module nested.