This relates to Autoload and qsave (swipl -c) don't go together. I think I made a lot of progress addressing this issue. Here is the status and remaining problems:
Autoload handling
Prototype implementation (on git swipl-devel
, master
)
-
Added
:- autoload(File)
and:- autoload(File, Imports)
. Syntax
and semantics are a subset of:- use_module
, except no loading
happens (see below for exceptions). The autoload/2 variant defines
the specified predicates such that a local definition is prevented.
Both directives add some data to the module that defines autoloading
depending on theautoload
flag. This flag has these values:-
false
Predicates are never auto-loaded. If predicates
have been imported before using autoload/1,2, load the
referenced files immediately using use_module/1,2. Subsequent autoload/1,2 is mapped directly to use_module/1,2 Note that most of the development utilities such as listing/1
now have to be explictly imported before they can be used
at the toplevel. -
explicit
Do not autoload from autoload libraries, but do
use lazy loading for predicates imported using autoload/1,2. This
is semantically the same asfalse
, but preserves lazy loading. -
user
Asfalse
, but to autoload library predicates
into the global user module. This makes the development
tools and library implicitly available to the toplevel,
but not to modules. -
user_or_explicit
Combinesexplicit
withuser
,
providing lazy loading of predicates imported using
autoload/1,2 and implicit access to the whole library for
the toplevel. -
true
Provide full autoloading everywhere. This is
the default.
-
Autoloading has a preference: (1) autoload/2, (2) autoload/1 and (3) autoload from the library.
Status and results
Added autoload/2 declarations for all files in library
that come from
the core system. This implies that all dependencies for these core
libraries are now explicit. Using my default personal init.pl
which
enables <N> ?-
history and XSB compatible loading of .P
files, startup
time resource usage is reduced significantly:
Before (Intel® Core™ i5-6260U, M2 SSD, Fedora 30)
?- statistics.
% Started at Tue Feb 11 09:56:26 2020
% 0.206 seconds cpu time for 726,531 inferences
% 6,515 atoms, 4,580 functors, 3,615 predicates, 74 modules, 136,107 VM-codes
After
?- statistics.
% Started at Tue Feb 11 10:01:17 2020
% 0.086 seconds cpu time for 247,031 inferences
% 6,001 atoms, 4,154 functors, 2,925 predicates, 49 modules, 102,780 VM-codes
Modes
The default mode is true
which, if all libraries have been updated to
use autoload/2 where possible, will remain fully compatible with the
current system while providing two benefits: (1) reduced load time and
memory usage and (2) a guarantee that all relevant code is loaded if
autoload
is switched to false
.
The mode false
is probably useful for creating stand-alone programs
reliably.
The mode user_or_explicit
is intended for developing programs that
eventually want to go for false
for creating an executable. While
developing it does the loading lazily which reduces startup time and
in addition it allows you to run whatever you like in user
space
without declarations. This includes tools (listing, gxref, gtrace,
edit, profile, statistics, …) and running stuff like this to test
stuff inside your modules without importing time/1, forall/2, etc.
?- time(forall(between(1, 1 000 000, _), mym:mygo)).
The mode user
, as false
, materializes all autoload/1,2 and, as
user_or_explicit
ensures the toplevel remains friendly.
Portability and compatibility
Several people here have argued for controlled import using use_module/2
(or now autoload/2). The advantage is that you can easily see what comes
from where without using tools. Another advantage is that it is
completely obvious what needs to be loaded for creating an executable
and thus there is no need to rely on the code analysis to include all
dependencies. As we know, code analysis in Prolog is by definition
potentially incomplete. I have seen actual production code using atom_concat(X,Y,Name), call(Name, ...)
There is also a price to pay. In the past predicates have often been
moved from built-in to the library or the other way around. Migrating to
the library reduces the size of the core while migration to a built-in
can use a low-level C implementation to gain performance. Migration
between libraries also happened, either for compatibility with other
Prolog systems or just to clean up the organization. Using autoload is
true
, this doesn’t affect any code. If use_module/2 or autoload/2 is
used and autoloading is disabled, this does break applications.
Note that the fear that running code breaks as a result of using the
autoloader is not justified. Programs first use the predicates they
define themselves, unless these conflict with the ISO core. Anything
else comes from built-in or the autoloader, where the system development
should guarantee a name/arity available as built-in or autoloading is
(1) not ambiguous, (2) does not disappear and (3) does not change
semantics. The latter (changing semantics) happens occasionally for
compatibility or due to new insights.
A similar argument applies to porting e.g., SICStus code. Typically we
can simply remove most of the use_module/1,2 calls and let the auto
loader do its magic. With explicit loading you will probably end up with
a lot of :- if(...)
or significant extension of the dialect
emulation libraries. For this reason SICStus and SWI long time ago
agreed on a :- require([PredicateIndicators]).
, meaning “Get these
things from somewhere” that was dynamically mapped to use_module/2 in
SICStus and ignored in SWI-Prolog as the autoloader would take care.
SWI-Prolog’s require will be changed to depend on the autoload flag.
Problems
The main remaining problem has to do with the role of the user
module.
SWI-Prolog provides a module inheritance system where module X
inherits from user
which inherits from system
where all the
built-ins live (by default, the hierarchy can be changed). The
inheritance mechanism does:
- Make predicates available. These can be overruled locally, except
for those markediso
. - Share operators. Operator lookup follows the module inheritance
relations. This allows defining operators inuser
for compatibility
with many systems that have a single global operator space. - Inherit the
unknown
flag. - Create a pipeline for term_expansion/2, which is first applied in the
local module, then inuser
and finally insystem
.
The module inheritance system for predicates interacts with unknown
and
autoloading. This leads to weird behaviour if these are changed from their
defaults, such as
:- module(m, [m/1]).
m(X) :- member(X, `aap`).
Now, without autoloading, calling m/1 raises an exception. Now call
or import member/2 in user
and suddenly m/1 runs fine. This is of
course unacceptable. I see some ways out:
-
Block inheritance of predicates from
user
using some flag. This
probably has the least implications. It feels bad to change the
general inheritance relation for a specific module though. -
Move
user
to be cousins of the other modules rather than the
parent. The problem is that this breaks the desirable relations
for operators,unknown
and term expansion -
Use a new module for the default interactive toplevel. It is a
bit unclear to me how much would be affected by this. If this
new module inherits fromuser
and non-module files are by
default still loaded intouser
, possibly not that much? For
the toplevel to load non-module files intouser
itself
is quite a kluge though. Loading code into the new toplevel
has some advantages, such that it starts out empty rather than
containing the various hooks that the system inherrited from
other Prolog implementations and its past.
Any opinions?
Plan
Regardless on how the above is resolved, there are plans for two
next steps:
- Resolve term/goal expansion. That could impact the above as it
may resolve one of the issues wrt. theuser
module. - Use autoload consistenty throughout all libraries and extensions
such that the system becomes fully functional regardless of the
autoload mode. - Complete and make the tool I now have to create the autoload
directives available, probably both as command line tool and
integrated into PceEmacs.