-
Notifications
You must be signed in to change notification settings - Fork 134
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
Fix dcgs using call(M:Pred) when M was left unassigned #2738
Conversation
Thank you a lot for working on this! I think your first approach had the right intuition: The fix seems better placed in Example, for the case of no context module, i.e., used directly on the toplevel: ?- strip_module(G0, M, G). G0 = G, unsound, unexpected. M = user, G0 = G. % expected |
Thank you a lot! Could you please consider fusing the first two commits, since the first commit in this sequence is no longer needed? |
I'm indeed happier with that solution: ?- strip_module(G, M, P).
G = P, M = user.
?- strip_module(hello, user, P).
P = hello.
?- default_module(Default).
Default = user. I've introduced |
911d536
to
f032eaa
Compare
(squashed) |
For tests, please consider a test case that uses a module other than :- module(m, [f/1]). f(M) :- strip_module(goal, M, goal). We expect: $ scryer-prolog m.pl ?- f(M). M = m. |
There are now still changes to |
f032eaa
to
99b1571
Compare
Shouldn't it return |
My understanding of this: A goal Whether the indicated predicate stems from a library seems to make no difference regarding this specific point (i.e., regarding context modules). For instance, if the goal Are there any other opinions on this, and corrections on this? (@UWN please?) As an aside, |
Hmm, that would mean that we would now have to differentiate between atoms that appear within a module and atoms that appear outside of it? Right now if we have a module that exports an atom, then it's equivalent to other atoms: :- module(bello, [bello_to_you/1]).
bello_to_you(X) :-
X = bello. Then the following query should either fail (if both atoms are considered distinct), or ?- bello:bello_to_you(B), B = bello, strip_module(B, M1, _), strip_module(bello, M2, _). Otherwise it doesn't make much sense to me that two values which unify would behave differently |
No, definitely not. An atom is always the same atom.
Yes, absolutely, this is how it should be.
Via the In your example, the predicate used by The arguments of goals are not affected in any way by In the example you gave, |
Already now, Scryer Prolog must be in a sense aware of the compilation unit (module) it currently compiles or runs, in order to resolve calls correctly. I believe it is that context module that |
Regarding the last post, it just occurred to me that it may be possible to resolve this at least partly by declaring For instance, if we simulate this effect with :- module(m, [f/1]). :- meta_predicate(custom_strip_module(0, ?, ?)). custom_strip_module(Goal0, M, Goal) :- strip_module(Goal0, M, Goal). f(M) :- custom_strip_module(goal, M, goal). Then it already works as expected also with your branch @adri326! ?- f(M). M = m. What else is needed? Let's consider how |
Oh, I see what you mean. I think that
In all three of these scenarios, the current behavior of Doing this would ensure that no funniness arises when a module calls a meta-predicate like :- module(game_loader, [load_game/1]).
:- use_module(library(dcgs)).
game_dcg(Game) --> Game.
load_game(Game) :- phrase(game_dcg(Game), "This is just an example"). If But if the module resolution happens when Right now this is largely the way in which this works, although I've found some edge cases in testing, which I would like to address in another issue. I think that we should then document how |
Hmm, looks like metapredicates are meant to default to adding the calling module as a prefix, even though the predicate passed as argument might originate from another module. This is kind of necessary for It does mean that there is a bug in module resolution. Given the following module: :- module(module_resolution, [get_module/2, some_predicate/2]).
:- meta_predicate(get_module(:, ?)).
get_module(Pred, M) :- strip_module(Pred, M, _).
some_predicate(X, Y) :- X is Y + 1. The query from within the |
You consider adding meta-predicate declaration with |
Let's consider this concrete use of Line 262 in d57f871
This occurs during goal expansion, when a specific module (such as What we expect from this use of Note that atoms are not ever associated with any module in Scryer Prolog: Atoms and functors, and in fact all terms, remain the same over module boundaries. I mention this because there are Prolog systems that implement so-called functor based module systems where two terms may have the exact same shape and appearance yet be distinct because they stem from different modules. This is not the case in Scryer Prolog. All that |
I believe the current behaviour is correct. Any goal of the form It is this mechanism that makes it possible to explicitly indicate the intended module of a goal, even in situations where the goal is later qualified (automatically) with further prefices. For instance, within module To make discussing such issues easier: We can describe expected and unexpected behaviour of Scryer Prolog, via toplevel interactions. For instance, to make the case above more compact and also testable, I could say: ?- module_resolution:get_module(some_predicate, M). M = module_resolution. This indicates the expected behaviour. I could add further answers, and also add the qualifier ?- module_resolution:get_module(some_predicate, M). M = module_resolution. M = user, unexpected. This shows what we expect, and also what we do not expect. |
Regarding your comment on #2745 @triska, this does seem to confirm my worry: there currently is no code that is able to retrieve the current context at runtime, and I don't want to introduce more complexity to term/goal expansion by making a custom case. I guess that making If I do that then should the |
It may well be that I think it should be statically decidable to know in which module an unqualified goal is meant to be called, at least as an "approximation" in the sense that the outermost module prefix should be statically deducible: It is either So, I think that |
Or, phrased differently: What if |
As I have it, it currently essentially calls But the question I still have is when Honestly I wouldn't bikeshed on this too much. I would instead vouch on having good error messages around this subject, like a hint to add the prefix if the predicate is found in a loaded module. |
The suggestion is that |
To clarify: The purpose of everything here is only to add the prefix of the context module, i.e., the name of the module that is being compiled. That is what every goal within the module implicitly refers to, it is the implicit outermost module qualifier of every goal that occurs in the module. A |
As an example, let's consider :- module(m, []). :- use_module(library(dcgs)). t --> "hello". With these definitions, I get: $ scryer-prolog m.pl ?- m:phrase(t, Cs). Cs = "hello". I would say this works as expected: Note that even though |
Specifically, I suggest the following slightly changed definition of strip_module(Goal, M, G) :- '$strip_module'(Goal, MQ, G), ( MQ = specified(M) -> true ; MQ = unspecified, load_context(M) ). What do you think? |
As I hinted before, my current implementation is very close to doing that already, so I have no issue using some of the already-defined predicates (that I didn't know of a few days ago). The only worry is that |
Perfect, thank you a lot! This looks very good! |
3573f06
to
1ad88fb
Compare
There is one small change that needs to be done to |
1ad88fb
to
e89701c
Compare
337b77d
to
42df377
Compare
… strip_module/3 This fixes mthom#2725, by making it so that `strip_module(Pred, M, P), call(M:P)` doesn't throw an `instanciation_error` when `Pred` isn't in the form `module:predicate`. Now, `strip_module(hello, M, P)` will call `load_context(M)`, which unifies `M` with the topmost module (or `user`). Two new test cases are added: issue2725.pl, which tests the minimal case id(X) --> X. and the strip_module(P, M, _), call(M:P) scenario, and module_resolution, which tests the behavior of strip_module in a few scenarios.
42df377
to
b76bdd7
Compare
Little bit of cleanup now that #2756 is merged, this PR should now be ready to merge :) |
Fixes #2725.
phrase(GRBody, _, _)
first callsstrip_module(GRBody, M, _)
to splitGRBody
into the module and the predicate, but then callscall(M:GRBody3)
to prepend the module again.However, when there is no module, then
strip_module
doesn't assign a value toM
.I first tried to fix this by making it so thatstrip_module(predicate, M, Pred)
assigns[]
toM
, but that seems to have broken several pieces of code that instead check whether or notM
was assigned.Right now I have defaulted to just handle the result ofstrip_module
inphrase
instead, but I feel like it would be much cleaner to allowstrip_module
to assign a value toM
and to work both ways.call
could then seamlessly work with it.As per the suggestions of Markus,strip_module/3
now unifies the second argument with the default module,user
.I did this by introducing a new predicate,
default_module/1
, which does this unification, using the same logic asmachine.quantification_to_module_name(ModuleQuantification::Unspecified)
.With this PR,
strip_module/3
now callsload_context(M)
in theunspecified
case.This lets us remove fixes around the previous behavior (namely some uses of
strip_subst_module/4
).A couple of test cases validate that
strip_module/3
and DCGs now work as expected.