A list of facts on there own is not overly useful and could easily be placed within a database. What makes declarative powerful is the use of rules. A rule is another predicate, which has x number of atoms, which inside it has a collection of predicates which must be true. Consider the following rule-

grandfather(X,Z) :-

father(X,Y),

father(Y,Z).

What we are trying to say with this rule is that the father of my father must be my grandfather. However it may not be immediately obvious how this rule works. As such lets look at a simpler one so we can look at the syntax

male(bob)

male(fred)

hairy(bob)

 

hairyMale(X):-

male(X),

hairy(X).

This rule tells us who is both hairy and a male. The hairyMale() predicate has a single atom. This is the predicate which the rule will be called. The single atom is a variable. We use a variable here as we want to see which males are hairy. After the predicate there is a :- which simply indicates that this is a rule.

Inside the rule are two predicates. The first one is ended with a comma and the second one is ended with a full stop. The full stop says that the rule is ended and the comma says that there are more predicates to follow. Notice that both male and hairy both use the same variable.

Let's say we typed the following into the interpreter -

hairyMale(bob).

What the interpreter will now do is replace the X in hairyMale with the value bob. It will then do a search to see if a fact exists for male(bob) and hairy(bob). As they both exist the result should produce the answer Yes. If we did a search for hairyMale(fred) it should return the answer No. Finally if we did the search hairyMale(Z) it should return the answer Z = bob.

The last answer is interesting as what the interpreter has done is find all hairy males. As such it tries all of the male facts and all of the hairy facts and tries to combine them, illuminating the answers it can not use and reporting the correct answers. It uses a mechanism called backtracking in order to accomplish this. Backtracking will be looked at later in some detail.

grandfather(X,Z) :-

father(X,Y),

father(Y,Z).

Going back to the above rule we can see there are three variables

X - The grandsons name

Y - The fathers name

Z - The grandfathers name

The best way to understand this is to work through a typical query. Here is a typical query and its result.

We use the variable Z so it will return the persons grandfather. As such we start off with grandfather(fred,Z). The next step is to start matching the fathers. As there are only two fathers it only has two initially to consider.

The above search tree shows what logic the code is following. The letters underneath the atoms represent what variable they are to replace. The bold values are successful matches. The first predicate, father(X,Y) has already had X replaced with fred. This was passed in so for the rest of the search X will always be fred. As such when the search tried to match to bob it failed. Bob does not equal fred. So that line of search is removed. We then traverse back out of the search tree and try the left hand side. As fred equals fred we now can match Y to be bob. The 2 nd predicate rule then says that father(Y,Z) must be true. We now have a value for Y, bob, as such we are now looking for a fact which says father(bob,Z) where Z could be anything. We find that father(bob, barny) matches this. As such Z then becomes barny. Finally the result is passed up and the value of Z returned.