2
votes

I'm trying to understand what assert and retract do with the term they are passed. If I run the following:

?- assertz(item(first)).
true.

?- assertz(item(second)).
true.

?- assertz((item(Y) :- =(Y, third), writeln(Y))).
true.

?- retract(item(X)).
X = first ;
X = second ;
false.

retract/1 removes all of the items but my writeln/1 is never called, so it appears retract is not actually resolving the term it is passed. It looks like it is doing some special operation where it:

  • Unifies the term with only facts (i.e. rules with no tail) in the database
  • Retracts each one after applying the substitution from unification

Is this right? Or is something else happening here?

Said another way: If I write my own single argument rule, Prolog does not automatically unify item(X) with the database and iterate through all facts that are item/1. It just gives me item(X). How is retract doing its magic?

?- assertz((myRule(X) :- writeln(X))).
true.

? myRule(item(X)).
item(_2556)
true.

Answer with further clarification: Based on the answer by User9213, it appears that the answer is "retract (but not assert!) has a funny behavior where it does a simple unification of its term before it does something to the database". I'm left wondering why other built-in functions that I've seen (like atom/1 and writeln/1) don't appear to do this. Maybe it is more common than I've experienced?

For consistency, it seems like retract should have required ground terms and if the user wanted to do something like my above example, they could have done this:

?- (item(X), retract(item(X))).

It all makes me think I'm missing something or maybe these built in functions were just inconsistently designed? Any clarification that would explain that would be great.

3
While =(Y, third) works, it's a lot less readable than it's equivalent Y = third, so I'd encourage you to ignore the "canonical" representation of such things and use the more clear one if possible. I think in this case you were trying to avoid some kind of body-inlining optimization, but I don't think there is anything like that; if anything, it probably works the other way around (try asserta(foo), clause(foo, Body) and you'll get Body = true back, implying it was equal to foo :- true.)Daniel Lyons
You mean retract/1 not revert/1, right?lurker
Yes, sorry! Fixing...Eric Zinda
Note that unless you call item(third), you're never going to activate the body of the predicate you've added to the dynamic store. Remember that Prolog is not an expression-based language, it does not evaluate nested terms. If you want to call it and then retract it, you have to do item(third), retract(item(third)) or item(X), retract(item(X)) because the argument to retract/1 is a term.Daniel Lyons
I was actually trying to test if the language was "resolving" the goal item(X) in the implementation of retract/1 when retract(item(X)) was called. If it was resolving it, then item(X) would unify with the rule item(Y) :- ..., Y would get assigned item(third) via the unification in the rule, and writeln(Y) would get called. Just like if I ran the query item(X) after asserting the items in that example. Or that was my intention at least.Eric Zinda

3 Answers

2
votes

I had a vague memory of there being a retractall/1 that lives alongside retract/1 and it turns out the reason for it is situations like this. retract/1 accepts a Prolog term, as the documentation states:

retract(+Term)

When Term is an atom or a term it is unified with the first unifying fact or clause in the database. The fact or clause is removed from the database.

Emphasis added by me. This is illustrated by the following:

?- asserta(foo(X) :- X = 2 ; X = 4).
true.

?- foo(X).
X = 2 ;
X = 4.

?- retract(foo(X)).
false.

?- foo(X).
X = 2 ;
X = 4.

?- retract(foo(X) :- X = 2 ; X = 4).
true.

?- foo(X).
false.

Note that if you furnish the complete clause you gave to asserta/1, it will be retracted. As @CapelliC points out below, you can also furnish a clause with a variable as a parameter to retract things with bodies:

retract(foo(X) :- Y).

However, if you do want to retract things that match a pattern, you can use retractall/1, which the documentation states:

retractall(+Head)

All facts or clauses in the database for which the head unifies with Head are removed.

This is illustrated by the following:

?- asserta(foo(X) :- X = 2 ; X = 4).
true.

?- foo(X).
X = 2 ;
X = 4.

?- retractall(foo(X)).
true.

?- foo(X).
false.

Another important difference between these two is that retract/1 will remove one thing at a time, and it will unify those things:

?- asserta(foo(X) :- X = 2).
true.

?- asserta(foo(X) :- X = 4).
true.

?- retract(foo(X) :- Y).
Y =  (X=4) ;
Y =  (X=2) .

This is in contrast to retractall/1 which will remove everything that matches the pattern, without unifying anything:

?- asserta(foo(X) :- X=2).
true.

?- asserta(foo(X) :- X=4).
true.

?- foo(X).
X = 4 ;
X = 2.

?- retractall(foo(X)).
true.

?- foo(X).
false.

So, retract/1 is really for doing one-at-a-time retractions but expects something shaped like the term you asserted, whereas retractall/1 only wants a head, treats it as a pattern, and will remove as many things as match the pattern.

This certainly helped me improve my understanding of Prolog, so I hope it helps you as well!

2
votes

To add my voice to the chorus, yes, all these predicates (assert*, retract*) manipulate the Prolog database of facts and rules; they do not evaluate their arguments as if they were facts or rules. Yes, they just do "syntactic" unification between their argument and the facts and rules in the database.

Prolog is a homoiconic language. The program can be manipulated as if it was data; the data structures can be interpreted as if they were the program. Most importantly: whether a certain structure is data or program depends only on the context.

You seem to understand this, but for the next poor soul that stumbles across your question and this answer, here is a toy example to demonstrate.

There is a built-in predicate atom/1 that succeeds if its argument is, well, an atom:

?- atom(foo).
true.

?- atom(Bar). % Bar is a free variable, which is not an atom
false.

?- atom(atom(bar)). % atom(bar) is a compound term, which is not an atom
false.

But atom(bar) is just a compound term in the last query. It is a compound term with a functor atom/1. Its only argument is an atom. So if you now queried it:

?- atom(bar).
true.

There is also this really nice predicate called call. It makes it even easier to blur the lines between program and data. The following three queries are identical in meaning:

?- atom(bar).
true.

?- call(atom, bar).
true.

?- call(atom(bar)).
true.

In addition, you can dynamically (at run-time) create a data structure and evaluate it as program. Here is a naive implementation of call/2 that can evaluate predicates, provided a predicate name and a list of arguments. It constructs the compound term using "univ" and evaluates it by just placing it in a slot where a subgoal is supposed to be. I'll call it my_call/2 and I will add it to the database using assertz:

?- assertz((my_call(Name, Args) :- Goal =.. [Name|Args], Goal)).
true.

?- my_call(between, [1, 3, X]).
X = 1 ;
X = 2 ;
X = 3.

Do you see what is going on? (Note: it seems that call is a relatively new addition to Prolog. Before call was widely available, you had to do this trick if you wanted to meta-call predicates. I don't know this from experience, just educated guessing based on things I've read or heard and can't be bothered to cite right now.)


So, let's try that again. In reality things are more complicated, but very simplified:

A Prolog program is a set of predicates. Not ordered!

Each predicate is a list of clauses. It is a list because a predicate may have 0 or more clauses, and they have order.

A clause can be a fact or a rule.

A rule is a compound term with functor :-/2. Yes, this is right. To see what I mean:

?- assertz(:-(foo(X), between(1, 3, X))).
true.

?- listing(foo/1).
:- dynamic foo/1.

foo(A) :-
    between(1, 3, A).

true.

This is just another way to write it. It is more conventional.

So indeed, these two are very different:

foo(X). % a fact
foo(X) :- between(1, 3, X). % a rule

You cannot unify the one with the other. This is why, if you want to retract foo(X) :- between(1, 3, X), you cannot just pass foo(X) as the argument to retract. Or, to complete your example:

?- assertz(item(first)).
true.

?- assertz(item(second)).
true.

?- assertz((item(Y) :- =(Y, third), writeln(Y))).
true.

?- retract(item(X)).
X = first ;
X = second ;
false.

?- retract((item(X) :- X = Y, writeln(X))).
Y = third.

Do you see it now?

1
votes

The definition of assert in swi-prolog is:

Assert a clause (fact or rule) into the database. The predicate asserta/1 asserts the clause as first clause of the predicate while assertz/1 assert the clause as last clause. The deprecated assert/1 is equivalent to assertz/1. If the program space for the target module is limited (see set_module/1), asserta/1 can raise a resource_error(program_space) exception. The example below adds two facts and a rule. Note the double parentheses around the rule.

Hence, it does not call or infer anything! It's just an assertion for the fact and the rule into the active memory.