Re-defining a predicate in a new module file, vs. in a traditional file

Suppose I want to load a new definition of a predicate from a new module:

% mod_1.pl:
:-module(mod_1, [exported/0]).

exported :- writeln(mod_1).


% mod_2.pl:
:-module(mod_2, [exported/0]).

exported :- writeln(mod_2).

I don’t want to have the two definitions living side-by-side, as in polymorphism. I want to replace one definition with the other. However, if I load mod_2 after loading mod_1 that will raise a permission error:

?- use_module(mod_1).
true.

?- use_module(mod_2).
ERROR: import/1: No permission to import mod_2:exported/0 into user (already imported from mod_1)
true.

But if I define exported/0 in a traditional file, I can load a new definition from a different file with only a warning:

% trad_1.pl:
exported :- writeln(trad_1).

% trad_2.pl:
exported :- writeln(trad_2).

?- [trad_1].
true.

?- exported.
trad_1
true.

?- [trad_2].
Warning: c:/users/.../trad_2.pl:1:
Warning:    Redefined static procedure exported/0
Warning:    Previously defined at c:/users/.../trad_1.pl:1
true.

?- exported.
trad_2
true.

Can I avoid the permission errors and still load different definitions of a predicate from different files, whle not giving up the advantages of using modules?

P.S. I’ve asked this question before but in a more confusing manner- apologies and I hope this one makes more sense.

Note that also in the non-moule case you end up loosing one of the definitions. The difference is that in the non-module case you end up with the last loaded definition and in the module case with the first. What are you after?

Using modules you can use

:- use_module(mod_1, [except([exported/1])]).

Or explicitly use use_module/2 to explcitly import the stuff you want from the appropriate module. You can also use the "as name" construct to import (one of the) definitions under a different local name.

If you want clauses from multiple files, multifile/1 is the way to go. If you want the disjunction of two definitions the options is to import from neither and use

exported(X) :- mod_1:exported(X).
exported(X) :- mod_2:exported(X).
1 Like

Hi Jan, and thanks for the reply.

What I would like to be able to do is to load a definition of one predicate from one module, then load a new definition from another module with the new definition replacing the old definition. So, yes, I do want to “lose” one of the two definitions. e.g. I don’t want it to stay behind and take up memory.

In my example above, I’d like to load mod_2 after loading mod_1 and then find that the definition of exported/0 is the one loaded from mod_2, without raising an error. For example:

?- use_module(mod_1).
true.

?- exported.
mod_1

?- use_module(mod_2).
% Everything below here doesn't actually happen because an error is raised
true.

?-exported.
mod_2

My specific use case is that I have an ILP system that loads its data from module files, each of which exports the same four predicates (for positive and negative examples, background knowledge and language bias). The module declarations in the module files holding the data look like this:

:-module(anbn, [background_knowledge/2
               ,metarules/2
               ,positive_example/2
               ,negative_example/2
               ]).

Where the four exports are the common predicates that must be exported by all module files that hold experiment data.

So I want to be able to load one such file, train my system with the data in the four predicates it exports, then load another file and train my system with the data in the new file.

Importing with a different local name using the except etc. mechanism is a bit cumbersome because I’d have to generate new local names everytime I import the same predicate (if I understand correctly).

I don’t want clauses from multiple files, or a disjunction thereof, so multifile/1 and :/2 aren’t good - the data from different files can be very large and I don’t want to keep unnecessary examples etc. around.

In the past I achieved all this by unloading the first file, abolishing its four exported predicate and then exporting the new file from an intermediary module with the reexport/1 mechanism, but it looks like it was all a bit of a hack because it broke twice after two Swi updates.

That certainly is not going to work with modules. Even when replacing the import (which is possible), the clauses stay in the original module.

I suspect the data modules only contain these 4 predicates, no? I.e., there is no need for hiding feature which is the main purpose of modules? In that case the simplest way is probably to put the data in a plain file and load it using consult/1 into your reasoning module. Just remember the file you loaded and for the next one first use unload_file/1 on the currently loaded data file.

A good alternative is to define these four predicates as dynamic and write your own read/assert loop to load a data file. For the next data, simply call retractall/1 on the current definitions and load the new data. That also makes it easy to combine data sets, sample them (instead of loading all data), etc. Note that loading the data yourself is faster than what the load_files/2 based versions do as you do not need term/goal expansion, source code admin, etc.

On the other hand, if you load the files more than once, getting them into .qlf form gives a huge speedup – I measured 16 seconds vs 0.7 seconds for 480,000 clauses.

3 Likes

You’re right- I think that’s why in my previous setups I was unloading (with unload_file/1) the last data file module before loading a new one. I think I noticed the old clauses in the database.

Unfortunately, there may be any number of other predicates that may be defined in those data files. In particular, background knowledge consists of arbitrary Prolog programs with any number of auxiliaries and even examples may be defined as generators, rather than sets of clauses. So it’s “data” in the sense of Prolog, i.e. programs. Indeed, a primary motivation to use modules was to hide the implementation of background knowledge and examples.

I guess that’s an option but I’m worried I’ll just end up with a buggy, slow mechanism to load an almost-plain-Prolog file.

I’ll have to think about it. Thank you for your valuable help.

Just one last question: I mentioned that my previous hacks failed twice after that many updates of Swi-Prolog. Might it be useful for you to give you more detail about the versions etc where that happened? Maybe something changed in Swi unexpectedly?

I don’t know. To some extend relying on edge cases that are not explicitly specified in the docs is asking for trouble. (Re-)loading source code has been changed in various details to accommodate atomic reloading of code in multiple threads, reliable multi-threaded autoloading, etc. If you want to do stuff relying on edge cases I guess the right way is to discuss that here, get to an agreement that something should work and help getting tests in place that ensures that the assumptions you make remain valid in the future. Given a test case, a possible violation doesn’t go unnoticed and will only happen if there are strong reasons to change behavior.

Note that in_temporary_module/3 is another possible interesting option to deal with temporary data.

I guess what I was doing was a bit of a hack, in the sense that I tried to work around the module system’s restrictions and just avoid doing something that would raise an error, while getting my way with it. I’ll know not to do that again, unless I’ve discussed it here.

I think what I really want is a “forget_module/1” predicate- something that unloads a module and garbage collects everything it defined so there’s no namespace clashes (which I’m guessing are what necessitate the errors when a predicate is re-exported from a new module). I’ll see if I can figure out how to do something like that and if so, discuss it here like you suggest.

I hadn’t seen that predicate before. I’ll give it a try. Thanks.

In case you’re curious, this is the source for the hack that broke:

I had to abandon it after updating to Swi 8.3.14. I also encountered some errors in a previous version but I managed to avoid producing them without changing my code.

1 Like