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