Previous | Table of Contents | Next |
6.3.10.1. Using the Predicates
assert does basically what its name suggests; it asserts that some fact or rule now holds, which may not have held before. Making the predicate call assert(outformat(verbose)), for instance, has the effect of immediately making the predicate outformat true of the atom verbose, even if it was not true before.
Subsequent calls to the outformat predicate reflect this change. After the call, it will be as if we had the clause outformat(verbose) in the program from the beginning. Consider the predicate definition
prompt(N, NGname) :- outformat(verbose), !, write(N), write( articles in newsgroup ), writestring(NGname), write( -- read now? ). prompt(N, NGname) :- write(N), write( in ), writestring(NGname), write( -- read? ).
The predicate outformat is treated as any other predicate, even though it has been manipulated by assert and may never have appeared in the original program. If prompt is called just after assert(outformat(verbose)) has been called, only the first clause of prompt is used.
In fact, the assert/retract database is usually implemented as a separate entity from the rest of the program clauses, although it is referred to in exactly the same way as the program clauses.
retract does exactly the opposite of assert. retract(outformat(verbose)) deletes that clause from the assert/retract database. Immediately after the retraction of this fact, for instance, a call to the prompt predicate uses only the second clause. If the clause that is the argument of retract does not exist in the database, the call fails. Conversely, if the argument to retract contains free variables and one or more facts in the database match the argument, the first fact is matched, the variables are instantiated, and the clause is deleted. This can be useful for retrieving an old stored value; for instance, the following code implements a useful switch-setting predicate:
switch(Switchname, Newsetting) :- retract(switchvalue(Switchname, Oldsetting)), !, write(Old setting: ), write(Oldsetting), nl, assert(switchvalue(Switchname, Newsetting)), write(New setting: ), write(Newsetting), nl. switch(Switchname, Newsetting) :- write(Error: switch ), write(Switchname), write( not known), nl.
These examples illustrate one of the main uses of assert and retract. An interactive system might benefit from implementing some kind of switch to record (for instance) whether the user wants verbose or terse output. We could do this by passing the switch as a parameter to all predicates, but this would be somewhat tedious because typically only the low-level output predicates would have to know the information. It simplifies the code (at the cost of a little non-declarativeness) to assert the switch value.
6.3.10.2. Declaring Dynamic Predicates
Most Prolog systems, especially compilers, require programmers to declare their intention to assert and retract clauses of a particular predicate with the dynamic predicate. To allow the asserts and retracts, for instance, we can type the query dynamic outformat/1 to an interpreter. dynamic acts as a unary operator on a term of the form pred/arity, where pred is a predicate name and arity is a number indicating how many arguments it has.
The effect of this query can be achieved in a Prolog source file by including the following line in the file:
:- dynamic outformat/1.
The :- here is very important! If it is left out, then Prolog will think we are trying to give a clause for the predicate dynamic and we will probably get an attempt to redeclare error. As it is, we are asking Prolog to execute the goal dynamic outformat/1 in the course of its reading the source file.
In fact, any goal can be executed during source file processing in this way; for instance, having the expression :- write(Welcome to my system), nl. in a source file causes that string to be written out. However, the facility is most often used for the dynamic predicate and the op predicate (to be encountered soon).
6.3.10.3. Recording Persistent Data
Another main use of the assert/retract database is to record data that is maintained during a whole interpreter session. When we give an interpreter the query X = 42, it responds with X = 42 but then discards that instantiation of X when processing the next query. The assert and retract data, in contrast, persist from query to query.
One of the useful applications of this feature is to convert a Prolog interpreter into a command interpreter for a kind of command subsystem. For instance, we might decide to write a game-playing program by implementing one predicate, move, which refers to a dynamic predicate, current_board:
:- dynamic current_board/1. move(Move) :- retract(current_board(Curr_board)), board_after_move(Curr_board, Move, Mid_board), machine_response(Mid_board, Machine_move), board_after_move(Mid_board, Machine_move, New_board), print_board(New_board), assert(current_board(New_board)).
This predicate would remove the need for us to use the I/O predicates to interact with the user. The user knows that he has only to call the simple move predicate repeatedly in order to interact with the system.
We have encountered some examples of binary infix relations in Prolog, such as = and =.., and binary infix operators, such as + and /. We can declare such relations and operators ourselves; in fact, this facility is related to the representation of all goals and terms in Prolog.
Previous | Table of Contents | Next |