Skip to content
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

meta_predicate/1 directive sometimes unexpectedly has no effect #2745

Open
triska opened this issue Jan 2, 2025 · 5 comments
Open

meta_predicate/1 directive sometimes unexpectedly has no effect #2745

triska opened this issue Jan 2, 2025 · 5 comments

Comments

@triska
Copy link
Contributor

triska commented Jan 2, 2025

While considering #2738, I ran into a case that came as a surprise to me because the meta_predicate/1 directive unexpectedly has no effect.

As an example, consider mycall/1 declared as a meta-predicate, with its single argument denoting a goal that is meant to be invoked verbatim (i.e., without adding arguments), and p/0 defined in total as:

:- meta_predicate(mycall(0)).

:- dynamic(p/0).

p :-
   mycall(t),
   (   X = t ; X = t ),
   mycall(X).

Let's look at the listing of p/0:

?- use_module(library(format)).
   true.
?- listing(p/0).
p :-
   mycall(user:t),
   (  A=t
   ;  A=t
   ),
   mycall(A).

We see that the user: module prefix is added to the argument of the first mycall/1 goal, as expected. But for the second mycall/1 in the clause body, no module prefix is added! This is unexpected; I expected the second goal to read mycall(user:A). Note that the goal (i.e., A) is again t, so we expect both invocations of mycall/1 to be equivalent and the module prefix therefore present in both cases identically.

@triska
Copy link
Contributor Author

triska commented Jan 2, 2025

As an addendum, what I actually wanted to test in this case is whether the module prefix is not added in cases where it can be rightfully omitted. For instance, consider the well-known definition of maplist/2:

:- meta_predicate(maplist(1, ?)).

maplist(_, []).
maplist(G_1, [L|Ls]) :-
        call(G_1, L),
        maplist(G_1, Ls).

In this case, even though call/2 is a meta-predicate, there is no need to add a module prefix for G1, i.e., no need to turn the first goal into call(user:G1, L). Why? Because maplist/2 itself is declared as a meta-predicate where the first argument is a meta-goal, and that argument G1, which is only passed through to call/2, will therefore already have a module prefix attached which stems from a place where maplist/2 itself was used as a goal. For the same reason, the module prefix also need not be added in the (recursive) last goal of the clause body. Inserting a module prefix here would lead to a linear "pile-up" of useless module qualifiers, because the innermost (already present) qualifier would eventually override all these outer qualifiers in any case.

That is what I wanted to test, and it appeared to work, but only because Scryer apparently never adds the module qualifier at all in such cases, hence the issue. If anyone has any feedback on this point, please go ahead. Thank you a lot!

@hurufu
Copy link
Contributor

hurufu commented Jan 2, 2025

I think the code that adds module specifiers isn't that advanced yet. It has a lot of quirks like this.

@adri326
Copy link
Contributor

adri326 commented Jan 3, 2025

I couldn't find where the code for adding module specifiers lives in the project (which is currently blocking me on #2738 to cleanly let strip_module/3 default to the context module instead of the topmost loaded module), where can I find it?

@hurufu
Copy link
Contributor

hurufu commented Jan 3, 2025

It is done in loader.pl if I'm not missing anything. Here is a call graph if it can help you with reading it: #2604 (comment)

UPDT: To be more precise you can take a look into expand_meta_predicate_subgoals/6 i think it is relevant 🤷 . The idea is to find goals like this maplist(maplist(maplist(foo, .... and correctly add module specifier to foo.

@triska
Copy link
Contributor Author

triska commented Jan 3, 2025

I second loader.pl as the relevant source! See especially expand_goal/3, which is applied to every goal being compiled via load_loop/2compile_term/2expand_terms_and_goals/2expand_term_goals/2.

For example, with the above definition of maplist/2 and pertaining meta_predicate/1 declaration, we get:

?- loader:expand_goal(maplist(goal, []), test, G).
   G = maplist(test:goal,[]).

prolog_load_context/2 (and shorter: load_context/2) seems to be the key to obtaining the context module that is being loaded.

For example, with m.pl comprising:

:- module(m, []).

:- use_module(library(format)).

:- initialization((prolog_load_context(module, M),portray_clause(M))).

we get:

$ scryer-prolog m.pl
m.

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

No branches or pull requests

3 participants