Logic Programming 1 - Overview
Logic Programming 1 - Overview
(An Overview)
Prepared By
Dr Augustine S. Nsang
An Overview of Prolog
Fig 1 below shows an example: a family relation. The fact that Tom is a parent of Bob
can be written in Prolog as:
parent(tom, bob).
pam tom
bob liz
ann
pat
jim
Here, we choose parent as the name of a relation: tom and bob are its arguments. For
reasons, that will become clear later, we write names like tom with an initial lowercase letter.
The whole family of Fig 1 is defined by the following Prolog program:
parent(pam, bob).
parent(tom, bob).
parent(tom, liz).
parent(bob, ann).
parent(bob, pat).
parent(pat, jim).
The program consists of six clauses. Each of these clauses declares one fact about the parent
relation.
When this program has been communicated to the Prolog system, Prolog can be
posed some questions about the parent relation. For example: Is Bob a parent to Pat? This
question can be communicated to the Prolog system by typing into the terminal:
?- parent(bob, pat).
Having found this as an asserted fact in the program, Prolog will answer:
yes
A further query can be:
?- parent(liz, pat).
Prolog answers
no
because the program does not mention anything about Liz being a parent of Pat. It also
answers ‘no’ to the question:
?- parent(tom, ben).
because the program has not even heard of the name Ben.
More interesting questions can also be asked. For example: Who is Liz’s parent?
?- parent(X, liz).
Prolog’s answer will not just be ‘yes’ or ‘no’ this time. Prolog will tell us what is the
(yet unknown) value of X such that the above statement is true. So the answer is:
X = tom
The question: Who are Bob’s children? can be communicated to Prolog as
?- parent(bob, X).
This time, there is more than just one possible answer. Prolog first answers with one
solution:
X = ann
We may now want to see other solutions. We can that say to Prolog (in many Prolog
implementations) by typing a semicolon, and Prolog will find other answers:
X = pat
If we request more solutions again, Prolog will answer ‘no’ because all solutions have
been exhausted.
Our program can be asked an even broader question: Who is the parent of whom?
Another formulation of this question is:
Find X and Y such that X is a parent if Y.
This is express in Prolog by:
?- parent(X, Y).
Prolog now finds all the parent child pairs, one after another. The solution will be
displayed one at a time as long as we tell Prolog we want more solutions, until all the
solutions have been found. The answers are output as:
X = pam
Y = bob;
X = tom
Y = bob;
X = tom
Y = liz;
We can stop the stream of solutions by typing, for example, a period instead of a
semicolon (this depends on the implementation of Prolog)
parent
Y grandparent
parent
Our example program can be asked still more complicated questions like:
Who is the grandparent of Jim?
As our program does not directly know the grandparent relation, this query has to be broken
down into two steps, as illustrated by Fig 2.
X = bob
X = pat
Yet another question could be: Do Ann and Pat have a common parent? This can expressed
again in two steps:
Our example program can easily be extended in many ways. Let us first add information
on the sex of the people that occur in the parent relation. This can be done by simply
adding the following facts:
female(pam).
male(tom)
male(bob)
female(liz)
female(pat)
female(ann)
male(jim).
The relations introduced here are male and female. These relations are unary (or one
place) relations. A binary relation (like parent) defines a relation between pairs of
objects; on the other hand, unary relations can be used to define simple yes/no properties
of objects. The first unary clause can be read: ‘Pam is female’. We could convey the
same information in two unary relations with one binary relation, sex, instead. An
alternative piece of program would then be:
sex(pam, feminine).
sex(tom, masculine).
sex(bob, masculine).
sex(liz, feminine)
.
.
.
We could define offspring in a similar way as the parent relation; that is, by simply
providing a list of simple facts about the offspring relation, each fact mentioning one pair of
people such that one is an offspring of the other. For example:
offspring(liz, tom).
However, the offspring relation can be defined much more elegantly by making use of the
parent relation i.e. by making use of the fact that it is the inverse of parent. This alternative
way can be based on the following logical statement:
For All X and Y, Y is an offspring of X if X is a parent of Y.
This formalism is already close to the formalism of Prolog. The corresponding Prolog clause
which has the same meaning is:
offspring(Y, X) :- parent(X, Y).
Prolog clauses such as the above are called rules. There is a very important difference
between facts and rules. A fact like:
parent(tom, liz).
specifies something that is always, unconditionally, true. On the other hand, rules specify
things that may be true if some condition is satisfied. Therefore, we say that rules have:
a condition part (the right hand side of the rule) and
a conclusion part (the left hand side of the rule).
The conclusion part is also called the head of the clause and the condition part is the body of
the clause. For example:
offspring(Y, X) :- parent(X, Y).
head body
How are rules actually used by Prolog? We will illustrate it by the following example.
Let us ask our program whether Liz is an offspring of Tom:
?- offspring(liz, tom).
There is no fact about offsprings in our program; therefore, the only way to consider this
question is to apply the rule about offsprings. The rule is general in the sense that it is
applicable to any objects X and Y; therefore, it can be applied to such particular objects as liz
and tom. To apply the rule to liz and tom, Y has to be substituted with liz, and X with tom.
We say that the variables X and Y become instantiated to:
X = tom and Y = liz.
After the instantiation, we have obtained a special case of our general rule. The special case
is:
offspring(liz, tom) :- parent(tom, liz).
The condition part has become:
parent(tom, liz).
Now, Prolog tries to find out whether the condition part is true. So the initial goal:
offspring(liz, tom)
has been replaced with the sub-goal
parent(tom, liz).
This new goal happens to be trivial, as it can be found as a fact in our program. This means
that the conclusion part of the rule is also true, and Prolog will answer the question with yes.
Let us now add more family relations to our example program. The specification of
the mother relation can be written in Prolog as:
mother(X, Y) :- parent(X, Y), female(X).
following the logical statement: “For All X and Y, X is the mother of Y if X is a parent of Y
and X is female”.
A comma between two conditions indicates the conjunction of the conditions,
meaning that both conditions have to be true.
Fig 3 below illustrates the sister relation, defined by:
“For any X and Y, X is a sister of Y if
i) both X and Y have the same parent, and
ii) X is female”
X Y
The key to the formulation was the use of predecessor itself in its definition. Such definitions
are, in general, referred to recursive definitions. Recursive programming is, in fact, one of
the fundamental principles of programming in Prolog. It is not possible to solve tasks of any
significant complexity in Prolog without the use of recursion.
Now, let’s put together all the pieces of our family program, which was extended
gradually by adding new facts and rules. The final form of the program is shown below (in
Fig 4). Looking at Fig 4, two further points are in order here: the first will introduce the term
procedure, the second will be about comments in programs.
The program in Fig 4 defines several relations: parent, male, female, predecessor etc. The
predecessor relation, for example, is defined by two clauses. We say that these two clauses
are about the predecessor relation. Sometimes, it is convenient to consider the whole set of
clauses about the same relation. Such a set of clauses is called a procedure.
In Fig 4, the rules about the predecessor relation have been distinguished by the
names ‘pr1’ and ‘pr2’, added as comments to the program. These names will be used later as
references to these rules. Like in Pascal (or any other programming language) comments are,
in general, ignored by the Prolog system. They only serve as further clarification to the
person who reads the program. They are distinguished in Prolog from the rest of the program
by being enclosed in special brackets ‘/*’ and ‘*/’. Thus comments in Prolog look like this:
/* This is a comment */
Another method, more practical for short comments, uses the percent character ‘%’.
Everything between ‘%’ and the end of the line is interpreted as a comment.
% This is also a comment
We know that parent(bob, pat) is a fact. Using this fact and rule pr1 we can conclude that we
can conclude predecessor(bob, pat). This is a derived fact; it cannot be found explicitly in
our program, but it can be derived from the facts and rules in the program. An inference step,
such as this, can be written in a more compact form as:
parent(bob, pat) predecessor(bob, pat).
This can be read: “From parent(bob, pat) it follows predecessor(bob,pat), by rule pr1”.
Further, we know that parent(tom, bob) is a fact. Using this fact, and the derived fact
predecessor(bob,pat), we can conclude predecessor(tom, pat), by rule pr2. We have thus
shown that our goal statement predecessor(tom, pat) is true. This whole inference process of
two steps can be written as:
parent(bob, pat) predecessor(bob, pat).
parent(tom, bob) and predecessor(bob, pat) predecessor(tom, pat).
We have thus shown what can be a sequence of steps that satisfy a goal – that is, makes it
clear that the goal is true. Let us call this a proof sequence. We have not, however, shown
how the Prolog system finds such a proof sequence.
Prolog finds the proof sequence in the reverse order to that we have used. Instead of
starting with simple facts in the program, Prolog starts with the goals and, using rules,
substitutes the current goal with the new goals until new goals happen to be simple facts.
Given the question:
?- predecessor(tom, pat).
Prolog will try to satisfy this goal. In order to do so, it will try to find a clause in the program
from which the above goal could immediately follow. Obviously, the only clauses relevant to
this end are rules pr1 and pr2. These are the rules about the predecessor relation. We say that
the heads of these rules match the goal.
The two clauses, pr1 and pr2, represent two alternative ways for Prolog to proceed.
Prolog first tries that clause which appears first in the program:
predecessor(X, Z) :- parent(X, Z).
Since the goal is predecessor(tom, pat), the variables in the rule must be instantiated
as follows:
X = tom; Z = pat
The original goal predecessor(tom, pat) is then replaced by a new goal:
parent(tom, pat).
There is no clause in the program whose head matches the goal parent(tom, pat). Therefore
this goals fails. Now, Prolog “backtracks” to the original goal in order to try an alternative
way to derive the top goal predecessor(tom, pat). The rule pr2 is thus tried:
predecessor(X, Z) :- parent(X, Y), predecessor(Y, Z).
As before, the variables X and Z become instantiated as:
X = tom; Z = pat
But Y is not instantiated yet. The top goal predecessor(tom, pat) is replaced by two
goals:
parent(tom, Y) and predecessor(Y, pat).
Being now faced with two goals, Prolog tries to satisfy them in the order that they are
written. The first one is easy as it matches one of the facts in the program. The matching
forces Y to become instantiated to bob. Thus the first goal has been satisfied, and the
remaining goal has become:
predecessor(bob, pat).
To satisfy this goal, the rule pr1 is used again. Note that this (second) application of the same
rule has nothing to do with its previous application. Therefore, Prolog uses a new set of
variables in the rule each time the rule is applied. To indicate this we shall rename the
variables in rule pr1 for this application follows:
predecessor(X’, Z’) :- parent(X’, Z’).
The head has to match our current goal predecessor(bob, pat). Therefore,
X’ = bob, Z’ = pat.
The current goal is then replaced by:
parent(bob, pat).
This goal is immediately satisfied because it appears in the program as a fact. This completes
the execution trace!