assert can be used in machine learning
programs, program which learn new facts as they progress.
In some Prolog implementations you have to specify
whether a certain clause is dynamic (new clauses
can be added to the database during execution) or
static:
This means that we can add and remove clauses with five
arguments whose functor is hanoi.
retract(X) removes the first
clause that matches X.
assert and retract behave differently
on backtracking. When we backtrack
through assert nothing happens.
When we backtrack to retract Prolog continues
searching the database trying to find another matching clause.
If one is found it is removed.
If the argument to retract(clause(X)) contains some
uninstantiated variables they will be instantiated.
retract(X) fails when no matching
clause can be found.
The goal clause(X, Y) instantiates X to the
head of a goal (the left side of :-) and Y to
the body.
X can be just a variable (in which case it will
match all the clauses in the database), a fully
instantiated (ground) term, or a term which
contains some uninstantiated variables.
Normally we represent a data structure using a combination of
Prolog lists and structures.
A graph can for example be represented as a list
of edges, where each edge is represented by a binary structure:
However, it is also possible to use clauses to represent
data structures such as lists, trees, and graphs.
It is usually not a good idea to do this, but sometimes it is
useful, particularly when we are faced with a static data
structure (one which does not change, or changes very little).
In general it is a bad idea to represent data in this way.
Inserting and removing data has to be done using assert
and retract, which are fairly expensive operations.
However, in Prolog implementations which support clause
indexing, storing data in clauses gives us a way to access
information directly, rather than through sequential
search.
The reason for this is that indexing uses hash tables to
access clauses.
In some cases it is a good idea to use global data rather than
passing it around as a parameter.
Assume we want to be able to switch
between short and long error messages. Instead of
extending every clause by an extra
parameter (clumsy and inefficient) we
use a global switch.
The first clause in turnon
will fire if the switch is already turned on.
The first clause in turnoff fails if
Switch was already off.
The first clause in flip fails if
Switch was turned off, in which case
the second clause fires and the switch is turned on.
Many recursive program are extremely inefficient because they solve the
same subproblem several times.
In dynamic programming the idea is
simply to store the results of a computation in a table, and when
we try to solve the same problem again we retrieve the value from
the table rather than computing the value once more.
There is a variation of dynamic programming known as memoization.
I'm sure you've heard of the Towers of Hanoi problem. It is one
first year computer science students are tortured with to no end.
The problem is to move a number of disks from a peg A to a peg B,
using a peg C as intermediate storage. Additionally, we are only
allowed to put smaller disks onto larger disks.
A recursive solution of the problem to move disks from to
is as follows:
If we want to store data between different top-level queries, then
using the database is our only option.
In the following example we want to generate new atoms.
In order to make this work, gensym has to store the number
of atoms with a given prefix that it has generated so far. The
clause current_num(Root, Num) is used for this purpose.
There is one current_num clause for each kind of
atom that we generate.