Unknown procedure error after re-exporting predicates from unloaded module

[UPDATE: I changed the post title to be more descriptive of the error encountered. The original title was about using modules as multiple worlds.]

I want to represent multiple “worlds” as separate modules that export the same predicates and so that I can switch between each “world” in the same session.

For a while, I had a setup that worked, but this was broken twice in previous versions of Swi-Prolog, first in 8.2.1 and now in 8.3.14. I guess it’s a bit hacky and convoluted, but this is how it worked until 8.3.14:

First, we have the following modules:

  1. data_file_1.pl and data_file_2.pl, two Prolog modules representing alternative “worlds”. Each exports its own definition of the same predicates, exported_predicate/1 and exported_predicate/2.

  2. config.pl is the “configuration” module that is responsible for declaring the current data file as a fact data_file/2, where the first argument is the path to a data file and the second its module name.

  3. reloader.pl defines a predicate reload.pl that unloads the last loaded data file and reexports the world predicates from the data file declared in config.pl. It also registeres the name of the current data file, as declared in config.pl, in the dynamic database.

The source of the four modules follows below as well as a “loader” file to load the lot in the Swi IDE:

% Begin load.pl
:- doc_browser.

:-use_module('config.pl').

:- edit('load.pl').
:- edit('config.pl').
:- edit('data_file_1.pl').
:- edit('data_file_2.pl').
:- edit('reloader.pl').

% End load.pl


% Begin config.pl
:-module(config, [data_file/2]).

:-user:use_module(reloader).

data_file('data_file_1.pl',data_file_1).
%data_file('data_file_2.pl',data_file_2).

:-reloader:file_reload.

% End config.pl


%Begin data_file_1.pl
:-module(data_file_1, [exported_predicate/1
                      ,exported_predicate/2
                      ]).

exported_predicate(a1).
exported_predicate(a1,b1).

% End data_file_1.pl


% Begin data_file_2.pl
:-module(data_file_2, [exported_predicate/1
                      ,exported_predicate/2
                      ]).

exported_predicate(a2).
exported_predicate(a2,b2).

% End data_file_2.pl

% Begin reloader.pl
:-module(reloader, [file_reload/0
                   ]).

:-dynamic '$data_file'/2.

file_reload:-
	config:data_file(P, M)
	,replace_data_file(P,M)
	,reexport(P).

replace_data_file(P,M):-
	\+ '$data_file'(_,_)
	,assert('$data_file'(P,M))
	,!.
replace_data_file(P1,M1):-
	'$data_file'(P0,_M0)
	,unload_file(P0)
	,retractall('$data_file'(_,_))
	,assert('$data_file'(P1,M1)).

% End reloader.pl

Now, the first time I reload config.pl (calling reloader:file_reload in the process) things work as I expect them. Note that I start the session by consulting load.pl:

?- listing([data_file/2,'$data_file'/2,exported_predicate/1, exported_predicate/2]).
config:data_file('data_file_1.pl', data_file_1).

:- dynamic reloader:'$data_file'/2.

reloader:'$data_file'('data_file_1.pl', data_file_1).

data_file_1:exported_predicate(a1).

data_file_1:exported_predicate(a1, b1).

true.

% Changed config.pl to declare the other data file, here.

?- make.
%  data_file_2.pl compiled into data_file_2 0.00 sec, 2 clauses
% c:/.../config compiled into config 0.00 sec, 0 clauses
true.

?- listing([data_file/2,'$data_file'/2,exported_predicate/1, exported_predicate/2]).
config:data_file('data_file_2.pl', data_file_2).

:- dynamic reloader:'$data_file'/2.

reloader:'$data_file'('data_file_2.pl', data_file_2).

data_file_2:exported_predicate(a2).

data_file_2:exported_predicate(a2, b2).

true.

However, if I change config.pl again to declare data_file_1.pl as the data file, again, it doesn’t work as I’d expect it:

% Changed config.pl to declare the other data file, here.

?- make.
% c:/.../config compiled into config 0.00 sec, 0 clauses
true.

?- listing([data_file/2,'$data_file'/2,exported_predicate/1, exported_predicate/2]).
config:data_file('data_file_1.pl', data_file_1).

:- dynamic reloader:'$data_file'/2.

reloader:'$data_file'('data_file_1.pl', data_file_1).

ERROR: Unknown procedure: exported_predicate/1 (DWIM could not correct goal)
^  Exception: (13) setup_call_catcher_cleanup(system:true, prolog_listing:listing_(user:[data_file/2, '$data_file'/2, exported_predicate/1, exported_predicate/2], []), _5502, prolog_listing:close_sources) ? abort
% Execution Aborted

What I expected to see is that everytime file_reload/0 is called from inside config.pl, the data file declared in data_file/2 is loaded into memory and its exported predicates re-exported. This happens only the first time I call file_reload/0.

As I note above, I’ve made this work twice so far and twice it was broken after updating to a new version of Swi. What is it that’s changing and is there a way to do what I want that won’t break in future versions?