meta_predicate/1 directive sometimes unexpectedly has no effect
While considering https://github.com/mthom/scryer-prolog/pull/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.
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!
I think the code that adds module specifiers isn't that advanced yet. It has a lot of quirks like this.
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?
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: https://github.com/mthom/scryer-prolog/discussions/2604#discussioncomment-10856631
UPDT: To be more precise you can take a look into expand_meta_predicate_subgoals/6 i think it is relevant :shrug: . The idea is to find goals like this maplist(maplist(maplist(foo, .... and correctly add module specifier to foo.
I second loader.pl as the relevant source! See especially expand_goal/3, which is applied to every goal being compiled via load_loop/2 → compile_term/2 → expand_terms_and_goals/2 → expand_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.