I feel really stupid, and feel like I'm missing something.
I've basically got two files:
module.pl
for the universal logic rules (meant to be reusable)state.pl
one for the current scenarioIn the module file (module.pl
) I've declared:
inside(Food,Eater,T) :-
isTime(T),
injestEvent(InjEvent),
justAfter(T,InjEvent),
actorOfEvent(InjEvent, Eater),
objectOfEvent(InjEvent, Food).
Q1) I've had to declare all those other predicates with singleton variables (in the same file), just to stop module.pl
complaining they don't exist:
isTime(_T).
justAfter(_Time,_Event).
actorOfEvent(_Event, _ActorOfEvent).
objectOfEvent(_Event,_ActorOfEvent).
Is that right?
Q2) I can't use those predicates like justAfter/2
my other file without it saying:
Local definition of user:justAfter/2 overrides weak import from module
How can I use the predicates I've imported from my module, rather redefining it?
Prolog modules were designed to hide auxiliary predicates. They don't provide a concept of interface that allows separating predicate declarations from predicate definitions. That's why the compiler complains if you export predicates that are not defined. From your description, I assume you tried something like:
----- module.pl -----
:- module(module, [
inside/3, isTime/1, injestEvent/1, justAfter/2, actorOfEvent/2, objectOfEvent/2
]).
inside(Food,Eater,T) :-
isTime(T),
injestEvent(InjEvent),
justAfter(T,InjEvent),
actorOfEvent(InjEvent, Eater),
objectOfEvent(InjEvent, Food).
---------------------
which results in:
?- [module].
ERROR: Exported procedure module:justAfter/2 is not defined
ERROR: Exported procedure module:isTime/1 is not defined
ERROR: Exported procedure module:injestEvent/1 is not defined
ERROR: Exported procedure module:objectOfEvent/2 is not defined
ERROR: Exported procedure module:actorOfEvent/2 is not defined
true.
You attempted to workaround this error by adding local definitions. But this just result in the second problem you describe. When you do something like:
?- use_module(module).
You import all the predicates exported by module
, including those that you want to define in state.pl
. Therefore, the compiler warns you, when loading state.pl
, that this file is overriding those predicates. E.g. with:
----- state.pl -----
isTime(1).
injestEvent(injEvent).
justAfter(1, injEvent).
actorOfEvent(injEvent, eater).
objectOfEvent(injEvent, food).
--------------------
we get:
?- [state].
Warning: /Users/pmoura/Desktop/state.pl:1:
Local definition of user:isTime/1 overrides weak import from module
Warning: /Users/pmoura/Desktop/state.pl:2:
Local definition of user:injestEvent/1 overrides weak import from module
Warning: /Users/pmoura/Desktop/state.pl:3:
Local definition of user:justAfter/2 overrides weak import from module
Warning: /Users/pmoura/Desktop/state.pl:4:
Local definition of user:actorOfEvent/2 overrides weak import from module
Warning: /Users/pmoura/Desktop/state.pl:5:
Local definition of user:objectOfEvent/2 overrides weak import from module
true.
Although these are warnings and not errors, calling the inside/3
predicate will not give you what you want:
?- inside(Food,Eater,T).
true.
Where are the bindings?!? Let's trace the call to highlight the cause:
?- trace.
true.
[trace] ?- inside(Food,Eater,T).
Call: (8) module:inside(_2508, _2510, _2512) ? creep
Call: (9) module:isTime(_2512) ? creep
Exit: (9) module:isTime(_2512) ? creep
Call: (9) module:injestEvent(_2804) ? creep
Exit: (9) module:injestEvent(_2804) ? creep
Call: (9) module:justAfter(_2512, _2806) ? creep
Exit: (9) module:justAfter(_2512, _2806) ? creep
Call: (9) module:actorOfEvent(_2804, _2510) ? creep
Exit: (9) module:actorOfEvent(_2804, _2510) ? creep
Call: (9) module:objectOfEvent(_2804, _2508) ? creep
Exit: (9) module:objectOfEvent(_2804, _2508) ? creep
Exit: (8) module:inside(_2508, _2510, _2512) ? creep
true.
The trace makes it clear that the "state" predicates are being called in the wrong context.
A clean solution is to use Logtalk objects instead of Prolog modules. Logtalk extends Prolog and supports most systems, including SWI-Prolog. It supports interfaces/protocols as first-class entities (which solve the first problem you mention) and supports inheritance and calling predicates in their usage context (which solves the second problem). You could use e.g.
----- common.lgt -----
:- object(common).
:- public([
inside/3, isTime/1, injestEvent/1, justAfter/2, actorOfEvent/2, objectOfEvent/2
]).
inside(Food,Eater,T) :-
% call the next predicates in "self", i.e. in the
% object that received the inside/3 message
::isTime(T),
::injestEvent(InjEvent),
::justAfter(T,InjEvent),
::actorOfEvent(InjEvent, Eater),
::objectOfEvent(InjEvent, Food).
:- end_object.
----------------------
and then represent "state" as:
----- state.lgt -----
:- object(state, extends(common)).
isTime(1).
injestEvent(injEvent).
justAfter(1, injEvent).
actorOfEvent(injEvent, eater).
objectOfEvent(injEvent, food).
:- end_object.
---------------------
A quick test (after installing Logtalk):
$ swilgt
...
?- {common, state}.
...
true.
?- state::inside(Food,Eater,T).
Food = food,
Eater = eater,
T = 1.
As a bonus, you can define as many "state" objects as you need. You can also have default definitions for the "state" predicates in the common
object. These will be inherited and used when the "state" objects don't provide a definition for a particular predicate. For example, let's add to common
the clause:
objectOfEvent(injEvent, drink).
and delete (or comment out) the clause objectOfEvent(injEvent, food).
from state
. Save and reload and retrying the query will give you:
?- {*}. % abbreviation for Logtalk's make
% Redefining object common
...
% Redefining object state
...
true.
?- state::inside(Food,Eater,T).
Food = drink,
Eater = eater,
T = 1.
If needed, you can also dynamically create new state objects instead of defining them in source files. For example:
?- create_object(s2, [extends(common)], [], [isTime(42), ...]).
This may not be the answer you were looking for but this is also the case where the best answer is to use the right tool^H^H^H^H encapsulation mechanism for the job. Your programming pattern is also a quite common one (and one of the reasons Logtalk was developed).