-
Notifications
You must be signed in to change notification settings - Fork 55
Modules
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.
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 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 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.
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.
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.
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.
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.