Prolog: Difference between overriding predicate an

2019-06-25 18:45发布

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 scenario

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?

3条回答
做个烂人
2楼-- · 2019-06-25 19:30

It's very simple to add a basic form of 'object orientation'. Let's say we have a clause in a module logic:

:- module(logic, [inside/4]).

% apply the rule to a specified module (expected to be a state object)
inside(M,Food,Eater,T) :-
    M:isTime(T),
    M:injestEvent(InjEvent),
    M:justAfter(T, InjEvent),
    M:actorOfEvent(InjEvent, Eater),
    M:objectOfEvent(InjEvent, Food).

and we have a lot of state objects pertinent: in a file state1.pl

isTime(10).
injestEvent(eat).
justAfter(10, eat).
actorOfEvent(eat, mick).
objectOfEvent(eat, food).

and in a file state2.pl

isTime(20).
injestEvent(sleep).
justAfter(20, sleep).
actorOfEvent(sleep, everyone).
objectOfEvent(sleep, dream).

then a possible session:

?- [logic].
true.

?- s1:consult(state1).
true.

?- s2:consult(state2).
true.

?- inside(s1,Food,Eater,T).
Food = food,
Eater = mick,
T = 10.

?- inside(s2,What,Who,T).
What = dream,
Who = everyone,
T = 20.

A small generalization, worth to try:

inside(M,Food,Eater,T) :-
    resolve(M),
    M:isTime(T),
    ...

where resolve/1 could be

resolve(M) :- var(M) -> current_module(M), catch(M:isTime(_),_,fail) ; true.

this trick enable 'browsing the objects':

?- inside(M,X,Y,Z).
M = s2,
X = dream,
Y = everyone,
Z = 20 ;
M = s1,
X = food,
Y = mick,
Z = 10 ;
false.
查看更多
欢心
3楼-- · 2019-06-25 19:34

An alternative to CapelliC is the use of Prolog dicts. They have been introduced by SWI-Prolog and since release 1.3.0 they are also available in Jekejeke Prolog. If the receiver is not needed, one can simply use an underscore.

File state1.pl:

:- module(state1, [isTime/2, injestEvent/2, justAfter/3, 
                   actorOfEvent/3, objectOfEvent/3]).
:- reexport(logic).
_.isTime() := 10.
_.injestEvent() := eat.
_.justAfter(10) := eat.
_.actorOfEvent(eat) := mick.
_.objectOfEvent(eat) := food.

File state2.pl:

:- module(state2, [isTime/2, injestEvent/2, justAfter/3, 
                   actorOfEvent/3, objectOfEvent/3]).
:- reexport(logic).
_.isTime() := 20.
_.injestEvent() := sleep.
_.justAfter(20) := sleep.
_.actorOfEvent(sleep) := everyone.
_.objectOfEvent(sleep) := dream.

File logic.pl:

:- module(logic, [inside/4]).
M.inside(Food,Eater) := T :-
    T = M.isTime(),
    InjEvent = M.injestEvent(),
    InjEvent = M.justAfter(T),
    Eater = M.actorOfEvent(InjEvent),
    Food = M.objectOfEvent(InjEvent).

To make the logic also visible in state1 and state2 use reexport/1. This allows sending a message to state1 or state2, but nevertheless a method from logic will be processed. Here is an example run:

Welcome to SWI-Prolog (threaded, 64 bits, version 7.7.19)
SWI-Prolog comes with ABSOLUTELY NO WARRANTY. This is free software.

?- T = state1{}.inside(Food,Eater).
T = 10,
Food = food,
Eater = mick.

?- T = state2{}.inside(Food,Eater).
T = 20,
Food = dream,
Eater = everyone.

The exports of isTime/2, injestEvent/2, etc.. will go away with the upcoming release 1.3.1 of Jekejeke Prolog when we have made ('.')/3 call-site aware. But the result for Jekejeke Prolog is the same:

Jekejeke Prolog 3, Runtime Library 1.3.0
(c) 1985-2018, XLOG Technologies GmbH, Switzerland

?- T = state1{}.inside(Food,Eater).
T = 10,
Food = food,
Eater = mick

?- T = state2{}.inside(Food,Eater).
T = 20,
Food = dream,
Eater = everyone
查看更多
地球回转人心会变
4楼-- · 2019-06-25 19:45

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

查看更多
登录 后发表回答