Skip to content

Document module qualification #2959

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open

Document module qualification #2959

wants to merge 3 commits into from

Conversation

rotu
Copy link
Contributor

@rotu rotu commented May 15, 2025

I don't know where else to document :/2 and I don't understand it well enough, but someone's gotta do it.

@rotu
Copy link
Contributor Author

rotu commented May 15, 2025

This is a very subtle part of the language and was omitted from #2030 (native builtins missing documentation). Heck, I'm not even sure whether it's correct to call it a "procedure", a "control construct" or something else entirely.

It's also confusing to me where the definition of call/1 ends and :/2 begins.

Please review this carefully for correctness.

@rotu rotu requested a review from triska May 15, 2025 23:20
@UWN
Copy link

UWN commented May 16, 2025

Reject

@rotu
Copy link
Contributor Author

rotu commented May 16, 2025

Reject

Please recommend alternate documentation for this language feature. I may not have done it justice but it's important enough to document.

@rotu
Copy link
Contributor Author

rotu commented Jun 16, 2025

@triska, @UWN, could you please re-review.

I put this in builtins by analogy with #2265. But maybe it belongs in loader.pl since (:)/2 is not in ISO General Core.

For background, SICStus documents the (:)/2 functor thusly:

The following construct is meaningful in the context of modules (see ref-mod), meaning “P is true in the context of the M module”:
M:P

@ocharles
Copy link

@UWN Could you try and be a little more constructive and less passive aggressive? To any one watching this repository it just looks extremely hostile to contribute.

@UWN
Copy link

UWN commented Jun 16, 2025

Please refer to the past useless conversations. That is just trolling.

@rotu
Copy link
Contributor Author

rotu commented Jun 16, 2025

Could you try and be a little more constructive and less passive aggressive? To any one watching this repository it just looks extremely hostile to contribute.

Thank you for saying something. That's the way it feels sometimes.

Please refer to the past useless conversations

There have been plenty of conversations which may seem useless to you but I've learned much from (including many I have not been a participant in but read after the fact!). I've tried to be as humble as possible, even though learning Prolog is extremely frustrating.

If it's not worth your time to explain why you oppose my wording, then please write your own version of this PR so we can all learn.

@hurufu
Copy link
Contributor

hurufu commented Jun 16, 2025

I'm against "trivial" documentations. Like is commonly found in different kinds of projects: "This is a constructor. It constructs an object" – it is just silly.

Also users should not be encouraged in any way to use (:)/2. Ideally they shouldn't need to be aware that it exists. If you happen to (rarely) need explicitly qualified predicate it is much better to use this:

:- meta_predicate(goal_qualified_2(2,-)).
goal_qualified_2(G_2, G_2).

IMHO bad documentation is worse then no documentation. For example I have couple of years of experience with Prolog and I'm not qualified (pun intended) to write a good documentation for how module system works – but I've spent quite a while reading it. Just adding comments here and there doesn't help the project. There are plenty of other issues that must be fixed.

@rotu
Copy link
Contributor Author

rotu commented Jun 16, 2025

@hurufu Then maybe :/2 should not be exposed at all or should be documented as “do not use”.

I agree that trivial documentation is bad. Documenting a symbol in searchable words, or a language feature not elsewhere documented is not trivial.

@rotu
Copy link
Contributor Author

rotu commented Jun 16, 2025

Also users should not be encouraged in any way to use (:)/2. Ideally they shouldn't need to be aware that it exists. If you happen to (rarely) need explicitly qualified predicate it is much better to use this:

:- meta_predicate(goal_qualified_2(2,-)).
goal_qualified_2(G_2, G_2).

I'm not understanding. How would you use your meta-predicate to write the equivalent of m1:foo, m2:foo? Or call user:goal_expansion from module code?


Even if (:)/2 only occurs as the principal functor in expanded subgoals and library code, it's probably worth documenting so you can understand the code you didn't write but nevertheless have to debug!

@hurufu
Copy link
Contributor

hurufu commented Jun 17, 2025

It depends on why do you need it and what do you want to do with your qualified predicates. Here is one way of doing it.

Utilities module (file q.pl)

:- module(q, [goal_qualified_1/2]).
:- meta_predicate(goal_qualified_1(1,-)).
goal_qualified_1(G_1, G_1).

Let's say you have two modules a and b and they export predicate with the same predicate indicator foo/1. Then you can add a special helper predicate to take qualified predicate names (files a.pl and b.pl).

:- module(a, [a_foo/1]).
:- use_module(q).

foo(42).

a_foo(Foo) :-
    goal_qualified_1(foo, Foo).
:- module(b, [b_foo/1]).
:- use_module(q).

foo(hello).

b_foo(Foo) :-
    goal_qualified_1(foo, Foo).

Finally in the main program:

:- use_module(a).
:- use_module(b).

run([A,B]) :-
    a_foo(AFoo),
    b_foo(BFoo),
    call(AFoo, A),
    call(BFoo, B).

Call this program:

?- run(A).
   A = [42,hello].
?- 

@UWN
Copy link

UWN commented Jun 17, 2025

:- module(q, [goal_qualified_1/2]).

...

run([A,B]) :-
    a_foo(AFoo),
    b_foo(BFoo),
    call(AFoo, A),
    call(BFoo, B).

This is a clean approach when one needs to refer to a predicate in an entirely different context, like when using attributed variables. But to resolve a name clash it seems better to add just interface modules that perform the renaming directly.

@rotu rotu marked this pull request as draft June 18, 2025 04:29
@hurufu
Copy link
Contributor

hurufu commented Jun 18, 2025

Just for completeness and for future reference – usage example of interface predicates (at least my understanding):

Modules a.pl and b.pl same as in previous post.

Next add two more renaming modules:

:- module(renamed_a, [a_foo/1]).
:- use_module(a).
a_foo(X) :- foo(X).
:- module(renamed_b, [b_foo/1]).
:- use_module(b).
b_foo(X) :- foo(X).

You can then import renamed_a and renamed_b and use those predicates directly:

?- a_foo(A), b_foo(B).
   A = 42, B = hello.

The main benefit is that renamed predicates behave as regular predicates, they don't have strange functor (:)/2, you can do a lot with them and you wouldn't have any corner cases.

BONUS: You can merge clauses – this can be useful in knowledge bases. Just define facade module:

:- module(m, [foo/1]).
:- use_module(renamed_a).
:- use_module(renamed_b).
foo(A) :- a_foo(A).
foo(A) :- b_foo(A).

Example query:

?- foo(A).
   A = 42
;  A = hello.

UPDATE: Obviously I know about discontiguous and multifile predicates, this is here just an example.

@rotu
Copy link
Contributor Author

rotu commented Jun 18, 2025

Modifying the original module to rename the predicates apart or introducing new intermediate modules does not replace the use of :.

It depends on why do you need it and what do you want to do with your qualified predicates. Here is one way of doing it.

:- module(q, [goal_qualified_1/2]).
:- meta_predicate(goal_qualified_1(1,-)).
goal_qualified_1(G_1, G_1).
% ...

:- module(a, [a_foo/1]).
a_foo(Foo) :-
    goal_qualified_1(foo, Foo).
% ...

a_foo(AFoo),

In this example, AFoo gets unified to a:foo. So you've just buried use of module-qualification, not removed the module-qualification operator.

It's also probably a corner-case for now, but also predicate_property(a_foo(_),P) will not generally be the same as predicate_property(a:foo(_),P) (which may or may not be important).


Wrapping a predicate with helper modules seems needlessly indirect.

Contrast:

:- module(renamed_a, [a_foo/1]).
:- use_module(a).
a_foo(X) :- foo(X).

% ...
:- module(renamed_b, [b_foo/1]).
:- use_module(b).
b_foo(X) :- foo(X).

% ...
:- module(m, [foo/1]).
:- use_module(renamed_a).
:- use_module(renamed_b).

foo(X) :- a_foo(X).
foo(X) :- b_foo(X).

Versus explicit qualification in the consuming module:

:- module(m, [foo/1]).
:- use_module(a). % note: for some reason this breaks with use_module(a, [])
:- use_module(b).

a_foo(X) :- a:call(foo(X)).
b_foo(X) :- b:call(foo(X)).

foo(X) :- a_foo(X).
foo(X) :- b_foo(X).

Or just qualifying the predicate without intermediaries:

:- module(m, [foo/1]).
:- use_module(a). % note: for some reason this breaks with use_module(a, [])
:- use_module(b).

foo(X) :- a:foo(X).
foo(X) :- b:foo(X).

@hurufu
Copy link
Contributor

hurufu commented Jun 18, 2025

Have you tried your code? Does it work?

UPDATE: Another question do really think adding module names has any merit? Consider C++ language they (C++ programmers) almost universally use explicit namespaces like std::vector. What's the point?! How is it better from std_vector? I think namespaces and explicit module specification is overrated.

For me using namespaces (aka explicit module names) should be rarely the case, and shall be used only to avoid name clash or in some special cases. Regardless of the language.

@rotu rotu marked this pull request as ready for review June 18, 2025 19:27
@hurufu
Copy link
Contributor

hurufu commented Jun 18, 2025

In Prolog predicates such a a:foo(X) need also be constructed. First you need to place predicate (:)/2 on the stack, then atom a, then you need to construct foo(X) and at the very end you cal this thing. So IMHO and I haven't tested this, but a:foo(X) can actually be slower then a_foo(X).

@UWN
Copy link

UWN commented Jun 18, 2025

@hurufu, the advantage of using extra intermediary modules for renaming shows when the predicates to be renamed have a meta_predicate declaration. With explicit qualification, the meta-arguments would inherit that explicit qualification.

@rotu
Copy link
Contributor Author

rotu commented Jun 18, 2025

Have you tried your code? Does it work?

Oops! No, something weird is going on. It should work but for some reason, the explicit [] import list is interfering with the foo/1 predicate from being created in the a module.

Stranger still, a:current_predicate(X) seems to ignore the qualifying module a and calls current_predicate in the context of module user (EDIT: wrote up as #2984). I think the module-qualification operator is working differently from both SICStus and from ISO-13211-2.

In Prolog predicates such as a:foo(X) need also be constructed.

True. But (1) that's done for meta-arguments (2) there is lots of opportunity here for optimization. I expect in most cases, the qualifying module will be known at compile time and can be inlined.

UPDATE: Another question do really think adding module names has any merit? Consider C++ language they (C++ programmers) almost universally use explicit namespaces like std::vector. What's the point?! How is it better from std_vector? I think namespaces and explicit module specification is overrated.

For me using namespaces (aka explicit module names) should be rarely the case, and shall be used only to avoid name clash or in some special cases. Regardless of the language.

Yes, I think module names have tons of merit. I'd rather see the : operator than have to deal with C++-style identifier mangling! I agree that manual module-qualification should be rarely necessary and having to use it can indicate a code smell.

Another place where explicit module qualification can be useful is helper predicates (especially with dcgs for some complex grammars). Even if these are usually encapsulated inside some well-defined interface, it makes sense to module-qualify for unit testing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants