bimbart@kulcs.uucp (Bart Demoen) (03/15/88)
See what you can do in BIMprolog: back_retract((_h :- _b)) :- clause(_h,_b,_n) , functor(_h,_f,_a) , functor(_nh,_f,_a) , clause(_nh,_nb,_n) , (retract((_h :- _b),_n) ; assert((_nh :- _nb),_n) , fail ) . clause/3 has as 3th argument the number of the clause: ex. database = a(x) . a(y) . a(z) . then ?- clause(a(y),true,2) . succeeds and ?- clause(a(X),true,3) . says: X = z and ?- clause(a(x),true,N) . says: N = 1 the second argument in retract/2 has a similar meaning ; in a call to assert/2, the 2nd arg must be instantiated and the clause is asserted at the arg2'th place in the database; in this way, asserta/1 is just a special case of assert/2, with definition: asserta(_x) :- assert(_x,1) . so the query ?- assert(a(foo),3) . on the above database has as effect that the database will look afterwards like: a(x) . a(y) . a(foo) . a(z) . But there should be a better way of doing what you want.
ok@quintus.UUCP (Richard A. O'Keefe) (03/15/88)
In article <1194@kulcs.kulcs.uucp>, bimbart@kulcs.uucp (Bart Demoen) writes: > See what you can do in BIMprolog: [ I have reformatted this so that I can read it: if you want to see how Bart Demoen wrote it, read his message. ] > back_retract((HeadUsed:- BodyUsed)) :- > clause(HeadUsed, BodyUsed, Index), % FIND the clause > functor(HeadUsed, Symbol, Arity), > functor(HeadCopy, Symbol, Arity), > clause(HeadCopy, BodyCopy, Index), % COPY the clause > ( retract((HeadUsed :- BodyUsed), Index) % DELETE the clause > ; assert((HeadCopy :- BodyCopy), Index), % REPLACE the clause > fail % resume failing > ). Porting problem: DEC-10 Prolog has assert(+Clause, -Ref) and clause(+Head, ?Body, -Ref) where Ref is a sort of pointer, and several other Prologs do the same. Even AAIS Prolog tries. But that's a minor change, just invent new names for the operations. This will do what the original poster wants, ONLY IF there are no other changes to the same predicate. Consider back_retract(<clause>) -- removes 3rd clause asserta(<new clause>) fail This will shuffle the data base, which was not what was wanted. This kind of excessively unpleasant interaction is precisely why the DEC-10 Prolog family hasn't got "positional" data base commands. We could get around this by having an 'assert_before' predicate which took a clause and a data base reference and put the clause before the one the reference pointed to (even if the referenced clause had since been deleted): * back_retract(Clause) :- * prolog_clause(Clause, Head, Body), % see DECONS.PL in DEC-10 lib * clause(Head, Body, Ref), % FIND the clause * instance(Ref, Copy), % COPY the clause * ( erase(Ref) % DELETE the clause * ; assert_before(Ref, Copy), % REPLACE the clause * fail % resume failing * ). This would be free of shuffling problems, even when other updates to the predicate in question were being made. However, both versions of back_retract/1 have a rather more serious failure mode. Consider back_retract(<clause>), ... !, ... fail By analogy with variable bindings, you would expect the back-retracted clause to reappear. But the cut will have had the effect of pruning away the "assert". Was it in 1983 or 1984 that I pointed this problem out in the Prolog Digest, with reference to an 'assume' operation? This problem is the reason that Quintus Prolog hasn't got backtrackable assignment to global variables in the library: cuts prune away the restoration code. > But there should be a better way of doing what you want. If back_retract is being used to simulate something even vaguely logical, there has been some very interesting work done at Stony Brook on extending Prolog with some features of dynamic logic. Roughly speaking, there are three classes of predicates: pure predicates (don't depend on things that change) state predicates (depend on things that change, but change nothing) transition predicates (express a relation between states) For example, we might say p(X) :- <-fred(X)> q(X). p(X) is true in a world W if there is a world W1 such that -(fred(X), W, W1) -- roughly, retract and q(X) is true in W1. Note that this doesn't change W. Permanent changes are effected by the top level, where queries have the meaning "let W be the current world; if there is a world W1 and a substitution S such that the query arrives in W1 with substitution S, print S and make W1 the current world." I'm sorry that I can't remember the name of the person who was working on this. He promised me a copy of a paper about it, but it never arrived, so I can't give you a reference either. A pity, because this strikes me as the obviously right way to handle "local" data base updates, it's even related to an existing logic! It remains the case that converting an O(N**N) operation to an O(N!) -- which the original poster cited as motive for having back_retract/1 -- is scarcely worth the trouble.
micha@ecrcvax.UUCP (Micha Meier) (03/16/88)
In article <777@cresswell.quintus.UUCP> ok@quintus.UUCP (Richard A. O'Keefe) writes: >However, both versions of back_retract/1 have a rather more serious >failure mode. > >Consider > > back_retract(<clause>), > ... > !, > ... > fail > >By analogy with variable bindings, you would expect the back-retracted >clause to reappear. But the cut will have had the effect of pruning >away the "assert". Was it in 1983 or 1984 that I pointed this problem >out in the Prolog Digest, with reference to an 'assume' operation? 1983, but not really pointed out :-) This is only an implementation problem. If you, instead of using code to implement the backtrackable retract, push a suitable reference on the trail, then it works fine even with the cut, provided that you do not tidy up the trail (as described in the old engine). For some systems this might be too costly, as they use no special trail entries, but if the trail is already used to undo different things, that one is easily incorporated. You can get backtrackable assignment for free, then. Since several people here at ECRC asked me to provide backtrackable assert and retract, I assume that it can really be useful. One possible application is hypothetical reasoning - with the asserts and retracts you create alternative worlds, but you certainly want to be able to return to your universe. This is completely logical. Another area where it could be used is for database integrity constraints checking, or of course for theorem proving. Since all this stuff is logical, the above mentioned implementation can be used (there are no cuts), but sometimes it is desirable to have the old clause back at the position where it was before. --Micha
ok@quintus.UUCP (Richard A. O'Keefe) (03/17/88)
In article <516@ecrcvax.UUCP>, micha@ecrcvax.UUCP (Micha Meier) writes: > In article <777@cresswell.quintus.UUCP> ok@quintus.UUCP (Richard A. O'Keefe) writes: > > back_retract(<clause>), > > ... > > !, > > ... > > fail > This is only an implementation problem. The point of my message was that the "obvious" way of doing this in Prolog doesn't work. I think it was worth mentioning this because it is one of those old chestnuts that keeps popping up. Someone once kindly showed Quintus how to implement backtrackable global assignment; he'd put a fair bit of work into it, but the ( change /* exit */ ; /* redo */ undo_change, fail ) pattern was what he had used, so it didn't actually work. Credit: of the Prologs I know anything about, LM-Prolog has had undoable side-effects longest. I've seen a demo of LM-Prolog "undrawing" a picture, I think this was at IJCAI-83. The person who explained the technique of pushing arbitrary functions onto the trail to me was Preben Folkjaer, then of InterFace. Pushing functions on the trail is a neat trick, but it isn't a panacea. Consider the problem of I/O: you really don't want to push a function onto the trail for every character you write. Chris Moss has a paper, I don't know where it appeared, on undoable I/O, and when you can actually produce the output. > Since several people here at ECRC asked me to provide > backtrackable assert and retract, I assume that it can > really be useful. You should see the things people ask for in comp.lang.c! > One possible application is hypothetical > reasoning - with the asserts and retracts you create > alternative worlds, but you certainly want to be able to > return to your universe. I think Sanjay Manchanda's approach is much cleaner. Undo the side effect of an assert or retract? But <+X> and <-X> haven't GOT any side effects! For hypothetical reasoning in general, Ken Bowen's approach to meta-level reasoning is to make the alternative worlds explicit as data values; in a system like that you can reason about several worlds at once. If we can get logical arrays with acceptable efficiency (which would be useful in any case), it seems that the Manchanda & Bowen approaches need not be inferior in efficiency to the backtrackable side efffects approach. What happens if you are using a system with backtrackable side effects (such as back_retract) and the code aborts (explicit call to abort, or to some sort of error signalling operation)? Do the side-effects get unwound then? I'm not asking "how do you do it", but "what do you want it to do?" What happens if you have an AND-parallel system and one process executes back_retract, do the other processes notice the change or not? If you take the Bowen or Manchanda approach to data base updates, the answer is obvious: nothing is changing, you just get additional worlds. (The Manchanda approach may be easier for AND-parallel systems.)
tarau@ouareau.iro.umontreal.ca (Paul Tarau) (04/05/88)
A number of recent articles by Bart Demoen, Richard O'Keefe,
Sanjay Manchanda and Paul Voda re-opened the problem of the
"assert" family of primitives. I agree that we can do better without.
However, as much as we restrict us to a unique path of the Prolog
proof-tree, it is possible to manipulate their "soft" counterparts
safely and to implement them efficiently. I introduce these soft
counterparts in the context of an abstract data-type, namely a
"soft-database". They may be useful to do explicit hypothetical
resonning in Prolog.
Soft databases are sets of Prolog clauses having their
modifications undone on backtracking exactly as if they
were an ordinary Prolog data structure.
Soft databases contain chunks of executable Prolog code and/or
global "data".
Each one-argument operation on a soft-database is mapped in
a three-argument meta-logical operation using the following
transformation:
operation(SoftClause) ---> operation(SoftClause,OldDb,NewDb)
We can implement them simply by using the DCG preprocessor,
who can handle the last two arguments in a very nice way.
The basic idea is to meta-evaluate our program by a source-to-source
transformation of most of the operations on the soft database, using
a DCG driven term-expansion. On the other hand we must insure that
predicates which do not use meta-evaluation have not to pay for.
In the source file, clauses which use the soft database
operations are represented by the functor "-->" as in DCG rules.
This means that two additional arguments are appended at the end of
each predicate-term (say Db1 and Db2), to simulate the state of the
database before and after the call. However, if the predicate-term
is a variable, (say X), this gives "phrase(X,Db1,Db2)" and X will
be only expanded at run-time, when it becomes instantiated.
The "-->" functor may be interpreted declaratively
as a grammar based specification of the problem, or
procedurally as a description of soft data-base transformations.
The meta-circular evaluation mechanism works by chaining successive
states of the soft database.
"Assume" and "assume_last" work like asserta and assertz,
"forget" works like retract, but all their effect is backtrackable.
Previously assumed code in the database can be activated by using "wake-up".
The "{}" escape mechanism provides a call to Prolog without term-expansion.
The predicates "demo" and "wake_up" implement the meta-circular evaluation
mechanism.
This mechanism works as if it were a conventional meta-interpreter but
it is far more efficient, as only a limited amount of interpretation
is done (when compiled code explicitly uses wake-up).
In this case, the soft database is searched by "assumed" for a
matching clause, then "demo" is called and the body of the current
clause is traversed recursively, until a callable compiled predicate-term
is reached.
Then control is transferred to compiled code, which works as fast
as it can.
If one of the predicates which interact with the database
is called, it effectively uses the 2 hidden arguments
corresponding to the state of the database.
Suppose, for example that in the source code "assume(C)" is called.
The DCG expansion done at compile time insures that
"assume(C,Db1,Db2)" will execute, and if "Db1" is the state of
the database before, then "Db2" will be the state of the database
after assuming C.
/*------------------------ cut here -----------------------------------*/
% main predicate
go :- make_empty_database(_,Db),readloop(Db,_).
% definition of soft database operations
make_empty_database(_,Db):-new(Db). % the old database may be lost
assume(C)-->add_front(C). % like 'asserta'
assume_last(C)-->add_end(C). % like 'assertz'
forget(C)-->remove(C). % like retract, potentially non-monotonic operation
% retrieve a soft clause as data
assumed(C,D,D):-find(C,D). % like 'clause', but does not copy
% activate a soft-clause as code
wake_up(H) --> % works like 'call'
assumed(T),{copy_term(T,C)},
( {C = (H :- B)} -> demo(B) % match a clause with a body
; {C = H} % match a fact
).
% meta-circular interpreter
demo(C) -->
( {C=(P,Q)} -> (demo(P),demo(Q))
; {C=(I->T;E)} -> (demo(I) -> demo(T) ; demo(E))
; {C=(P;Q)} -> (demo(P);demo(Q))
; C
).
% hack for 'demo' to handle {} when evaluating itself
{C} --> {call(C)}.
getdb(Db,Db,Db). % gets the database as a term
setdb(Db,_,Db). % sets the database - the previous is lost
% failure driven printing of the assumed clauses
showdb -->
{write('Soft database:'),nl},getdb(Db),{numbervars(Db,0,_)},
assumed(C),{write(C),write('.'),nl,fail}
; {true}.
% simple read-eval loop
readloop -->
{nl,write('<READ LOOP>'),nl,write('| ?- '),read(X)},
( X -> {write('Yes.')}
; {write('No.')}
),{nl},readloop.
% difference list based implementation of
% soft database manipulation tools
% makes a new difference list
new(H:H).
% adds to the front of a difference list
add_front(X,H:T,[X|H]:T).
% adds to the end of a difference list
add_end(X,H:[X|T],H:T).
% finds an element of the difference list
find(X,H:T):- H\==T,
( H=[X|_]
; H=[_|H1],H1\==T,
find(X,H1:T)
).
% removes an element of the difference list
remove(X,L1:T,R1:T):- L1\==T,
( L1=[X|R1]
; L1=[Y|L2],L2\==T,R1=[Y|R2],
remove(X,L2:T,R2:T)
).
% As an example, let us consider the following solution
% to the N-queens problem:
queens(N) -->
make_free_positions(N),place(N),print_solution,{fail}
; {true}.
% makes a pool of free positions
make_free_positions(P) -->
{P>0} -> assume(free(P)),{P1 is P-1},make_free_positions(P1)
; {true} .
% try to place the queen Q
place(Q) -->
{Q>0} -> forget(free(P)),{S is Q+P, D is Q-P},% generate
not_under_attack(S,D), % test
{NewQ is Q-1},place(NewQ)
; {true}.
% tests if on diagonals S and D the queen is not under attack
% and assimilates the proposed position as the
% intersection of two diagonals
not_under_attack(S,D) -->
assumed(on_diagonals(S1,D1)),
{ S=S1
; D=D1
} -> {fail}
; assume(on_diagonals(S,D)).
% calculates the position of each queen and prints it
print_solution -->
assumed(on_diagonals(S,D)),{P is (S-D)//2,write(P),fail}
; {nl}.
The program was tested in Quintus Prolog 2.0, on a Sun 3.
To use it, type "go" and then something like "queens(8)" or
"assume(phone(mike,X)),assume(phone(mary,X)),showdb".
If soft databases will be of some interest on the net I will post some
less trivial applications to games and automated induction.
Paul Tarau