wow, this is indeed a very nice exchange.
I implemented a tou example using term_expansion/2 and goal_expansion/2 so that the KB can call to the corresponding “caller” module.
What I realized doing that is what both of you just pointed out:
The complete rewrite of the kb module to pass the additional argument is of course painful.
What makes it more complicated is that I don’t have the kb reasoning toolkit in just one module, but in many modules (actions, charge, items, etc…). So, a predicate X in the actions module may call one Y in charge module, which itself will need to post a goal Z against the original agent module (the one who called X in actions)! This means that the caller module should be passed across all predicates.
This makes me wonder that it is not so easy to design a mechanism, because in a sequence of goal calls, there will be many different contexts. which one we would want? the very first one? Why? not easy.
I am studying @jan last solution, it’s pretty smart, although using exceptions for this sounds a bit non-natural (of course, I am expecting everything can be done with any of this systems, as they are all turing-complete
)
Thanks nonetheless to both for the rich exchanges.
Sebastian
PS: @Jan, maybe would be good to add something like this as example for term_expansion/2?
:- module(test_kb, [far_away/2]).
% operator to signal when the predicate should be expanded with context module call
:- op(1, fx, ^).
% List all predicates that need to be added 1 more argument to track the calling module
transform_kb_clause(far_away(G):-Body, far_away(G, Agent):-Body2) :-
add_agent_module(Agent, Body, Body2).
% domain-independent
term_expansion(ClauseIn, ClauseOut) :-
transform_kb_clause(ClauseIn, ClauseOut).
% Add Agent as calling module in SOME predicates; here, only on percepts/1
add_agent_module(Agent, ^G, Agent:G2) :- !,
G =.. [F|Args],
append(Args, [Agent], Args2),
G2 =.. [F|Args2].
%add_agent_module(Agent, percepts(X), Agent:percepts(X)) :- !.
add_agent_module(Agent, (G1, G2), (G1_New, G2_New)) :-
add_agent_module(Agent, G1, G1_New),
add_agent_module(Agent, G2, G2_New), !.
add_agent_module(Agent, (G1; G2), (G1_New; G2_New)) :-
add_agent_module(Agent, G1, G1_New),
add_agent_module(Agent, G2, G2_New), !.
add_agent_module(Agent, (G1 -> G2), (G1_New -> G2_New)) :-
add_agent_module(Agent, G1, G1_New),
add_agent_module(Agent, G2, G2_New), !.
add_agent_module(_, G, G).
% Is destination far from the current agent location?
% Agent location LocAgent is obtained via the percept of the agent, which is not in this module
% but in the agent "calling" module
far_away(Destination) :-
^percepts(P),
member(loc(LocAgent), P), !,
distance(LocAgent, Destination, D),
D > 10.
distance(L1, L2, D) :- D is abs(L1-L2).
will produce this clause when consulting:
far_away(D, A) :-
call(A:percepts(B, A)),
member(loc(C), B), !,
distance(C, D, E),
E>10.
What I do not like here are the add_agent_module/3 clauses to process the Body. I am sure it is not even complete!