predicate_property(M:P) fails silently
I think predicate_property is supposed to fail with an instantiation error in both of these cases:
?- predicate_property(P,A).
error(instantiation_error,predicate_property/2).
?- predicate_property(M:P,A).
false.
Good point, thank you a lot!
I posted an implementation proposal in https://github.com/mthom/scryer-prolog/pull/2902/commits/ee6e34bd6749b7fad845bba3a3c3b93d4d6092ee. However, with this change, Scryer crashes on startup. https://github.com/mthom/scryer-prolog/pull/2902/commits/afc3aa253a631d52161640338553c42ce5a3c547 shows how to disable the branch that throws instantiation errors when only the module is a variable. This must be addressed before merging, since it may lead to wrong results or crashes. I would appreciate all input. Thank you a lot!
No, thank YOU! I'm incredibly lost in Prolog, and I don't even know how to ask the right questions. But at least errors provide some guidance when I'm doing something nonsensical!
This code is quite puzzling. I thought that built-ins are implemented in non-prolog (and don't have a "defining module" in their predicate properties).
Things I think I now understand:
- The "real" built-ins are named with atoms starting with
$(dollar sign). - All standard builtins and control constructs are actually implemented in prolog on top of these (even if a trivial wrapper like
fail :- '$fail'.) - Most standard builtins are in
builtins.pl. Some are inloader.pl. This shouldn't matter much since they are visible everywhere.
I don't know if there is any observable difference between a predicate called in the context of builtins vs loader? The only difference I can see is:
?- clause(loader:length(_,_),_).
error(permission_error(access,private_procedure,length/2),clause/2).
?- clause(builtins:length(_,_),_).
false.
There are actually a few builtins that are defined in Rust and not in Prolog without a $ intermediary. They currently don't appear in the documentation which is unfortunate, see #2030.
length/2 is actually from library(lists). It is loaded in the context of the module loader because it is imported there with use_module. That means it is also loaded by default in user (the default module), but none of it's predicates are visible. That is, length(A, B) doesn't work on startup, but lists:length(A,B) does work. builtins:length(A,B) doesn't work because library(builtins) doesn't import library(lists) at all, so none these predicates are visible there. As you may have noticed, it's often helpful to think of modules more as contexts than what you'd think of as modules in other languages.
Regarding terminology:
3.21 built-in predicate: A procedure whose execution is implemented by the processor (see 8).
8 Built-in predicates A built-in predicate is a procedure which is provided automatically by a standard-conforming processor.
There are actually a few builtins that are defined in Rust and not in Prolog without a
$intermediary. They currently don't appear in the documentation which is unfortunate, see #2030.
length/2is actually fromlibrary(lists). It is loaded in the context of the moduleloaderbecause it is imported there withuse_module.
Yep - that's why I chose it as an example.
It seems like an accident that builtins and loader are modules at all. These are the ways it seems to be observable:
- going out looking for trouble (as I did) by explicitly qualifying a term
- trying to create a module with these names
- (when implemented in the future) the output of
?- predicate_property(use_module(_), defined_in(M)).
Regarding terminology
User-defined procedures are organized into modules but built-in procedures aren't and are visible everywhere. It seems somewhat oxymoronic to even have a builtins module since built in predicates can be neither imported nor exported.
User-defined procedures are organized into modules
For user-defined procedures, the standard says:
A user-defined procedure is a sequence of (zero or more) clauses prepared for execution.
For user-defined procedures, the standard says:
A user-defined procedure is a sequence of (zero or more) clauses prepared for execution.
Predicates defined in builtins.pl and loader.pl fit that definition :-p
The key point is that modules are not mentioned in any of these definitions.
User defined procedures always belong to a module. Per 4.4.2 The module user:
A Prolog processor shall support a built-in module
user. User-defined procedures not defined in any particular module shall belong to the moduleuser.
Based on a few things, I think built-ins can't exist in a module in any meaningful sense. I have to read between the lines a little because no prolog processor implements the idea of a "module interface" and "module body" as different things:
- Per 6.3, every procedure that is neither a control construct nor a built-in predicate belongs to some module. (weakly implying that built-in predicates don't need to belong to any module)
- Per 6.2.6, The predicate indicator in a clause-term in module text may NOT be a built-in predicate or control construct.
- Per 6.2.4., 6.2.5. the built-in predicates may not be specified by the predicate indicators in directives, specifically
export/1,reexport/2,reexport/1,import/1,import/2. - Per 6.2.6, the head of a clause in module text can't match any built-in predicate indicator.
It seems un-useful, and arguably wrong, that builtins and loader are modules. I think it's fairly benign (except that builtins shows up in the docs whereas loader does not).
User defined procedures always belong to a module. Per 4.4.2 The module user:
A Prolog processor shall support a built-in module
user. User-defined procedures not defined in any particular module shall belong to the moduleuser.
With hindsight, I can see that this expectation was rooted in ISO/IEC 13211-2.
I think I should instead read the (:)/2 and predicate_property/2 predicates as an implementation-specific extension.
It would be nice to bootstrap :, but I don't even know if that's feasible!
I tried to understand your attempted fix in #2902, but I got stuck on strip_module/3, which is where I think the bug originates.
Its behavior differs between scryer and swipl (output var names changed for legibility):
:- initialization((strip_module(M:t,B,C),writeq(strip_module(M:t,B,C)),nl)).
In scryer, this prints strip_module(M:t,M,t)
In swipl, this prints strip_module(M:t,user,M:t)
When even less instantiated, scryer gives a concrete module again:
:- initialization((strip_module(A,B,C),writeq(strip_module(A,B,C)),nl)).
prints strip_module(A,user,A) in both scryer and swipl.