objectmoduleprolog

Prolog: Difference between overriding predicate and using it


I feel really stupid, and feel like I'm missing something.

I've basically got two files:

In 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?


Solution

  • 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).