There are two distinct semantics for retract/1:
- immediate update view: upon backtracking, retracted clauses can no longer be seen (they became invisible immediately).
- logical update view: upon backtracking, retracted clauses can still be seen (they became invisible only on the next predicate call). This update view is the ISO standard.
In the logical update view, for example, when the predicate remove/1 is called:
- First it sees
foo(a,b) and foo(b,a) and hence it retracts foo(b,a).
- Afterward, upon backtracking, it sees
foo(b,a) and foo(a,b) and hence it also retracts foo(a,b).
To solve the problem, you can use the ISO built-in predicate once/1 (which prevents backtracking).
:- dynamic foo/2.
foo(a,b).
foo(b,a).
foo(c,d).
foo(e,f).
foo(f,e).
remove :-
once( ( foo(A, B),
foo(B, A),
retract(foo(B, A)) ) ).
To retract only one symmetrical fact, you can ask:
?- listing(foo), remove, listing(foo).
:- dynamic foo/2.
foo(a, b).
foo(b, a). % <== only this fact is retracted!
foo(c, d).
foo(e, f).
foo(f, e).
:- dynamic foo/2.
foo(a, b).
foo(c, d).
foo(e, f).
foo(f, e).
true.
To retract all symmetrical facts, you can define:
remove_all_sym :-
( remove
-> remove_all_sym
; true ).
Example:
?- listing(foo), remove_all_sym, listing(foo).
:- dynamic foo/2.
foo(a, b).
foo(b, a). % <== this fact is retracted!
foo(c, d).
foo(e, f).
foo(f, e). % <== this fact is retracted!
:- dynamic foo/2.
foo(a, b).
foo(c, d).
foo(e, f).
NOTE A better alternative would be to avoid inserting symmetrical facts into the database:
assert_foo(A, B) :-
( foo(B, A)
-> true
; assertz(foo(A, B)) ).