By staying with the simple data types, but adding recursion to this database language, one gets a language called (positive?) Datalog, which is the language underlying deductive databases. Deductive databases are an extension of relational databases which support more complex data modeling. In this section we will see how simple examples of deductive databases can be represented in Prolog, and we will see more of the limitations of Prolog.
A standard example in Prolog is a geneology database. An extensional
relation stores the parent
relation: parent(X,Y)
succeeds with X
and Y
if X
has parent Y
.
(Maybe do an example consisting of some English monarchs?) Given this
parent
relation, we can define the ancestor relation as
follows:
ancestor(X,Y) :- parent(X,Y). ancestor(X,Y) :- parent(X,Z), ancestor(Z,Y).This says that X has ancestor Y if X has parent Y; and X has ancestor Y if there is a Z such that X has parent Z and Z has ancestor Y. Given a definition for the parent relation as follows:
parent(elizabeth_II, charles_??). etc.we can query about ancestors, such as:
:- ancestor(elizabeth_II,X).and find all of Queen Elizabeth's ancestors.
This works very nicely in Prolog, since the parent graph is
(essentially) a tree. However, if we try the same definition of
transitive closure for a graph that is not acyclic like a tree, we can
be in trouble. Say we have a relation owes(X,Y)
which
indicates that X
owes money to Y
, and we want to define
a predicate
avoids(X,Y)
, meaning that X
tries to avoid running into
Y
. The definition is that people avoid someone to whom they
owe money, and they avoid anyone that someone to whom they owe money
avoids:
avoids(X,Y) :- owes(X,Y). avoids(X,Y) :- owes(X,Z), avoids(Z,Y).This definition has the same form as the ancestor definition. The problem here is that the
owes
relation may be cyclic. It is
possible for Andy to owe money to Bill, Bill to own money to Carl and
Carl to owe money to Bill:
owes(andy,bill). owes(bill,carl). owes(carl,bill).and if we ask who Andy avoids:
| ?- avoids(andy,X).we get:
| ?- avoids(andy,X). X = bill; X = carl; X = bill; X = carl; X = bill; X = carl; X = bill; ....an infinite loop.
If we would like to use Prolog as an engine for deductive databases, this shows up a serious problem: that a user can write a simple specification (using only atoms and variablse) and yet Prolog won't give an answer, but will wander off to (or toward) infinity. One couldn't afford to give such a system to a naive database user. There it is important that any query come back with some answer. Think of an SQL system that sometimes went into an infinite loop. It wouldn't be much used, and certainly not by naive users.
This problem of infinite looping is a well-known problem in Prolog and
Prolog programmers learn to program around it. The usual fix is to
add an extra argument to the avoids/2
predicate that keeps the
list of people encountered in the process of finding the avoidees.
Then if one of them is encountered again, the search is made to fail
at that point, since it is known that all avoidees from that one have
already been found. I.e.,
avoids(X,Y,L) :- owes(X,Y), \+ member(Y,L). avoids(X,Y,L) :- owes(X,Z), \+ member(Z,L), avoids(Z,Y,[Z|L]).Here we've used the Prolog predicate
member/2
, and the Prolog
builtin, \+
, which implements not. \+ member(Y,L)
succeeds just in case member(Y,L)
fails, and fails if it
succeeds. Now with this program and the corresponding query, we get:
| ?- avoids(andy,X,[]). X = bill; X = carl; no | ?-This fix works to avoid the infinite loop, but it sometimes has some undesirable properties. There are graphs for which the computation will be exponential in the number of arcs. Consider the case in which Andy owes money to Bill and Bob, and Bill and Bob owe money to Carl; Carl owes money to Dan and Dave, and Dan and Dave owe money to Evan; Evan owes money to Fred and Frank, and Fred and Frank owe money to George; and so on. The graph of the owes relation can be pictured as in Figure 2.2.
member
-enhanced version, too) would
recompute the same answers again and again. It would in effect turn
this (essentially) linear list into a tree.