Need help with predicate calls traversing back and forth between multiple modules

This is hopefully a simplified but complete explanation and example of the issue.

There are two modules a and b. Module b has use_module(a,expect[...]).

Module b is only a few predicates that have the same predicate indicator as those in module a but have different bodies.

When predicates in module b call a predicate in module a and then module a only calls other predicates in module a all is working nicely, e.g.

B -> A -> A.

When predicates in module b call a predicate in module a and then module a calls a predicate with the same predicate indicator that has a clause in both module a and b was hoping the predicate in module b would be called but the predicate in module a is called, e.g.

Hoped for
B -> A -> B.

Actual
B -> A -> A.

In other words, for a goal, the predicate calls are to lower and lower levels of the layered modules, and once a call is made to a lower module, the higher level module with the same predicate indicator are not selected.

One solution that works but duplicates code is to copy the predicates, in module a called from module b into module b. But duplicating identical predicates into another module just to solve this seems so wrong.

I looked in the modules section of the documentation for some possible solutions

Tried Explicit manipulation of the calling context using @ with no success.
Tried Composing modules from other modules using reexport/1,2 wit no success.
Considered default_module/2 but did not know how to use it.

Also looked at Loading Prolog source files in the documentation and considered load_files/2 with option module(+Module) but was thinking that should be an option passed in from use_module/2 so did not know how to use the option.

While the implemented features I tried did not work, as I have never used those features before I don’t know if I used them properly, so they may have not succeeded because they were implemented improperly.


Is it possible to allow predicates in module b to call predicates in module a then those predicates call back to module b without duplicating code?


Below is a reprex.

The first module (color) is all of the predicates working as one module. Think of this as module a from above.

The second module (color_partial_all_predicates) is all of the predicates working as one module but with some of the predicate bodies changed. Think of this as module b with the duplicated code.

The third module (color_partial_modified_predicates) uses use_module/2 to access the predicates from module color. This module has only the modified predicates. Think of this as the way I would like module b to work without the need to duplicate the predicates. In the example module the duplicated predicates are shown for easier comparison but are commented out.

Module color
:- module(color, [
    items/1,
    red/1,
    pink/1,
    tomato/1,
    orange/1,
    pink/1,
    green/1,
    spring_green/1,
    chartreuse/1,
    blue/1,
    cyan/1,
    aqua/1,
    deep_sky_blue/1
    ]).

items(items(Red,Green,Blue)) :-
    red(Red),
    green(Green),
    blue(Blue).

red(red(Pink,Tomato,Orange)) :-
    pink(Pink),
    tomato(Tomato),
    orange(Orange).

pink(pink) :-
    true.

tomato(tomato) :-
    true.

orange(orange) :-
    true.

green(green(Lime,Spring_green,Chartreuse)) :-
    lime(Lime),
    spring_green(Spring_green),
    chartreuse(Chartreuse).

lime(lime) :-
    true.

spring_green(spring_green) :-
    true.

chartreuse(chartreuse) :-
    true.

blue(blue(Aqua,Cyan,Deep_sky_blue)) :-
    aqua(Aqua),
    cyan(Cyan),
    deep_sky_blue(Deep_sky_blue).

aqua(aqua) :-
    true.

cyan(cyan) :-
    true.

deep_sky_blue(deep_sky_blue) :-
    true.

:- begin_tests(color).

test(1) :-
    items(Items),
    assertion( Items == items(red(pink,tomato,orange),green(lime,spring_green,chartreuse),blue(aqua,cyan,deep_sky_blue)) ).

:- end_tests(color).
Module color_partial_all_predicates
:- module(color_partial_all_predicates, [
    items/1,
    red/1,
    pink/1,
    tomato/1,
    orange/1,
    pink/1,
    blue/1,
    aqua/1,
    cyan/1,
    deep_sky_blue/1
    ]).

% Different - Different from predicate in module color
% Same      - Same as predicate in module color. The predicate is left here but commented out so the predicate in color can be called.

% Different
items(items(Red,Blue)) :-
    red(Red),
    blue(Blue).

% Same
red(red(Pink,Tomato,Orange)) :-
    pink(Pink),
    tomato(Tomato),
    orange(Orange).

% Same
pink(pink) :-
    true.

% Same
tomato(tomato) :-
    true.

% Same
orange(orange) :-
    true.

% Same
blue(blue(Aqua,Cyan,Deep_sky_blue)) :-
    aqua(Aqua),
    cyan(Cyan),
    deep_sky_blue(Deep_sky_blue).

% Same
aqua(aqua) :-
    true.

% Different
cyan(light_cyan) :-
    true.

% Same
deep_sky_blue(deep_sky_blue) :-
    true.

:- begin_tests(color_partial_all_predicates).

test(1) :-
    items(Items),
    assertion( Items == items(red(pink,tomato,orange),blue(aqua,light_cyan,deep_sky_blue)) ).

:- end_tests(color_partial_all_predicates).
Module color_partial_modified_predicates
:- module(color_partial_modified_predicates, [
    items/1,
    red/1,
    pink/1,
    tomato/1,
    orange/1,
    pink/1,
    blue/1,
    cyan/1,
    deep_sky_blue/1
    ]).

:- use_module(color,except([
        items/1,
        cyan/1
    ])).

% Different - Different from predicate in module color
% Same      - Same as predicate in module color. The predicate is left here but commented out so the predicate in color can be called.

% Different
items(items(Red,Blue)) :-
    red(Red),
    blue(Blue).

% Same
% red(red(Pink,Tomato,Orange)) :-
%     pink(Pink),
%     tomato(Tomato),
%     orange(Orange).

% Same
% pink(pink) :-
%     true.

% Same
% tomato(tomato) :-
%     true.

% Same
% orange(orange) :-
%     true.

% Same
% blue(blue(Aqua,Cyan,Deep_sky_blue)) :-
%     aqua(Aqua),
%     cyan(Cyan),
%     deep_sky_blue(Deep_sky_blue).

% Same
% aqua(aqua) :-
%     true.

% Different
% Was expecting this clause with predicate indicator cyan/1 to take precedence
% over the clause with same predicate indicator in module color.
cyan(light_cyan) :-
    true.

% Same
% deep_sky_blue(deep_sky_blue) :-
%     true.

:- begin_tests(color_partial_modified_predicates).

test(1) :-
    items(Items),
    assertion( Items == items(red(pink,tomato,orange),blue(aqua,light_cyan,deep_sky_blue)) ).

:- end_tests(color_partial_modified_predicates).

% Expected: items(red(pink,tomato,orange),blue(aqua,light_cyan,deep_sky_blue))
% Actual:   items(red(pink,tomato,orange),blue(aqua,cyan      ,deep_sky_blue))

Example run of color_partial_modified_predicates showing an error.

Note: I don’t consider SWI-Prolog to be causing the error, the error is a result of the way my code is written. I am hoping a simple option for use_module/2 or an added Prolog directive, or something simple will allow the test case to work.

?- [color_partial_modified_predicates].
true.

?- run_tests.
% PL-Unit: color . done
% PL-Unit: color_partial_modified_predicates 
ERROR: c:/users/eric/documents/notes/discourse swi-prolog osu osl/osu osl prolog/prolog module inheritance/color_partial_modified_predicates.pl:73:
        test 1: assertion failed
        Assertion: items(red(pink,tomato,orange),blue(aqua,cyan,deep_sky_blue))==items(red(pink,tomato,orange),blue(aqua,light_cyan,deep_sky_blue))
A done
% 1 assertion failed
% 1 test failed
% 1 tests passed
false.

While this looks like inheritance in Object Oriented code I will not use that as an analogy as I think that can only lead to more confusion and problems.

If this does not make sense I can make some GraphViz diagrams but it will take time.


Personal Notes

Jan B. gave me a clue about message sending operator and noted an example in Threaded queries? Rulebase independence?. This notes the Many Worlds concept which Paulo blogs about as The “many worlds” design pattern albeit in Logtalk.

Interestingly I have asked the many worlds question before in How to call predicate in test that is called from outside of test? , but now that I have run into this pattern twice I will have a better idea of what I am bumping into as this part of Prolog is still a very dark room to me.

Paulo notes that to implement Many Worlds in Logtalk (ref)

Logtalk objects are not an abstraction over Prolog modules. They are not implemented using modules or require in any way that the backend Prolog compiler supports a module system. But you can do with objects everything you can do with modules. What makes Logtalk objects a technologically superior solution compared with Prolog modules is that you can natively and elegantly express a richer variety of programming idioms and patterns without having to resort to lower levels of abstraction to hack your way out or be forced to build your own custom abstractions. Thus, it’s not as much about making some tasks easier but more about what you can express at the same level of abstraction.

For related SWI-Prolog code see this post.

Keyword/phrases

Many worlds
Multiple concrete worlds (ref)
parameterized modules (ref)

Predicates that may be of use:
in_temporary_module/3 (ref)

See:
Wiki Discussion: Many Worlds
Can TerminusDB be used to model Prolog Many Worlds?