What is the best practice for "namespacing" imports?

I’ve just learned that Prolog does not have the notion of namespaces. This potentially makes importing predicates from modules confusing. Is there a best practice among Prolog programmers for namespacing imports? Two hacks come to mind:

Prepending every predicate with its parent module’s name
For example, math_add/2 for an add/2 in math module. This can get cumbersome fast not just because the long names but when maintenance is needed such as module refactor / name changing.

Keep each module small
This seems like a more sustainable path. If an importing module is small as well as the exporting ones, the need to namespace becomes less important.

I’m a Prolog noob who have just fallen in love with the language, but yet to build a real world app. I have experience in a few “neighbor” languages like Erlang, Ocaml, and Lisp, so I was expecting Prolog to have had a similar namespace system as Erlang (module:predicate/arity_num) since the latter was heavily inspired by the first.

I’m confused.

You are correct that Prolog does not have a namespace, i.e. namespace_a.namespace_b.namespace_c.predicate, but then you imply it does not have a module system like module:predicate/arity_num as you note in Erlang. But Prolog does have a module system like module:predicate/arity_num, see: Defining a Module. (as I don’t use Erlang I don’t know if module:predicate/arity_num is different from Prolog).

HTH

1 Like

Hi @EricGT, thanks for chiming in. I meant that when importing predicates from a parent module, what is the best practice for working around the lack of namespaces?

:- use_modules(foo).

% I can't do this
% foo:add(R, 1, 2).
% foo:minus(R, 10, 2).

% So This is my first resort
foo_add(R, 1, 2).
foo_minus(R, 10, 2).

I was wondering if there is a best practice most seasoned Prolog programmers use in this scenario.

I don’t do anything special, but I tend to list out all the imported predicates rather than import everything. (If you’ve done Python programming, it’s like the difference between from foo import bar, zot vs from foo import *. You can sort-of simulate import foo; foo.bar() but it doesn’t really fit with the module philosophy). Note that Prolog allows importing with another name, so if there’s a name clash, it’s easily taken care of.
I also turn off automatic loading, but there are many who disagree with me – my attitude is that I prefer maximum understanding from the code using as few tools as possible. Having said that, there are good cross-referencing tools, so if you don’t know where a predicate is defined, it’s easy to click on it and find out.

1 Like

I did not know that we could alias an import, so this is great! Thanks for your answer.

So here is a very simple example. Seems you might have figured this out based on the answer by Peter. This is also here for others looking to learn modules.

Directory: C:/Users/Groot

File: foo.pl

:- module(foo,
    [
        add/3,
        minus/3
    ]).

add(R,A,B) :-
    R is A + B.

minus(R,A,B) :-
    R is A - B.

File: bar.pl

:- module(bar,
    [
        test_01/0,
        test_02/0
    ]).

:- use_module(foo).

test_01 :-
    add(R,1,2),
    format('Result: ~w~n',[R]).

test_02 :-
    minus(R,2,1),
    format('Result: ~w~n',[R]).

Example run:

?- working_directory(_,'C:/Users/Groot').
true.

?- [foo].
true.

?- [bar].
true.

?- test_01.
Result: 3
true.

?- test_02.
Result: 1
true.

HTH


Note: This is not the best way to do tests but it is more understandable for a beginner. See: Prolog Unit Tests

The time line gets difficult. SWI-Prolog (and a few others, such as YAP, Ciao and SICStus) got there module system from Quintus. Wikipedia says Erlang was started in 1986, which is pretty close to when Quintus got modules. So yes, in these module systems a fully qualified predicate indicator is module:name/arity and calls take the form module:name(arg1, arg2, …).

Each module is headed by a :- module(Name, Exports)., splitting its predicates in exported and private predicates. use_module/1 imports all exported predicates from the target and use_module/2 only the listed ones.

Using module:name(arg1, …) in your code is not what the developers had in mind. You can conclude that from two observations:

  • The module:name(arg1, …) construct can call both exported and private predicates. The breaks the most important aspect of modules: distinguish the interface (exported preds) from the private ones, so you can do whatever you want to the implementation as long as you stick to the interface without harming the overall application.
  • The module:name(arg1, …) changes the module context to module, i.e., it acts as if the goal is called from inside module. This makes a difference if name(…) is a meta predicate, a predicate calling a goals passed in one or more of the arguments. Think of findall/3, maplist/3, etc. If we call apply:maplist(mypred, List1, List2) it will call apply:mypred/2 rather than mypred/2 in the module from which I make this call.

In other words, qualified goals (module:name(arg1, …)) are intended for debugging and testing, not for production code.

So, how must we deal with this? One way as you suggest is to have fairly small modules (or more precisely, modules with a small interface). This reduces the probability of conflicts when using use_module/1. Another is @peter.ludemann’s suggestion to use use_module/2, possibly with the “… as myname” in case of a conflict (added to SWI-Prolog after discussion with Vitor Santos Costa from YAP).

The third option is the common practice in most of the libraries developed in the Quintus descendant systems. This uses a short prefix for the interface, so you get e.g. ord_disjoint/2, ord_subset/2, etc. Using this type of naming the risk for conflicts when using use_module/2 or even relying on autoloading is pretty small. Note that relying on autoloading gets you pretty much in the same space as C, where you have static (private) functions and common functions that all live in the same namespace (in C the default is common, in Prolog private). The C way to live with that is similar, which is why all SWI-Prolog foreign symbols are called PL_*, X11 calls everything X*, etc.

Is this bad? I don’t know. The module names live in a flat namespace. This requires using module names that are rather long, typically including some package prefix, e.g., http_dispatch, http_server, etc. Shorter names can only be used for old common libraries such as lists. I’ve seen too many packages having a utils module. You can’t load two of these packages into the same Prolog instance. Well, actually you can as you can use load_files/2 using the module(Name) option to load a module into a different module than the one mentioned in the header. If your code uses utils:... it still breaks :frowning:

A more modern solution would be to only allow module:name(…) calls to exported predicates and not switch the calling context. This probably should be combined with a :- use_module(Path) as Name, such that we can use Name:… to refer to this module regardless of the module name in Path. Logtalk has solved this stuff in a more modern way. Possibly the biggest added value is that it is what most people coming from other recent languages expect.

Migrating to that would require renaming all library modules and their predicates to reach at a coherent system. Is ord_subtract(All, Subtract, Remain) better than ordsets:subtract(All, Subtract, Remain)? It gets value if we have different implementations for the same interface. That is hard to do elegantly using this module system.

3 Likes