5.7 Meta-Predicates in Modules

As indicated in the introduction, the problem with a predicate based module system lies in the difficulty to find the correct predicate from a Prolog term. The predicate `solution(Solution)' can exist in more than one module, but `assert(solution(4))' in some module is supposed to refer to the correct version of solution/1.

Various approaches are possible to solve this problem. One is to add an extra argument to all predicates (e.g. `assert(Module, Term)'). Another is to tag the term somehow to indicate which module is desired (e.g. `assert(Module:Term)'). Both approaches are not very attractive as they make the user responsible for choosing the correct module, inviting unclear programming by asserting in other modules. The predicate assert/1 is supposed to assert in the module it is called from and should do so without being told explicitly. For this reason, the notion context module has been introduced.

5.7.1 Definition and Context Module

Each predicate of the program is assigned a module, called its definition module. The definition module of a predicate is always the module in which the predicate was originally defined. Each active goal in the Prolog system has a context module assigned to it.

The context module is used to find predicates from a Prolog term. By default, this module is the definition module of the predicate running the goal. For meta-predicates however, this is the context module of the goal that invoked them. We call this module_transparent in SWI-Prolog. In the `using maplist' example above, the predicate maplist/3 is declared module_transparent. This implies the context module remains extend, the context module of add_extension/3. This way maplist/3 can decide to call extend_atom in module extend rather than in its own definition module.

All built-in predicates that refer to predicates via a Prolog term are declared module_transparent. Below is the code defining maplist.

:- module(maplist, maplist/3).

:- module_transparent maplist/3.

%       maplist(+Goal, +List1, ?List2)
%       True if Goal can successfully be applied to all successive pairs
%       of elements of List1 and List2.

maplist(_, [], []).
maplist(Goal, [Elem1|Tail1], [Elem2|Tail2]) :-
        apply(Goal, [Elem1, Elem2]), 
        maplist(Goal, Tail1, Tail2).

5.7.2 Overruling Module Boundaries

The mechanism above is sufficient to create an acceptable module system. There are however cases in which we would like to be able to overrule this schema and explicitly call a predicate in some module or assert explicitly in some module. The first is useful to invoke goals in some module from the user's top-level or to implement a object-oriented system (see above). The latter is useful to create and modify dynamic modules (see section 5.8).

For this purpose, the reserved term :/2 has been introduced. All built-in predicates that transform a term into a predicate reference will check whether this term is of the form `<Module>:<Term>' . If so, the predicate is searched for in Module instead of the goal's context module. The : operator may be nested, in which case the inner-most module is used.

The special calling construct <Module>:<Goal> pretends Goal is called from Module instead of the context module. Examples:

?- assert(world:done).  % asserts done/0 into module world
?- world:assert(done).  % the same
?- world:done.          % calls done/0 in module world