How Prolog's logical update view works for assert and retract?
Asked Answered
E

2

5

Can someone please explain the Prolog logical view about assert and retract in details?

For example in code below, in the first run Prolog returns true and in subsequent runs returns false. I don't know why because of Prolog logical view when asserta(nextBound(100)) satisfies, nice(X) is still frozen with values when it started so this change should be ignore and nextbound(100) must be false.

nextBound(10000).

nice(X) :-
   asserta(nextBound(100)),
   retract(nextBound(10000)),
   nextBound(100).
Estheresthesia answered 23/1, 2015 at 18:18 Comment(0)
D
5

You can do a trace to determine what happens:

| ?- nice(_).
      1    1  Call: nice(_17) ?
      2    2  Call: asserta(nextBound(100)) ?
      2    2  Exit: asserta(nextBound(100)) ?   <-- 1st assert of netBound(100) succeeds
      3    2  Call: retract(nextBound(10000)) ?
      3    2  Exit: retract(nextBound(10000)) ? <-- retract nextBound(10000) succeeds
      4    2  Call: nextBound(100) ?
      4    2  Exit: nextBound(100) ? <-- Succeeds because netBound(100) now a fact
      1    1  Exit: nice(_17) ?

(1 ms) yes
{trace}
| ?- nice(_).
      1    1  Call: nice(_17) ?
      2    2  Call: asserta(nextBound(100)) ?
      2    2  Exit: asserta(nextBound(100)) ?   <-- 2nd assert of netBound(100) succeeds
      3    2  Call: retract(nextBound(10000)) ?
      3    2  Fail: retract(nextBound(10000)) ? <-- retract nextBound(10000) fails
      1    1  Fail: nice(_17) ?

(3 ms) no
{trace}
| ?-

You can see that, in the first case, first the nextBound(100) fact is successfully asserted (for the 1st time). Then, the retract(nextBound(10000)) succeeds because nextBound(10000). is a fact which exists in the data. Following that, the query nextBound(100) succeeds because two steps before this fact was asserted into the data.

On the second execution of nice(_), nextBound(10000) doesn't exist because it was retracted in the first execution, and the code doesn't re-assert it. So, in the second execution of nice(_), retract(nextBound(10000)) fails because the fact nextBound(10000) doesn't exist and the entire second execution of nice(_) fails at that point since backtracking over asserta and retract do not re-execute and produce additional results.

A listing shows that now there are two nextBound(100) facts, since we've asserted one in each of the two runs of nice(_), and no nextBound(10000) since it was retracted in the first run of nice(_):

| ?- listing.

% file: user

nice(_) :-
        asserta(nextBound(100)),
        retract(nextBound(10000)),
        nextBound(100).

% file: user_input

nextBound(100).
nextBound(100).

(1 ms) yes
| ?-

The logical update view, as stated in the SWI documentation, says, Starting with SWI-Prolog 3.3.0 we adhere to the logical update view, where backtrackable predicates that enter the definition of a predicate will not see any changes (either caused by assert/1 or retract/1) to the predicate.

In other words, the logical update view prevents the predicate from dynamically changing itself while it's executing. That's not the scenario here.

In fact, it's critical in Prolog that, during the execution of a predicate, if you assert a fact at one point in the predicate, that result must be visible in it immediately, or the predicate may not be able to function properly. There are many many common library predicates that rely on this behavior.

Dramatization answered 23/1, 2015 at 18:42 Comment(5)
thank sir, but i think that because of logical view of prolog "nextBound(100)" should evaluate 'false' by nice() in first execution because asserta(nextBound(100)) done in nice() predicate and nice(_) is frozen on that time.isn't it?Estheresthesia
@Estheresthesia as you can see in the trace, nextBound(100) is evaluated and succeeds in the first execution of nice(_) as expected because it has been asserted before it is queried.Dramatization
@CoderInNetwork, to be clear, when nice(_) is executed, it doesn't operate on a frozen state of the database. The database is dynamic during the course of execution. Any facts asserted or retracted are done so immediately and subsequent queries see those results. Have I answered your question?Dramatization
dear lurker Your answer is clear but isn't that in contraction with this ?swi-prolog.org/pldoc/man?section=updateEstheresthesia
@Estheresthesia I don't think it's a contradiction. The comment on that link says, Starting with SWI-Prolog 3.3.0 we adhere to the logical update view, where backtrackable predicates that enter the definition of a predicate will not see any changes (either caused by assert/1 or retract/1) to the predicate. In other words, a predicate cannot change it's own definition dynamically. The definition of the predicate itself is static with respect to changes to that predicate in the database. If this were not the case, then it would be a bit chaotic.Dramatization
S
5

To start from a historical view, the logical update view has been first implemented in Quintus 2.0 (whose current successor is SICStus) and described in the literature in 1987. It has been adopted in ISO Prolog ISO/IEC 13211-1:1995. The principal idea is that any goal of a dynamic predicate will take exactly those clauses into account that are present at the point in time when the goal is executed. Any further changes — be they additions or deletions — are not taken into account for the execution of that very goal.

Prior to the logical update view, there have been various more or less consistent implementations, most were incompatible with various optimizations for Prolog systems. Note that the difference only shows if you have goals that may have more than one answer. Either as a simple goal, or when using retract and you are using retract/1 or assertz/1. The difference does not show when using asserta/1 only. So your example cannot clarify the differences.

Consider a dynamic predicate p/1. Since the following interaction is using the toplevel only, I will make p/1 known to the system by asserting a fact and immediately retracting all facts of p/1. Also, I will remove all facts with retractall(p(_)) before starting the next query.

?- asserta(p(1)).
   true.  % now p/1 is known to the system.
?- retractall(p(_)), assertz(p(1)), p(X), assertz(p(2)).
   X = 1.  % only one answer!
?- retractall(p(_)), assertz(p(1)), p(X), assertz(p(2)), p(Y).
   X = 1, Y = X
;  X = 1, Y = 2.

So the first goal p(X) sees only p(1), whereas the second goal p(Y) sees both. This applies for any active goal:

?- retractall(p(_)), assertz(p(1)), assertz(p(2)), p(X), assertz(p(3)), p(Y).
   X = 1, Y = X
;  X = 1, Y = 2
;  X = 1, Y = 3
;  X = 2, Y = 1
;  X = 2, Y = X
;  X = 2, Y = 3
;  X = 2, Y = 3
;  false.

Again, note that X is only 1 or 2 but not 3.

Alternatively, you can imagine that each goal p(X) is replaced by:

... findall(Xi, p(Xi), Xis), member(X, Xis) ...

This shows you a bit the idea behind: Conceptually, all answers are stored temporally, and only then each answer is shown.

Er, above is not quite true, for only the clauses of p/1 are handled this way. That is, as long as you are storing only facts, above explanation is perfect, but should you also store rules, you would need a more complex explanation, roughly:

 ... findall(Xi-Bi, clause(p(Xi),Bi), XiBis), member(X-B,XiBis), B ...

And, again, this is not the plain truth, for some more exotic issues like cuts might intervene. I will leave it that way for the moment1.

Similarly, retract/1 will also see and delete the clauses it sees at the point in time of executing it. For most situations this is quite intuitive and agrees with our expectations. Nevertheless, there are quite absurd situations as the following:

?- retractall(p(_)),
   assertz(p(1)), assertz(p(2)),
   retract(p(X)), ( X = 1, retract(p(Y)) ; X = 2, Y = none ).
   X = 1, Y = 2
;  X = 2, Y = none.

Here, the fact p(2) was deleted twice, although the data base contained only a single fact p(2).


Footnotes

1 Actually, replace

... p(X) ...

by

... findall(Xi-Bi, clause(p(Xi),Bi), XiBis), answs_goal_x(XiBis,X, G), G ...

with

answs_goal_x([], _, true).
answs_goal_x([Xi-Bi|XiBis], X, ( X = Xi, Bi ; G) ) :-
   answs_goal_x(XiBis, X, G).
Seek answered 23/1, 2015 at 19:41 Comment(1)
Thanks for adding this answer. The historical info and examples are quite helpful. (+1)Dramatization

© 2022 - 2024 — McMap. All rights reserved.