Calling a module-qualified predicate calls the wrong one (IMHO): What guarantees do I get through qualification?

I had opened an issue on this but I don’t understand Jan’s response at all (sorry Jan), so I thought I will just repeat it in the group to hope for some enlightenment.

I will just repeat what I have verbatim, see at the end for an explanatory image.

The problem:

Calling a module-qualified predicate whose module has not been loaded yet causes a predicate with the same predicate indicator from unrelated (but autoloaded?) module to be called instead.

I can’t see a reason why this would be the case.

For example:

Take the original swipl module ${swipl_install_dir}/lib/swipl/library/prolog_pack.pl which exports pack_install/1.

Modify the code for pack_install/1 code by adding a format/2 line just to see what is being called:

pack_install(Spec) :-
    format("***** Running original pack_install/1 ******"),
    pack_default_options(Spec, Pack, [], Options),
    pack_install(Pack, [pack(Pack)|Options]).

Copy prolog_pack.pl it into a new my_prolog_pack.pl (giving the module the name my_prolog_pack in the module declaration, too). This module also exports pack_install/1, modified as follows:

pack_install(Spec) :-
    format("***** Running replacement pack_install/1 ******"),
    pack_default_options(Spec, Pack, [], Options),
    pack_install(Pack, [pack(Pack)|Options]).

Now start swipl and run:

?- pack_install('foo').
***** Running original pack_install/1 ******

So far so good. A default package is autoloaded and run.

Restart swipl and run a qualified predicate call, qualified with the name of a not-available module. The predicate in the library module is called instead:

?- my_prolog_pack:pack_install('foo').
***** Running original pack_install/1 ******

That’s at least unexpected. What I would expect is an exception telling me in no uncertain terms that my setup is foobared (as I say, it’s always better to get an solid exception & halt than to get good radiation dose from a suddenly differently specced Therac-25)

Loading the module used in the above qualification first now:

?- use_module('my_prolog_pack.pl').
true.

The the qualification works properly:

?- my_prolog_pack:pack_install('foo').
***** Running replacement pack_install/1 ******
% Contacting server at https://www.swi-prolog.org/pack/query ... done
Warning: No registered pack matches "foo"
false.

Good!

The default is now taken from the replacement pack:

?- pack_install('foo').
***** Running replacement pack_install/1 ******
% Contacting server at https://www.swi-prolog.org/pack/query ... done
Warning: No registered pack matches "foo"
false.

Also good.

However, a predicate qualified with prolog_pack does not autoload the correct package and execute its predicate, it executes the predicate from the replacement package:

?- prolog_pack:pack_install('foo').
***** Running replacement pack_install/1 ******
% Contacting server at https://www.swi-prolog.org/pack/query ... done
Warning: No registered pack matches "foo"
false.

Note that both packs cannot coexist:

?- use_module(library('prolog_pack.pl')).
ERROR: import/1: No permission to import prolog_pack:pack_url_file/2 into user (already imported from my_prolog_pack)
ERROR: import/1: No permission to import prolog_pack:pack_rebuild/1 into user (already imported from my_prolog_pack)
ERROR: import/1: No permission to import prolog_pack:pack_remove/1 into user (already imported from my_prolog_pack)

The above is quite a wall of text, so here is an image with the three scenarios, whereby

  • I’m okay with the module resolution for the green “action boxes”.
  • I’m not okay with the module resolution for the orange “action boxes”. I would have expected exceptions instead of freewheeling dynamical resolution. (Why have module qualifications if they can be ignored by the system?)

This is late after your question but actually for me what Jan says makes sense, given what I understand about the Swi module system (i.e. not that much). Like Jan says in the github issue, if you make a call like the following:

?- new_module:foo(bar).

The call will fail, but a module named “new_module” will be created internally, so that when you query the names of modules, you will find new_module in it.

Here:

?- current_module(new_module).
false.

?- new_module:foo(bar).
ERROR: Unknown procedure: new_module:foo/1 (DWIM could not correct goal)

?- current_module(new_module).
true

This is certainly something to catch you by surprise and it sure seems surprising to me, but then again modules are a bit ad-hoc, in the sense that they are not part of FOL semantics, they are part of programming language semantics. And, like exceptions, say, or unit tests, it works the way it works.

1 Like