wow, wow, amazing @jan, ninja…
OK so I implemented both of your suggestions. It took me a while to reconstruct them so as to understand what is happening but seems to work. I did all this because I was now very curious, I totally lost track of my application!!!
So, TWO parts:
- I have a solution using expansion, of goals and terms., that is generic. I think the doc does need an example as this is far from trivial. Agree not a huge example. As I want to document this somewhere, I will post here the simplest extract I could manage to write as example (I know this is long, but markdown makes all the post readable I hope):
/*
This example shows how to expand both head and body of clauses
using term_expand/2 and goal_expand/3.
See post in forum:
https://swi-prolog.discourse.group/t/modules-design-for-multi-agent-modeling/557
So, user writes:
far_away :-
percepts(P),
member(loc(Loc), P),
Loc > 100.
farther_than(D) :-
^percepts(P),
member(loc(Loc), P),
Loc > D.
... and it gets transformed into (percept/1 is outside the module):
far_away(A) :-
call(A:percepts(B)),
member(loc(C), B),
C>100.
farther_than(D, A) :-
call(A:percepts(B)),
member(loc(C), B),
C>D.
*/
% Operator used to signal that expansion is needed for the goal
:- op(1, fx, ^).
expand_clause((Head0 :- Body0), (Head :- Body)) :-
expand_head(Head0, Head, ExtraArg),
b_setval(extra_arg, ExtraArg), % remember the created extra variable when processing the Body
expand_goal(Body0, Body), % will use defined goal_expansion/2
b_setval(extra_arg, []).
% What heads to expand (add one more argument)
expand_head(far_away, far_away(M), M).
expand_head(farther_than(D), farther_than(D, M), M).
% What goals in bodies to change context module (use one stored in global var extra_arg
goal_expansion(^G, call(M:G)) :-
b_getval(extra_arg, M).
goal_expansion(percepts(P), call(M:percepts(P))) :-
b_getval(extra_arg, M).
% Domain-independent: this will be the trigger, one per clause in the DB
term_expansion(ClauseIn, ClauseOut) :-
expand_clause(ClauseIn, ClauseOut).
% These are the clauses that will be transformed/expanded automatically
far_away :-
percepts(P),
member(loc(Loc), P),
Loc > 100.
farther_than(D) :-
^percepts(P),
member(loc(Loc), P),
Loc > D.
- Then I went and implemented your “crazy” last solution. Amazing. This needs no expansion at all and works with many modules. So I have an agent using two modules kb1 and kb2, and even kb1 calls kb2, which itself is the one calling
percepts/1
that is in the agent. Because I will have many kb modules for different aspects, I factored out your solution into a separate module calledwithself
which provides the raw tools:
:- module(withself, [with_self/2, call_self/1, percepts/1]).
% The `no_lco_ is needed to avoid last call optimization while calling Goal.
% We pass this Module to avoid it being garbage collected.
with_self(Module, Goal) :-
Goal, % this will call
no_lco(Module).
no_lco(_).
call_self(Goal) :-
prolog_current_frame(F),
prolog_frame_attribute(F, parent_goal, withself:with_self(Module, _)), % who called with_self/2?
call(Module:Goal).
%% Below we need to define hooks for every predicate in the original module that needs to recover its module
%% by tracing back the call to with_self/2 from that caller module
percepts(P) :-
call_self(percepts(P)).
I think that domain-dependent percepts/1
here can be factored out into a different module itself.
I am quite impressed. Seems to me Paolo has a point: not just because something can be done somehow, it has to be done that way. Seems Logtalk has all this all sorted out and can provide you with more. I will at some point look at that, I think it is worth a student project.
At this point Logtalk requires significant thinking and work, and the above solution, while is impressive, is hard to estimate the performance cost, as every time the agent access the percepts, Prolog will traverse the call stack…
For now, I will stick to my non-module solution: I have a copy of all the KB loaded in each agent module.
However, I now understand that a totally module-based solution that is clean and not too cumbersome is doable. Thanks @jan and @pmoura!
wow what a estar Friday…
Sebastian