The Art of Prolog
The Art of Prolog
The Art of Prolog
Leon Sterling
Case Western Reserve University
Ehud Shapiro
The Weizmann Institute of Science
Seventhprinting , 1991
@ 1986bytheMassachusetts
Instituteof Technology
All rights reserved . No part of this book may be reproduced in any form by any
electronic or mechanical means (including photocopying , recording , or information
storage and retrieval ) without permission in writing from the publisher .
Sterling, Leon.
The Art of Prolog.
-~ -~
ililill
~
~-
To Ruth, Miriam, Michal, and Danya
Contents
Foreword Xl
Preface XIV
Introduction xviii
1 . 1 Facts
2
1 . 2 Queries 3
1 . 4 Existential queries 5
1 . 5 Universal facts
6
1 . 7 Rules
8
1 . 10 Summary 16
cCX
3.3 Composing recursive programs
3.4 Binary trees
3.5 Manipulating symbolic expressions
3.6 Background
Chapter 8: Arithmetic
121
284
Chapt ~r 18: Se~ ch ,Techniques
18.1 Searching state- space graphs 284
18.2 Searching game trees 295
18.3 Background 302
Appendix 399
A . Working with Prolog 399
B . System Predicates 400
C . Predefined Operators 405
Index 425
Foreword
by David HiD . Warren
University of Manchester
I shall never forget my first Prolog program . The time was early 1974 . I
had learnt about the abstract idea of logic programming from Bob Kowalski at
Edinburgh , although the name " logic programming " had not yet been coined .
The main idea was that deduction could be viewed as a form of computation , and
P if Q and R and S .
Now I had been invited to Marseille . Here , Alain Colmerauer and his colleagues
had devised the language Prolog based on the logic programming conceptSome -
how , this realization of the concept seemed to me , at first sight , too simple - minded .
However Gerard Battani and Henri Meloni had implemented a Prolog interpreter
in Fortran ( their first major exercise in programming , incidentally ) . Why not give
Prolog a try ?
IBM machine far away in Grenoble . I typed in some rules defining how plans could
on the SRI planner Strips ~ which described how a plan could be elaborated by
adding an action at the end . Another rule , necessary for completeness , described
example for the planner to work on , I typed in facts about some simple actions
XII Foreword
in a "blocks world " and an initial state of this world . I entered a description of a
goal state to be achieved . Prolog spat back at me:
?
meaning it couldn 't find a solution . Could it be that a solution was not deducible
from the axioms I had supplied ? Ah , yes, I had forgotten to enter some crucial
facts . I tried again . Prolog was quiet for a long time and then responded :
DEBORDEMENT DE PILE
Stack overflow ! I had run into a loop . Now a loop was conceivable since the space
of potential plans to be considered was infinite . However , I had taken advantage
of Prolog 's procedural semantics to organize the axioms so that shorter plans
ought to be generated first . Could something else be wrong ? After a lot of head
scratching , I finally realized that I had mistyped the names of some variables . I
corrected the mistakes , and tried again .
Lo and behold , Prolog responded almost instantly with a correct plan to
achieve the goal state . Magic ! Declaratively correct axioms had assured a correct
result . Deduction was being harnessed before my very eyes to produce effective
computation . Declarative programming was truly programming on a higher plane !
I had dimly seen the advantages in theory . Now Prolog had made them vividly
real in practice . Never had I experienced such ease in getting a complex program
coded and running .
Of course , I had taken care to formulate the axioms and organize them in
such a way that Prolog could use them effectively . I had a general idea of how
the axioms would be used. Nevertheless it was a surprise to see how the axioms
got used in practice on particular examples . It was a delightful experience over
the next few days to explore how Prolog actually created these plans , to correct
one or two more bugs in my facts and rules , and to further refine the program .
Since that time , Prolog systems have improved significantly in terms of de-
bugging environments , speed, and general robustness . The techniques of using
Prolog have been more fully explored and are now better understood . And logic
programming has blossomed , not least because of its adoption by the Japanese a...,
the central focus of the Fifth Generation project .
After more than a decade of growth of interest in Prolog , it is a great pleasure
to see the appearance of this book . Hitherto , knowledge of how to use Prolog
for serious programming has largely been communicated by word of mouth . This
textbook sets down and explains for the first time in an accessible form the deeper
principles and techniques of Prolog programming .
Foreword Xlll
The book is excellent for not only conveying what Prolog is , but also explaining
how it should be used. The key to understanding how to use Prolog is
to properly understand the relationship between Prolog and logic programming .
This book takes great care to elucidate the relationship .
Above all , the book conveys the excitement of using Prolog - the thrill
of declarative programming . As the authors put it "declarative programming
clears the mind " . Declarative programming enables one to concentrate on the
essentials of a problem , without getting bogged down in too much operational
detail . Programming should be an intellectually rewarding activity . Prolog helps
to make it so. Prolog is indeed , as the authors contend , a tool for thinking .
Preface
The origins of this book lie in graduate student courses aimed at teaching
advanced Prolog programming . There is a wealth of techniques that has emerged
in the fifteen years since the inception of Prolog as a programming language . Our
intention in this book has been to make accessible the programming techniques
that kindled our own excitement , imagination and involvement in this area.
The book fills a general need. Prolog , and more generally logic programming ,
have received wide publicity in recent years . Currently available books and accounts
, however , typically describe only the basics . All but the simplest examples
of the use of Prolog have remained essentially inaccessible to people outside the
Prolog community .
We emphasize throughout the book the distinction between logic programming
and Prolog programming . Logic programs can be understood and studied ,
using two abstract , machine independent concepts : truth and logical deduction ;
One can ask whether an axiom in a program is true , under some interpretation of
the program symbols ; or whether a logical statement is a consequence of the pro ~
gram . These questions can be answered independently of any concrete execution
mechanism .
"
'
AX
xvi Preface
to write elegant short programs but have difficulty in building a major program .
The applications covered are game - playing programs , a prototype expert system
for evaluating requests for credit , a symbolic equation solver and a compiler .
During the development of the book , it has been necessary to reorganize the
foundations and basic examples existing in the folklore of the logic programming
community . Our structure constitutes a novel framework for the teaching of
Prolog .
Material from this book has been used success fully for several courses on
logic programming and Prolog : in Israel , the United States and Scotland . The
material more than suffices for a one semester course to first - year graduate students
or advanced undergraduates . There is considerable scope for instructors to
particularize a course to suit a special area of interest .
For the student who is studying the material on her own , we strongly advise
reading through the more abstract material in Part I . A good Prolog programming
style develops from thinking declaratively about the logic of a situation . The
theory in Chapter 5 , however , can be skipped until a later reading .
The exercises in the book range from very easy and well - defined to difficult
and open - ended . Most of them are suitable for homework exercises . Some of the
Preface XVII
The " code in this book is essentially in Edinburgh Prolog . The course has
been given where students used several different variants of Edinburgh Prolog ,
and no problems were encountered . All the examples run on Wisdom Prolog ,
We acknowledge and thank the people who contributed directly to the book .
We also thank , collectively and anonymously , all those who indirectly contributed
by Lawrence Byrd , Oded Maler , Jack Minker , Richard O ' Keefe , Fernando Pereira ,
material from the book was being debugged . The first author acknowledges students
University , and Case Western Reserve University . The second author taught
We are grateful to many people for assisting in the technical aspects of producing
drafts and camera - ready copy , above and beyond the call of duty . This book may
not have appeared without her tremendous efforts . Arvind Bansal prepared the
index and helped with the references . Yehuda Barbut drew most of the figures .
Max Goldberg and Shmuel Sarra prepared the appendix . The publishers , MIT
The inception of logic is tied with that of scientific thinking . Logic provides a
precise language for the explicit expression of one's goals, knowledge , and assumptions
. Logic provides the foundation for deducing consequences from premises ;
for studying the truth or falsity of statements given the truth or falsity of other
statements ; for establishing the consistency of one's claims ; and for verifying the
validity of one's arguments .
Computers are relatively new in our intellectual history . Similar to logic ,
they are both the object of scientific study , and a powerful tool for the advancement
of scientific endeavor in general . Like logic , computers require a precise
and explicit statement of one's goals and assumptions . Unlike logic , which has
developed with the power of the human thinking as the only external consideration
, the development of computers has been governed from the start by severe
technological and engineering constraints . Although computers were intended for
use by humans , the difficulties in constructing them were so dominant , that the
language for expressing problems to the computer and instructing it how to solve
them was designed from the perspective of the engineering of the computer alone .
Almost all modern computers are based on the early concepts of yon Neu-
mann and his colleagues , which emerged during the 1940's. The yon Neumann
machine is characterized by a large uniform store of memory cells, and aprocessing
unit with some local cells, called registers . The processing unit can load data
from memory to registers , perform arithmetic or logical operations on registers ,
and store values of registers back into memory . A program for a yon Neumann
machine consists of a sequence of instructions to perform such operations , and an
additional set of control instructions , which can affect the next instruction to be
executed , possibly depending on the content of some register .
As the problems of building computers were gradually understood and solved ,
the problems of using them mounted . The bottleneck ceased to be the inability
of the computer to perform the human 's instructions , but rather the inability of
the human to instruct , or program , the computer . A search for programming
Introduction xix
languages convenient for humans to program in has begun . Starting from the
language understood directly by the computer , the machine language , better notations
and formalisms were developed . The main outcome of these efforts was
languages that were easier for humans to express themselves in , but still mapped
rather directly to the underlying machine language . Although increasingly abstract
, the languages in the mainstream of development , starting from assembly
language , through Fortran , Algol , Pascal , and Ada , all carried the mark of the
underlying machine - the von Neumann architecture .
To the uninitiated intelligent person , who is not familiar with the engineering
constraints that lead to its design , the von Neumann machine seems an arbitrary ,
even bizzare , device . Thinking in terms of its constrained set of operations is a
non -trivial problem , which sometimes stretch es the adaptiveness of the human
mind to its limits .
Rather , we think that programming can be , and should be , part of the problem
solving process itself ; that thoughts should be organized as programs , so that
consequences of a complex set of assumptions can be investigated by "running "
the assumptions ; that a conceptual solution to a problem should be developed
hand -in -hand with a working program that demonstrates it and exposes its different
aspects . Suggestions in this direction have been made under the title "rapid
prototyping ."
xx Introduction
To achieve this goal in its fullest - to become true mates of the human
thinking process - computers have still a long way to go. However , we find it both
appropriate and gratifying from a historical perspective that logic , a companion
to the human thinking process since the early days of human intellectual history ,
has been discovered as a suitable stepping -stone in this long journey .
Although logic has been used as a tool for designing computers , and for
reasoning about computers and computer programs since almost their beginning ,
the use of logic directly as a programming language ) termed logic programming ,
is quite recent .
Logic programming , as well as its sister approach , functional programming ,
departs radically from the mainstream of computer languages . Rather then being
derived , by a series of abstractions and reorganizations , from the von Neumann
machine model and instruction set , it is derived from an abstract model , which
has no direct relationship or dependency to one machine model or another . It is
based on the belief that instead of the human learning to think in terms of the
operations of a computer , which some scientists and engineers at some point in
history happened to find easy and cost-effective to build , the computer should
perform instructions that are easy for humans to provide . In its ultimate and
purest form , logic programming suggests that even explicit instructions for operation
not be given but , rather , the knowledge about the problem and assumptions
that are sufficient to solve it be stated explicitly , as logical axioms . Such a set
of axioms constitutes an alternative to the conventional program . The program
can be executed by providing it with a problem , formalized as a logical statement
to be proved , called a goal statement . The execution is an attempt to solve the
problem , that is , to prove the goal statement , given the assumptions in the logic
program .
A distinguishing aspect of the logic used in logic programming is that a
goal statement typically is existentially quantified : it states that there exist some
individuals with some property . An example of a goal statement is that there
exists a list X such that sorting . the list [9,1,2] gives X . The mechanism used to
prove the goal statement is constructive : if successful, it provides the identity
of the unknown individuals mentioned in the goal statement , which constitutes
the output of the computation . In the example above, assuming that the logic
program contains appropriate axioms defining the sort relation , the output of the
computation would be X = [1,2,9].
Introduction xxi
The ideas behind these equations can be traced back as far as intuitionistic
mathematics and proof theory of the early century . They are related to Hilbert 's
program , to base the the entire body of mathematical knowledge on logical foundations
, to provide mechanical proofs for its theories , starting from the axioms of
logic and set theory alone . It is interesting to note that the fall of this program ,
which ensued the incompleteness and undecidability results of Godel and Turing ,
also marks the beginning of the modern age of computers .
The first use of this approach in practical computing is a sequel to Robinson 's
unification algorithm and resolution principle , published in 1965. Several hesitant
attempts were made to use this principle as a basis of a computational mechanism ,
but they did not gain any momentum . The beginning of logic programming can
be attributed to Kowalski and Colmerauer . Kowalski formulated the procedural
interpretation of Horn clause logic . He showed that an axiom
A if Bl and B2 and . . . and Bn
very difficult to control . Given their bitter experience with logic -based high -level
languages , it is no great surprise that U .S. AI scientists , when hearing about Prolog
, thought that the Europeans are over-excited over what we, Americans , have
already suggested , tried , and discovered not to work .
In spite of the promise of the ideas , and the practicality of their implementation
, most of the Western computer science and AI research community was
ignorant , outwardly hostile , or , at best , indifferent to logic programming . By
1980, the number of researchers actively engaged in logic programming were only
a few dozens in the U .S . , and about one hundred around the world .
Since that time the Prolog language has undergone a rapid transit from
adolescence to maturity . There are numerous commercially available Prolog implementations
on most widespread computers . There is a large number of Prolog
programming books , directed to different audiences and emphasizing different aspects
of the language . And the language itself has more -or -less stabilized , having
a de facto standard , the Edinburgh Prolog family .
The maturity of the language means that it is no longer a concept for scientists
yet to shape and define , but rather a given object , with all its vices and
virtues . It is time to recognize that , on the one hand , Prolog is falling short of
the high goals of logic programming , but that , on the other hand , it is a powerful ,
productive , and practical programming formalism . Given the standard life cy-
Introduction xxiii
cle of computer programming languages , the next few coming years will witness
whether these properties will show their merit only in the classroom or will also
be proven useful in the field , where people pay money to solve problems they care
about .
So what are the current active subjects of research in logic programming and
Prolog ? The answer to this question can be found in the regular scientific journals
and conferences of the field . The Logic Programming Journal , the Journal of New
Generation Computing , the International Conference on Logic Programming , and
the IEEE Symposium on Logic Programming , as well as in the general computer
science journals and conferences .
Clearly , one of the dominant areas of interest is the relationship between
logic programming , Prolog , and parallelism . The promise of parallel computers ,
combined with the parallelism that seems to be available in the logic programming
model , have lead to numerous attempts , which are still ongoing , to execute Prolog
in parallel , and to devise novel concurrent programming languages based on the
logic programming computation model . This , however , is a subject for another
book .
Leonardo Da Vinci , Old Man thinking. Pen and ink (slightly enlarged
). About 1510. Windsor Castle, Royal Library
P art I
Logic PrograIns
The ba.sic constructs of logic programming , terms and statements , are inherited
from logic . There are three ba.sic statements : facts , rules and queries . There
is a single data structure : the logical term .
1.1 Facts
This fact saysthat Abraham is the father of Isaac, or that the relation father
holds betweenthe individuals named abrahamand isaac. Another namefor a
relationshipis a predicate
. Namesof individuals are known as atoms. Similarly
plus(2,3,5) express
esthe relationshipthat 2 plus3 is 5. The familiar plusrelationship
can be realizedvia a set of facts that definesthe addition table. An initial
segmentof the table is
plus(O,O,O). plus(Oil,l ). plus(O,2,2). plus(O,3,3).
plus(l ,Oil). plus(1,1,2). plus(1,2,3). plus(1,3,4).
-
father :terach ,abraham ) . male (terach ) .
father -
:terach ,nachor ) . male (abraham ) .
father :terach ,haran ) . male (nachor ) .
father :abraham ,isaac ) . male (haran ) .
father :haran ,lot ) . male (isaac) .
-
both predicates and atoms in facts begin with a lowercase letter , as opposed to
an uppercase letter . These names are italicized when they appear inrunning text .
A finite set of facts constitutes a program. This is the simplest form of logic
program. A set of facts is also a description of a situation . This insight is the
basis of databaseprogramming, to be discussedin the next chapter. An example
database of family relationships from the Bible is given as Program 1.1. The
predicates father, mother, male, and female expressthe obvious relationships.
1.2 Queries
1 .4 Existential queries
pIe solutions is plus (X , Y,4) 'I for finding numbers that add up to 4. Solutions are,
for example , { X = O, Y= 4} and { X = l , Y= 9} . Note that the different variables X
and Y correspond to (possibly ) different objects .
An interesting variant of the last query is plus (X ,X ,4) 'I which insists that
the two numbers that add up to 4 be the same. It has a unique answer { X = 2} .
1 .5 _Universal facts
Variables are also useful in facts . Suppose that all the Biblical characters like
pomegranates . Instead of including in the program an appropriate fact for every
individual :
an instance of B . In other words , if there are substitutions fJ1 and fJ2 such that
For example , the goals plus ( 0 , 9 , Y) and plus ( 0,X ,X) have a common instance
the query and fact . The answer is the common instance , if one exists . Otherwise
the answer is no .
involves two logical deductions . The instance is deduced from the fact by the
rule of instantiation , and the query is deduced from the instance by the rule of
generalization .
special case of conjunctive queries when there is a single goal . Logically it asks
whether a conjunction is deducible from the program . We use ' , ' throughout to
denote logical ' and .' Do not confuse the comma that separates the arguments in
In the simplest conjunctive queries all the goals are ground , for example ,
father ( abraham , isaac ) , male ( lot ) ? The answer to this query using Program 1 . 1 is
clearly yes as both goals in the query are facts in the program . In general , the
very interesting .
Conjunctive queries are interesting when there are one or more shared variables
, variables that occur in two different goals of the query . An example is the
query father ( haran , X ) , male (X) ? The scope of a variable in a conjunctive query
is the whole conjunction . Thus the query p (X) ,q(X) ? reads : " Is there an X such
that both p (X) and q(X ) ? " Like in simple queries , variables in conjunctive queries
the range of a variable . We have already seen an example with the query
8 Basic Constructs 1.6
plus (X ,X ,;,) 'i where the solution of numbers adding up to 4 was restricted to
the numbers being the same. Consider the query father ( haran ,X) , male (X) ? Here
solutions to the query father ( haran ,X) 'i are restricted to children that are male .
Program 1.1 shows there is only one solution , { X = lot } . Alternatively this query
can be viewed as restricting solutions to the query male (X) ? to individuals who
have Haran for a father .
A slightly different use of a shared variable can be seen in the query father
( terach ,X) ,father (X , Y) ? On the one hand it restricts the sons of terach to
those who are themselves fathers . On the other hand it considers individuals
Y, whose fathers are sons of terach . There are several solutions , for example ,
{ X = abrahamY = isaac} , and { X = haran , Y= lot } .
Operationally , to solve the conjunctive query Al ,A2 ' . ' .,An ? using a program
P, find a substitution {} such that ai {} and . . . and An {} are ground instances of
facts in P. The same substitution applied to all the goals ensures that instances
of variables are common throughout the query . For example , consider the query
Lather (harani X) , male (. X) ? with respect to Program 1.1. Applying the substitution
{ X = lot } to the query gives the ground instance Lather (haran ,lot ) , male ( lot ) ?
which is a consequence of the prog Fam.
1.7 Rules
A +- Bl ,B2,. . .,Bn .
1.7 Rules 9
where n 2:: O. A is the head of the rule , and the Bi ' S are its body . Both A and the
Bi ' S are goals . Rules , facts and queries are also called Horn clauses , or clauses
for short . Note that a fact is just a special case of a rule when n = O. Facts are
also called unit clauses . We also have a special name for clauses with one goal
in the body , namely when n= l . Such a clause is called an iterative clause . As
for facts , variables appearing in rules are universally quantified , and their scope
is the whole rule .
Rules can be viewed in two ways . First , they are a means of expressing
new or complex queries in terms of simple queries . A query son ( X , haran ) ? to
the program that contains the above rule for son is translated to the query father
( haran , X) , male ( X) ? according to the rule , and solved as before . A new query
about the son relationship has been built from simple queries involving father and
male relationships . Interpreting rules in this way is their procedural reading . The
procedural reading for the grandfather rule is : " To arlswer a query is X the grandfather
of Y , answer the conjunctive query is X the father of Z and Z the father of
Y ."
The second view of rules comes from interpreting the rule as a logical axiom .
The backward arrow +- is used to denote logical implication . The son rule reads :
" X is a son of Y if Y is the father of X and X is male ." In this view rules are
a means of defining new or complex relations using other , simpler , relationships .
The predicate son has been defined in terms of the predicates father and male . The
associated reading of the rule is known as the declarative reading . The declarative
reading of the grandfather rule is : " For all X , Y , and Z , X is the grandfather of
Y if X is the father of Z and Z is the father of Y ."
To incorporate rules into our framework of logical deduction , we need the law
of modus ponens . Modus ponens states that from Band A +- B we can deduce A .
Definition : The law of universal modus ponens says that from the rule
R = (A ~ B1,B2,. . .,Bn )
B'1-
~
-
Un-
A I can be deduced , if
is an instance of R . .
G . . .
modus ponens .
Consider the query son ( S , haran ) ' ? with respect to Program 1 . 1 augmented
by the rule for son . The substitution { X = lot , Y = haran } applied to the rule gives
the instance son ( lot , haran ) + - father ( haran , lot ) , male ( lot ) . Both the goals in the
body of this rule are facts in Program 1.1. Thus universal modus ponens implies
Similarly , to define the relation grandparent would take four rules to include
both cases of father and mother :
There is abetter , more compact , way of expressing these rules . We need to define
the auxiliary relationship , parent , as being a father or a mother . Part of the art
of logic programming is deciding on what intermediate predicates to define to
achieve a complete , elegant axiomatization of a relationship . The rules defining
parent are straightforward , capturing the definition of a parent being a father
or a mother . Logic programs can incorporate alternative definitions , or more
technically disjunction , by having alternative rules , as for parent :
paI'ent(X ,Y ) +- father (X ,Y ) .
parent (X ,Y ) +- mother(X ,Y ).
A collection of rules with the same predicate in the head , such as the pair of
parent rules , is called a procedure. We shall see later that under the operational
interpretation of these rules by Prolog , such a collection of rules is indeed the
analogue of procedures or subroutines in conventional programming languages .
12 Basic Constructs 1.8
Algorithm :
Initialize the resolvent to Q
while the resolventAl ,' . .,An is not empty
begin
choose a goal Ai , 1 SiS n, and
a ground instance of a clause
A +- B1 ,B2 , . . ., Bk , k ~ 0 in P , such that A = Ai
(if no such clause exists, exit the while loop);
determine the new resolvent
We relate these concepts to our example trace in Figure 1.2. There are three
reductions in the trace . The first reduces the goal son( lot ,haran ) and produces
two derived goals, father (haran ,lot ) and male (lot ) . The second reduction is of
father (haran ,lot ) producing no derived goals . The third reduction also produces
no derived goals in reducing male ( lot ) .
A proof tree consists of nodes and edges which represent the goals reduced
during the computation . The root of the proof tree for a simple query is the
query itself . The nodes of the tree are the goals which are reduced during the
computation . There is a directed edge from a node to each node corresponding to
a derived goal of the reduced goal . The proof tree for a conjunctive query is just
the collection of proof trees for the individual goals in the conjunction . Figure
1.3 gives a proof tree for the program trace in Figure 1.2.
There are two unspecified choices in the interpreter . The goal to reduce from
the resolvent must be chosen, as well as the clause (and an appropriate ground
instance ) to reduce it . The two choices have very different natures .
Output : yes
son(lot ,haran)
/ \
father (haran,lot ) male(lot )
In contrast , the choice of the clause and a suitable ground instance is critical
. In general , there are several choices of a clause, and infinitely many ground
instances . The choice is made non determinist ically . The concept of nondeterministic
choice is used in the definition of many computation models , e.g. finite
automata and Turing machines , and proves to be a powerful theoretical concept .
A nondeterministic choice is an unspecified choice from a number of alternatives ,
which is supposedto be made in a "clairvoyant" way: if only some of the alternatives
lead to a successfulcomputation (in our case, to finding a proof ), then
one of them is chosen. Formally , the concept is defined as follows : a computation
that contains nondeterministic choices is defined to succeed if there is a sequence
of nondeterministic choices that lead to success . Of course , no real machine can
implement directly this definition . However , it can be approximated in a useful
way, as done in Prolog , and explained in Chapter 6.
The interpreter given in Figure 1.1 can be extended to answer nonground
existential queries by an initial additional step : guess a ground instance of the
query . This is identical to the step in the interpreter of guessing ground instances
1.9 The meaning of a logic program 15
of the rules . It is difficult in general to guess the correct ground instance , since
that means knowing the result of the computation before performing it .
A new concept is needed to lift the restriction to ground instances and remove
the burden of guessing them . We show in Chapter 4 how the guess of ground instances
can be eliminated , and introduce the computation model of logic programs
more fully . Until then it is assumed that the correct choices can be made .
An important measure provided by proof trees is the number of nodes in the
tree . It indicates how many reduction steps are performed in a computation . We
use the measure as a basis of comparison between different programs in Chapter
3.
Throughout the book , when meaningful predicate and constant names are
used , the intended meaning of the program is assumed to be the one intuitively
For example , the program for the son relation containing only the first axiom
that uses father is incomplete with respect to the intuitively understood intended
meaning of son , since it cannot deduce son ( isaac , sarah ) . If we add to it the rule
it would make the program incorrect with respect to the intended meaning , since
it deduces son( sarah, isaac) .
The notions of correctness and completeness of a logic program are studied
further in Chapter 5.
Although the notion of truth is not defined fully here , we will say that a
ground goal is true with respect to an intended meaning if it is a member of it ,
and false otherwise . We will say it is simply true if it is a member of the intended
meaning implied the names of the predicate and constant symbols appearing in
the program .
1.10 Summary
variables ; otherwise they are . nonground . Goals are atoms or compound terms ,
aJld are generally nonground .
A +- B1 , B2 , . . .,Bk . k ~ 0,
where A and the Bi ' S are goals . Such a sentence is read declaratively " A is implied
by the conjunction of the Bi ' s , " and is interpreted procedurally " to answer query
A , answer the conjunctive query Bl , B2 , . . . , Bk . " A is called the clause ' s head and
the B ' s the clause ' s body . If k = O , the clause is known as a fact or unit clause
and written - A . , meaning A is true under the declarative reading , and goal A is
iterative clause .
AI , . . . , An ? n > 0 ,
where the Ai ' S are goals . Variables in a query are understood to be existentially
quantified .
Bi ' s are deducible from P . Deduction of a goal from an identical fact is a special
case .
The set of ground instances of facts in P are in the meaning . A ground goal G
that Bl , . . . , Bn are in the meaning . The meaning consists of the ground instances
correct and complete with respect to its intended meaning , which is the desired
situation , if M = M ( P ) .
18 Basic Constructs 1.10
There are two basic styles of using logic programs : defining a logical database ,
and manipulating data structures . This chapter discuss es database programming .
A logic database is comprised of a set of facts and rules . We show how a set of
facts can define relations , as in relational databases . We show how rules can define
complex relational queries , as in relational algebra . Together , a logic program
composed of a set of facts and rules of a rather restricted format can express the
functionalities associated with relational databases .
We begin by revising Program 1.1, the biblical database , and its augmentation
with rules expressing family relationships . The database itself had four basic
predicates , father / 2, mother / 2, male/ l , and female / l . We adopt a convention
from database theory and give for each relation a relation scheme that specifies
the role that each position in the relation (or argument in the goal ) is intended
to represent . Relation schemes for the four predicates here are, respectively ,
father (Father , Child) , mother (Mother , Child) , male (Person ) , and female (Person ) .
The mnemonic names are intended to speak for themselves .
We adopt the typo graphic convention that relation schemes are given in italics
. Variables are given mnemonic names in rules , but usually X or Y when
discussing queries . Multiword names are handled differently for variables and
predicates . Each new word in a variable is started with a capital letter , for example
, Niece Or Nephew, while words are delimited by underscores for predicate and
function name , for example , schedule_conflict .
New relations are built from these basic relationships by defining suitable
rules . Appropriate relation schemes for the relations introduced in the previous
20 DatabageProgramming 2.1
chapter are son ( Son , Parent ) , daughter ( Daughter , Parent ) , parent ( Parent , Child ) ,
if the available database consisted of parent , male and female facts , the rules
defining son and grandparent are still correct . New rules must be written for the
relationships no longer defined by facts , namely father and mother . Suitable rules
are :
present in the database only implicitly . For example , since we know the father
Biblical term , procreated . This is not given explicitly in the database but a
simple rule can be written recovering the information . The relation scheme is
This reads : " Man and Woman procreated if there is a Child such that Man is the
Another example of information that can be recovered from the simple information
This reads : " Brother is the brother of Sib if Parent is a parent of both Brother
There is a problem with the definition of brother given above . The query
brother ( X ,X ) ? is satisfied for any male child X , which is not our understanding of
uncle(Uncle,Person) +-
brother (Uncle,Parent), parent(Parent,Person).
sibling(Sibl ,Sib2) +-
parent(Parent,Sibl ), parent (Parent,Sib2), Sibl # Sib2.
cousin(Cousinl ,Cousin2) +-
parent (Parentl ,Cousinl ),
parent (Parent2,Cousin2) ,
sibling(Parentl ,Parent2).
Program 2. 1: Defining family relationships
In order to preclude such cases from the meaning of the program we introduce
a predicate # ( Terml , Term2 ) . It is convenient to write this predicate as an infix
operator . Thus Terml # Term2 is true if Terml and Term2 are different . For the
present it is restricted to constant terms . It can be defined , in principle , by a
table X # Y for every two different individuals X and Y in the domain of interest .
Figure 2.1 gives the appropriate table for Program 1.1.
The new brother rule is
The more relationships that are present , the easier it is to define complicated
relationships . Program 2.1 defines the relations uncle ( Uncle,Niece Or Nephew) ,
sibling (Sib1,Sib2) , and cousin ( Cousin1 , Cousin2 ) . More examples are posed as
exercises at the end of the section .
This reads : A Woman is a mother if she is the mother of some Child . Note that
we have used the same predicate name , mother , to describe two different mother
relationships . The mother predicate takes a different number of arguments , i .e.,
has a different arity , in the two cases. In general the same predicate name denotes
a different relationship when it has a different arity .
22 Database Programming 2.1
Power
n3
I 0
n5
, 0
- - - - o -~ 1 - - - - . 0-
is the input to the inverter , while the free end of the resistor must be connected
to the drain of the transistor , which forms the output of the inverter . Sharing of
variables is used to insist on the common connection.
Consider the query and_gate(Inl ,Inf ,Out) ? to Program 2.2. It has the solution
{ Inl = n9,Inf = nS,Out= nl } . This solution confirms that the circuit described
by the facts is an and-gate, and indicates the inputs and output .
(i ) Modify the rule for brother to give a rule for sister , the rule for uncle to give
a rule for niece, and the rule for sibling so that it only recognizes full siblings ,
i .e., those that have the same mother and father .
(ii ) Using a predicate married _couple( Wile ,Husband) , define the relationships :
mother _in _law, brother -in _law, and son_in _law.
(iii ) Describe the logical circuit for an or -gate depicted in Figure 2.3 using a
logic program like Program 2.2. Extend the program to a nor -gate using an
inverter .
2.2 Structured data and data abstraction 25
A limitation of Program 2.2 for describing the and- gate is the treatment of
the circuit as a black box . There is no indication of the structure of the circuit in
the answer to the and_gate query , even though the structure has been implicitly
used in finding the answer . The rules tell us that the circuit represents an and-
gate , but the structure of the and-gate is present only implicitly . We remedy this
by adding an extra argument to each of the goals in the database . For uniformity ,
the extra argument becomes the first argument . The base facts simply acquire
an identifier . Proceeding from left to right in the diagram of Figure 2.2, we label
the resistors rl and r2 , and the transistors tl , t2 and t3 .
Names of the functional components should reflect their structure . An inverter
is composed of a transistor and a resistor . To represent this , we need
structured data . The technique is to use a compound term , inv ( T,R) , where T
and R are the respective names of the inverter 's component transistor andresis -
tor . Analogously , the name of a nand - gate will be nand ( Tl , T2,R) , where Tl , T2
and R name the two transistors and resistor that comprise a nand -gate . Finally ,
an and -gate can be named in terms of an inverter and a nand -gate . The modified
code containing the names appears in Program 2.3.
The query and_yate( G,In1 ,Inf , Out ) ? has solution { G= and(nand (tf ,t9,rf ) ,
inv (t1,r1 ) ),In1 = n9,Inf = n5, Out = ni } . In1 , Inf , and Out have their previous values
. The complicated structure for G reflects accurately the functional composition
of the and-gate .
and
The first fact represents course as a relationship between eight items - a course
name , a day, a starting hour , a finishing hour , a lecturer 's first name , a lecturer 's
26 Database Programming 2.2
resistor(R,N odel,Node!!) +-
R is a resistor between N odel and Node !! .
resistor(r 1,power,nl ).
resistor(r2,power,n2).
transistor( T, Gate,Source,Drain ) +-
T is a transistor whose gate is Gate ,
source is Source , and drain is Drain .
surname , a building , and a room . The second fact makes course a relationship
between four items - a name , a time , a lecturer , and a location with further
qualification . The time is composed of a day , a starting time and a finishing
time , lecturers have a first name and a surname , and locations are specified by
a building and a room . The second fact reflects more elegantly the relationships
2.2 Structured data and data abstraction 27
that hold .
The four argument version of course enables more concise rules to be written
by abstracting the details which are irreleva J}t to the query . Program 2.4 is
comprised of some examples . The occupied rule assumes a predicate less thaJ} or
equal , represented aB a binary infix operator ~ .
Rules not concerning with the particular values of a structured argument
need not "know " how the argument is structured . For example , the rules for
duration and teaches represent time explicitly as time (Day ,Start ,Finish ) because
the Day or Start or Finish times of the course are desired . In contrast , the rule
for lecturer does not . This leads to greater modularity , as the representation of
time can be changed without affecting the rules that do not inspect it .
We do not have definite rules to decide whether to use structured data or
not . Not using structured data allows a uniform representation where all the data
are simple . The advantages of structured data are compactness of representation
which more accurately reflects our perspective of a situation , and modularity . We
can relate the discussion to conventional programming languages . Facts are the
counterpart of tables , while structured data correspond to records with aggregate
fields .
follows :
Rules would then be expressed differently , reverting to the previous style of making
Add rules defininf ?; the relationships location ( Course , Building ) , busy ( Lecturer ,
Time ) and cannot _ meet ( Lecturerl , Lecturer2 ) . Assume course facts as above .
relationship , e . g . , father ( X , Y , pa ( X , Y ) ) .
Design a small database for an application of your own choice . Use a single
2 .3 Recursive rules
The rules described so far define new relationships in terms of existing ones.
An interesting extension is recursive definitions of relationships which define relationships
in terms of themselves . One way of viewing recursive rules is as gen-
eralization of a set of nonrecursive rules .
A clear pattern can be seen , which can be expressed in a rule defining the relationship
A logic program for ancestor also requires a nonrecursive rule , the choice
of which affects the mea J1ing of the program . If the fact ancestor ( X , X ) is used ,
their own a J1cestors . This is not the intuitive mea J1ing of a J1cestor . Program 2 . 5 is
a logic program defining the a J1cestor relationship , where parents are considered
a J1cestors .
edge ( Nodel , Node2 ) is present in the program if there is an edge from Nodel to
Node2 in the graph . Figure 2 . 4 gives a graph , while Program 2 . 6 is its description
as a logic program .
Two nodes are connected if there is a series of edges that can be traversed
to get from the first node to the second . That is , the relationship connected
( Nodel , Node2 ) , which is true if Nodel and Node2 are connected , is the
transitive closure of the edge relationship . For example , a and e are connected in
the graph in Figure 2 . 4 , but b and fare not . Program 2 . 7 defines the relationship .
The meaning of the program is the set of goals connected ( X , Y ) , where X and Y
a - + b f
~ ~ ~
c - + d - + e g
The union operation creates a relation of arity n from two relations r and 8,
hoth of aritv n. The new relation . denoted here r _union _8. is the union of r and
v .
Projection involves forming a new relation comprising only some of the attributes
of an existing relation . This is straightforward for any particular case.
For example , the projection r19 selecting the first and third arguments of a relation
of arity 9 is
Some of the derived operations of the relational algebra are more closely
related to the constructs of logic programming . We mention two , intersection
32 Database
Programming 2.4
and the natural join . If r and 8 are relations of arity n, the intersection , r _meet_8
is also of arity n and is defined in a single rule .
2.5 Background
Recursive Programming
Logical terms can be classified into types. A type is a (possibly infinite ) set
of terms. Sometypes are conveniently defined by unary relations. A relation p/ l
defines the type p to be the set of X' s such that p(X) .
For example, the malell and femalell predicates used previously define the
male and female types .
More complex types can be defined by recursive logic programs . Such types
are called recursive types. Types defined by unary recursive programs are called
simple recursive types. A program defining a type is called a type definition .
In this chapter we show logic programs defining relations over simple recursive
types , such as integers , lists and binary trees , and also programs over more
complex types , such as polynomials .
3 .1 Arithmetic
The simplest recursive data type , natural numbers , arises from the foundations
of mathematics . Arithmetic is based on the natural numbers . This section
gives logic programs for performing arithmetic .
In fact , Prolog programs for performing arithmetic differ consider ably from
their logical counterparts , as we will see in later chapters . However , it is useful to
34 Recursive Programming 3.1
spend time discussing the logic programs . There are two main reasons . Firstly ,
the operations of arithmetic are usually thought of functionally rather than relationally
. Presenting examples for such a familiar area emphasizes the change in
thinking necessary for composing logic programs . Second, it is more natural to
discuss the underlying mathematical issues, such as correctness and completeness
of programs .
The natural numbers are built from two constructs , the constant symbol
0 and the successor function 8 of arity 1. All the natural numbers are then
recursively given as 0, 8( 0) , 8(8(0) ) , 8(8(8(0))) , . . .. We adopt the convention that
8n( 0) denotes the integer n, that is , n applications of the successor function to O.
As in the previous chapter , we give a relation scheme for each predicate ,
together with the intended meaning of the predicate . Recall that a program P is
correct with respect to an intended meaning M if the meaning of P is a subset of
M . It is complete if M is a subset of the meaning of P . It is correct and complete
if its meaning is identical to M . Proving correctness establish es that everything
deducible from the program is intended . Proving completeness establish es that
everything intended is deducible from the program . Two typical correctness and
completeness proofs are given in this section .
The simple type definition of natural numbers is neatly encapsulated in the
logic program , Program 3.1. The relation scheme used is natural _number (X) , with
intended meaning that X is a natural number . The program consists of one unit
clause and one iterative clause (a clause with a single goal in the body ) . Such a
program is called minimal recursive .
Proposition : Program 3.1 is correct and complete with respect to the set
of goals natural _number ( si (D) ) , for i :?: D.
-i ~:,:Proof : ( 1) Completeness . Let Nbe a natural number . We show that the goal
~atural _number ( N) is deducible from the program by giving an explicit proof tree .
Either N is 0 or of the form sN ( 0) . The proof tree for the goal natural _number ( 0)
is trivial . The " proof tree for the goal natural _numbers (. . .s( 0) . . .) ) contains N
r J J Uctions, using the rule in Program 3.1, to reach the fact natural _number ( O) , as
isrshown in the left half of Figure 3.1.
3.1 Arithmetic 35
. .
I I
IIlumber
natural ..number(sn- l (0)) pIUS
(sn- l (0),sm(o),Sn+m- l (0))
natural
-
i (
8(
0
)
I I
natural
-ilumber
(O
)
Figure 3 . 1: Proof trees establishing completeness of programs
The natural numbers have a natural order . Program 3.2 is a logic program
defining the relationship less than or equal to according to the order . We denote
the relationship with a binary infix symbol, or operator, $ according to mathematical
usage. The expression0 $ X is nonethelessa term with functor $ / 2, and
arguments 0 and X , and is syntactically equivalent to '$ '(O,X) .
The relation scheme is Nl ~ N2 . The intended meaning of Program 3.2 is
all ground facts X ~ Y where X and Yare natural numbers and X is less than or
equal to Y. Exercise (ii ) at the end of the section is to prove the correctnessand
completeness of Program 3.2.
The recursive definition of ~ is not "computationally efficient ." The proof
tree establishing that a particular Nis less than a particular Mhas M + f! nodes .
We usually think of testing whether one number is less than another as a unit
operation , independent of the size of the numbers . Indeed Prolog does not define
arithmetic according to the axioms presented in this section , but uses the
underlying arithmetic capabilities of the computer directly .
36 RecursiveProgramming 3.1
X : 5Y ~
X and Yare natural numbers ,
such that X is less than or equal to Y.
0 ~ X +- natural -ilumber (X ).
s(X ) ~ s(Y ) +- X ~ Y .
natural Jlumber (X ) +- SeeProgram 3.1
Program 3 .2 : The less than or equal relation
Proposition : Programs 3.1 and 3.3 constitute a correct and complete ax-
iomatization of addition , with respect to the standard intended meaning of plus/ So
Proof : ( 1) Completeness . Let X , Y, and Z be natural numbers such that
X + Y= Z. We give a proof tree for the goal plus {X , Y,Z) . If X equals 0, then Y
equals Z. Since Program 3.1 is a complete axiomatization of the natural numbers ,
there is a proof tree for natural _numberY ) , which is easily extended to a proof
tree for plus ( 0, Y, Y) . Otherwise , X equals sn ( 0) for some n. If Y equals sm( 0) ,
then Z equals sn+m ( o) . The proof tree in the right half of Figure 3.1 establish es
completeness .
(2) Correctness . Let plus (X , Y,Z) be in the meaning . A simple inductive
argument on the size of X , similar to the one used in the previous proposition ,
establish es that X + Y = z . .
Addition is usually considered to be a function of two arguments rather than
a three place relation . Generally logic programs corresponding to functions of
3.1 Arithmetic 37
Posing the query plus (s( O) ,s( O) ,X) ?, an example of the standard use, calculates
the sum of 1 and 1. However , the program can just ~ e~ ily be used
for subtraction by posing a query such ~ plus (s( O) ,X ,s(s(s( O) )) ) ? The computed
value of X is the difference between 3 and 1, namely 2. Similarly asking a query
with the first argument uninstantiated , and the second and third instantiated ,
also performs subtraction .
A more novel use exploits the possibility of a query having multiple solutions .
Consider the query plus (X , Yis (s(s( O)) ) ) ? It reads : "Do there exist numbers X
and Y that add up to 3." In other words , find a partition of the number 3 into
the sum of two numbers , X and Y. There are several solutions .
A query with multiple solutions becomes more interesting when the properties
of the variables in the query are restricted . There are two forms of restriction :
using extra conjuncts in the query , and instantiating variables in the query . We
saw examples of this when querying a database . Exercise (ii ) at the end of this
section requires to define a predicate even(X) , which is true if X is an even number
. Assuming such a predicate , the query plus (X , YiN) , even(X) ,even( Y) ? gives a
partition of N into two even numbers . The second type of restriction is exempli -
fied by the query plus ( s( s(X) ) ,s( s( Y) ) ,N) ? which insists that each of the numbers
adding up to N is strictly greater than one.
Almost all logic programs have multiple uses. Consider Program 3.2 for ~ ,
for example . The query s( 0) ~ s( s( O) ) ? checks whether 1 is less than or equal to
2. The query X ~ s( s( O)) ? finds numbers X less than or equal to 2. It even
computes pairs of numbers less than or equal to each other with the query X ~
Y?
38 RecursiveProgramming 3.1
Program 3 .3 defining addition is not unique . For example , the logic program
has precisely the same meaning as Program 3 .3 for plus . Two programs are to
be expected due to the symmetry between the first two arguments . A proof of
correctness and completeness given for Program 3 .3 applies to this program by
reversing the roles of the symmetric arguments .
The meaning of the program for plus would not change even if it consisted
of the two programs combined . This composite program is undesirable , however .
There are several different proof trees for the same goal . It is important both
for runtime efficiency and for textual conciseness that axiomatizations of logic
programs be minimal .
We define a type condition to be a call to the predicate defining the type . For
natural numbers , a type condition is any goal of the form natural _number (X) .
The basic programs shown are the building blocks for more complicated relationships
. A typical example is defining multiplication as repeated addition . Program
3 .4 reflects this relationship . The relation scheme is times (X , Y, Z) meaning
X times Y equals Z .
Not all relationships concerning natural numbers are defined recursively . Relations
can also be defined in the style of programs in Chapter 2 . An example
is Program 3 .7 determining the minimum of two numbers via the relation minimum
( ni , N .2,Min ) .
3.1 Arithmetic 39
times(X , Y,Z) +-
X , Yand Z are natural numbers ,
such that Z is the product of X and Y.
times(O,X ,O).
times(s(X),Y ,Z) +- times(X ,Y ,W), plus(W,Y ,Z).
plus (X ,Y ,Z) +- SeeProgram 3.3
Program 3.4 : Multiplication as repeated addition
exp(NiX , Y) +-
N , X , and Yare natural numbers ,
such that Yequals X raised to the power N .
exp(s(X ),O,O).
exp(O,s(X ),s(O) ).
exp(s(N),X ,Y ) ~ exp(N ,X ,Z), times(Z,X ,Y ).
times (X ,Y ,Z) +- SeeProgram 3.4
Program 3 .5: Exponentiation as repeated multiplication
factorial (N,F) +-
F equals N factorial .
factorial (O,s(O)).
factorials (N),F) -+- factorial (N ,Fl ), times(s(N),Fl ,F).
times (X ,Y ,Z) +- SeeProgram 3.4
Program 3 .6 : Computing factorials
mod ( X , Y , Z) + -
Z is the remainder of the integer division of X by Y.
mod ( X , Y , Z) +-
Z is the remronder of the integer division of X by Y.
mod (X ,Y ,X ) +- X < Y .
mod (X ,Y ,Z ) +- plus (Xl ,Y ,X ) , mod (Xl ,Y ,Z ) .
concept are translated into different logic programs . Programs 3 .8a and 3 .8b give
two definitions of the relation mod ( X , Y , Z) , which is true if Z is the value of X
modulo Y , or in other words Z is the remainder of X divided by Y . The programs
assume a relation < as specified in exercise (i ) at the end of the section .
rule says that the value of X modY is the same as X - Y modY . The effect of any
computation to determine the modulus is to repeatedly subtractY from X until
it becomes less than Y and hence is the correct value .
The mathematical function X mod Yis not defined when Yis zero . Neither
Program 3 .8a nor Program 3 .8b have goals mod ( X , O, Z) in their meaning for any
values of X or Z . The test of " < " guarantees that .
ackermann (X , Y,A ) +-
A is the value of Ackermann 's
function for the natural numbers X and Y.
programs for mod. Given a particular X , Yand Z satisfying mod, we can compare
the size of their proof trees . In general proof trees produced with Program 3.8b
will be smaller than those produced with Program 3.8a. In that sense Program
3.8b is more "efficient ." We defer more rigorous discussions of efficiency till the
discussions on lists , where the insights gained will carryover to Prolog programs .
Another example of translating a mathematical definition directly into a logic
program is writing a program that defines Ackermann 's function . Ackermann 's
function is the simplest example of a recursive function which is not primitive
recursive . It is a function of two arguments , defined by three cases:
ackermaJln (O,N ) = N + l .
ackermaJln (M ,O) = ackermann (M - l ,l ) .
ackermann (M ,N ) = ackermann (M - l ,ackermaJln (M ,N- l )) .
The final example in this section is the Euclidean algorithm for finding the
greatest common divisor of two natural numbers , recast as a logic program . Like
Program 3.8b , it is a recursive program not based on the recursive structure of
numbers . The relation scheme is gcd(X , Y,Z) , with intended meaning that Zis the
greatest common divisor (or gcd) of two natural numbers X and Y. It uses either
of the two programs , 3.8a or 3.8b , for mod.
42 Recursive Programming 3.1
gcd(X , Y,Z) ~
Z is the greatest common divisor of
the natural numbers X and Y.
The first rule in Program 3.10 is the logical essence of the Euclidean algorithm
. The gcd of X and Y is the same as the gcd of Y and X modY . A proof
that Program 3.10 is correct depends on the correctness of the above mathematical
statement about greatest common divisors . The proof that the Euclidean
algorithm is correct similarly rests on this result .
The second fact in Program 3.10 is the base fact . It must be specified that
Xis greater than 0 to preclude gcd( O,O,O) from being in the meaning . The gcd of
0 and 0 is not well defined .
(i ) Modify Program 3.2 for ~ to axiomatize the relations < , > , and ~ . Discuss
multiple uses of these programs .
(ii ) Prove that Program. 3.2 is a correct and complete axiomatization of ~ .
(iii ) Prove that a proof tree for the query sn( o) ~ Sffl( O) using Program 3.2 has
M + 2nodes .
(iv ) Define predicates even(X) and odd(X) for determining if a natural number is
even or odd . .
(Hint : Modify Program 3.1 for natural - number .)
(v ) Write a logic program defining the relationship fib (N ,F} to determine the
Nth Fibonacci number F .
(vi ) The predicate times can be used for computing exact quotients with queries
such as times (s(s(O)) ,X ,s(s(s(s( O) )) ) ) ? to find the result of ;, divided by 2.
The query times (8(s(O) ) ,X ,s(s(s( O) )) ) ? to find 9/ 2 has no solution . Many
applications require the use of integer division that would calculate 9/ 2 to
be 1. Write a program to compute integer quotients .
(Hint : Use repeated subtraction .)
(vii ) Modify Program 3.10 for finding the gcd of two integers so that it performs
repeated subtraction directly , rather than use the mod function .
3.2 Lists 43
(Hint : The program repeatedly subtracts the smaller number from the larger
number until the two numbers are equal.)
3 .2 Lists
The basic structure for arithmetic is the unary successor functor . Although
complicated recursive functions such a.s Ackermann 's function can be defined , the
use of a unary recursive structure is limited . This section discuss es the binary
structure , the list .
The first argument of a list holds an element , and the . second argument is
recursively the rest of the list . Lists are sufficient for most computations -
attested to by the success of the programming language LISP , which has lists
as its basic compound data structure . Arbitrarily complex structures can be
represented with lists , though it is more convenient to use different structures
when appropriate .
Terms built with the dot functor are more general than lists . Program 3.11
defines a list precisely . Declaratively it reads : "A list is either the empty list or a
cons pair whose tail is a list ." The program is analogous to Program 3.1 defining
natural numbers , and is the simple type definition of lists .
Figure 3.3 gives a proof tree for the goal list ([ a,b,c]). Implicit in the proof
~EEEEE
44 Recursive Programming 3.2
list(Xs) +-
Xs is a list.
list([ ]).
list([X I Xs]) +- list(Xs).
Program 3.11: Defininga list
~
Formal object Cons pair syntax Element syntax
0- .
[a]
'
[ ]]] [a,b]
"
[c I[ ]]]]
'
[a,b,c]
""~.
[aI X]
X]] [a,bIX]
list([a,b,c])
I
list([b,c])
I
list([c])
I
list([ ])
Figure 3.3: Proof tree verifying a list
tree are ground instances of rules in Program 3.11, for example , list ( [a,b, c]) +-
list ([b,c]) . We specify the particular instance here explicitly , as instances of lists
in cons pair notation can be confusing . [a, b, c] is an instance of [XlXs ] under the
substitution { X = a,Xs = [b,c]} .
Because lists are richer data structures than numbers there is a great variety
of interesting relationships that can be specified with them . Perhaps the most
basic operation with lists is determining whether a particular element is in a list .
The predicate expressing this relationship is member (Element ,List ) . Program 3.12
is a recursive definition of member/ 2.
Declaratively , the reading of Program 3.12 is straightforward . Xis an element
of a list if it is the head of the list by the first clause , or if it is a member of the
tail of the list by the second clause. The meaning of the program is the set of all
3.2 Lists 45
member ( X , [X I Xs ] ) .
prefix ( [ ] , Ys ) .
suffix ( Xs , Xs ) . .
ground instances member(X ,Xs) where X is an element of Xs. We omit the type
condition in the first clause. Alternatively it would be written
member(X,[XjXs ]) +- list(Xs).
relationships- as auxiliary - -predicates . The two cases considered are initial sublists ,
or prefix es, of a list , and terminal sublists, or suffixes, of a list . The programs are
interesting in their own right .
The predicate prefix(Prefix,List ) is true if Prefix is an initial sublist of List ,
for example , prefix ( (a,b],(a, b, c]) is true . The companion predicate to prefix is
sulJix ( SulJix,List ) determining if Suffix is a terminal sublist of List . For example ,
sulJix ( (b, c],[ a,b,c]) is true . Both predicates are defined in Program 3.13. The type
condition list (Xs ) should be added to the base fact in each predica ~e to give the
correct meaning .
An arbitrary sublist can be specified in terms of prefix es and suffix es: namely
as a suffix of a prefix , or as a prefix of a suffix . Program 3.14a express es the logical
rule that Xs is a sublist of Ys if there exists Ps such that Ps is a prefix of Ys and
Xs is a suffix of Ps. Program 3.14b is the dual definition of a sublist as a prefix
of a suffix .
The predicate prefix can also be used as the basis of a recursive definition of
3.2 Lists 47
append([ ],Ys,Ys).
append([X IXs],Ys,[X IZs]) +- append(Xs,Ys,Zs).
Program 3 . 15 : Appending two lists
sublist . This is given as Program 3.14c. The base rule reads that a prefix of a list
is a sublist of a list . The recursive rule reads that the sublist of a tail of a list is
a sublist of the list itself .
The basic operation with lists is concatenating two lists to give a third list .
This defines a relationship , append(Xs , Ys,Zs) , between two lists Xs , Ys and the
result Zs of joining them together . The code for append, Program 3.15, is identical
in structure to the basic program for combining two numbers together , Program
3.3 for plus .
Figure 3.4 gives a proof tree for the goal append([ a, b],[ c, dj,[a,b,c,d] ) . The tree
structure suggests that its size is linear in the size of the first list . In general , if
Xs is a list of n elements , the proof tree for append(Xs , Ys,Zs) has n + l nodes .
There are multiple uses for append similar to the multiple uses for plus .
The basic use is to concatenate two lists by posing a query such as append
( [a,b, c],[die],Xs ) ? with answer Xs = [a,b, c, die]. A query such as append
(Xs , [c,dj, [a,b,c,d] ) ? finds the difference Xs = [a, b] between the lists [c, dj and
[a,b, c, d] . Unlike plus , append is not symmetric in its first two arguments , and
thus there are two distinct versions of finding the difference between two lists .
The analogous process to partitioning a number is splitting a list . The query
48 Recursive Programming 3.2
a: Naive reverse
reverse ( [ ] , [ ] ) .
b: Reverse - accumulate
reverse ( Xs , Y s ) + - reverse ( Xs , [ ] , Y s ) .
reverse ( [ ] , Ys , Ys ) .
append(As ,Bs,[a, b,c, dJ) ?, for example , asks for lists As and Bs such that appending
Bs to As gives the list [a, b, c, dJ. Queries about splitting lists are made more
interesting by partially specifying the nature of the split lists . The predicates
member , sublist , prefix and suffix introduced previously can all be defined in terms
of append by viewing the process as splitting a list .
The most straightforward definitions are for prefix and suffix , which just
specify which of the two split pieces are of interest :
Sublist can be written using two append goals. There are two distinct vari -
ants , given as Programs 3.14d and 3.14e. These two programs are obtained from
Programs 3.14a and 3.14b, respectively , where prefix and suffix are replaced by
append goals.
Member can be defined using append, as follows :
This says that X is a member of Ys if Ys can be split into two lists where X
is the head of the second list .
length(Xs,N) +-
The list Xs hag N elements .
length ([ 1,0).
length ([X IXs1,s(N)) +- length (Xs,N).
Program 3 . 17 : Determining the length of a list
Program 3.16b is more efficient than Program 3.16a. Consider Figure 3.5
with proof trees for the goal reverse( [ a,b, c, d] ,[ d,c, b,a]) using both programs . In
general the size of the proof tree of Program 3.16a is quadratic in the number of
elements in the list to be reversed , while that of Program 3.16b is linear .
The insight in Program 3.16b is the use of a better data structure for representing
the sequence of elements , which we discuss in more detail in Chapters 7
and 15.
length(( ]) = 0
length((X IXs]) = s(length(Xs)).
The query length([a,b],s(s(O))) ? checks whether the list [a,b] has length 2.
The query length(Xs,s( s( O))) ? generatesa list of length 2 with variables for elements
.
3 .3 Composing recursive programs 51
(ii ) Write recursive programs for adjacent and last which have the same meaning
as the predicates defined in the text in terms of append .
( iii ) Write a program for double ( List , List List ) where every element in List appears
twice in List List , e .g . double ( [1 , 2 , 9] , [1 , 1 , 2 , 2 , 9 , 9] ) is true .
(iv ) Compute the size of the proof tree as a function of the size of the input list ,
for Programs 3 . 16a and 3 . 16b defining reverse .
(v ) Define the relation sum ( List Of Integers , Sum ) , which holds if Sum is the sum
of the List Of Integers .
( a ) Using plus / So
( b ) Without using any auxiliary predicate .
( Hint : Three axioms are enough .)
No explanation has been given so far about how the example logic programs
have been composed . To some extent we claim that the composition of logic
programs is a skill learned by apprenticeship or osmosis , and most definitely by
practice . For simple relationships , the best axiomatizatio DS have an aesthetic
elegance which look obviously correct when written down . Through solving the
exercises , the reader may find , however , that there is a difference between recognizing
and constructing elegant logic programs .
This section gives more example programs involving lists . Their presentation ,
however , places more emphasis on how the programs might be composed . Two
principles are illustrated : how to blend procedural and declarative thinking , and
how to develop a program top - down .
We have shown the dual reading of clauses : declarative and procedural . How
do they interrelate when composing logic programs ? Pragmatically , one thinks
procedurally when programming . However , one thinks declaratively when considering
issues of truth and meaning . One way to blend them in logic programming is
52 Recursive Programming 3.3
The first , and most important , step is to specify the intended meaning of the
relationship . There are clearly three arguments involved when deleting elements
from a list : an element X to be deleted , a list L1 which might have occurrences
of X , and a list L2 with all occurrences of X deleted . An appropriate relation
scheme is delete (L1 ,X ,L2 ) . The natural meaning is all ground instances where L2
is the list L1 with all occurrences of X removed .
When composing the program , it is easiest to think of one specific use . Consider
the query delete ( [a, b, c, b] , b,X) ?, a typical example of finding the result of
deleting an element from a list . The answer here is X = [a, c] . The program will be
recursive on the first argument . Let 's don our procedural thinking caps .
We begin with the recursive part . The usual form of the recursive argument
for lists is [XlXs ] . There are two possibilities to consider , one where X is the
element to be deleted , and one where it is not . In the first case the result of
recursively deleting X from Xs is the desired answer to the query . The appropriate
rule is
Switching hats , the declarative reading of this rule is : " The deletion of X
from [XlXs ] is Ys if the deletion of X from Xs is Ys." The condition that the head
of the list and the element to be deleted are the same is specified by the shared
variable in the head of the rule .
The second case where the element to be deleted is different from X , the head
of the list , is similar . The result required is a list whose head is X and whose tail
is the result of recursively deleting the element . The rule is
The rule 's declarative reading is : " The deletion of Z from [XlXs ] is [XI Y s]
if Z is different from X and the deletion of Z from Xs is Y s." In contrast to the
previous rule , the condition that the head of the list and the element to be deleted
are different is made explicit in the body of the rule .
delete(List,X ,HasNoXs) ~
The list Has No Xs is the result of removing all
occurrences of X from the list List .
Let us review the program we have written , and consider alternative formulations
. Omitting the condition X # Zfrom the second rule in Program 3.18 gives a
variant of delete. This variant has a less natural meaning since any number of oc-
currences of an element may be deleted . For example , delete( [ a, b, c,b],b,[ a, c]) ,
delete([ a, b, c, b], b, [a,c, b]) , delete([ a, b, c, b], b,[ a, b, c]) and delete( [ a, b, c,b], b, [a, b, c, b])
are all in the meaning of the variant .
Both Program 3.18 and the variant above include in their meaning instances
where the element to be deleted does not appear in either list , for example ,
delete( [aJib, [aJ) is true . There are applications where this is not desired . Program
3.19 defines select(X ,L1 ,L2 ) , a relationship that has a different approach to
elements not appearing in the list . The meaning of select(X ,L1 ,L2 ) is all ground
instances where L2 is the list L1 where exactly one occurrence of X has been
removed .
The program is a hybrid of Program 3.12 for member and Program 3.18 for
delete. Its declarative reading is : "X is selected from [X] Xs ] to give Xs ; or X is
selected from [ 11Ys] to give [ 11Zs] if X is selected from Ys to give Zs." We use
select to aid the construction of a naive program for sorting lists , presented below .
A major thrust in programming has been the emphasis on a top -down design
methodology , together with stepwise refinement . Loosely , the methodology is to
state the general problem , break it down into subproblems , and then solve the
pieces. A top -down programming style is one natural way for composing logic
programs . Our description of programs throughout the book will be mostly top -
54 Recursive Programming 3.3
down . The rest of this section describes the composition of two programs for
sorting a list : permutation sort and quicksort . Their top - down development is
stressed .
The top -level goal of sorting has been decomposed . We must now define permutation
and ordered.
ordered([X ]).
ordered([X ,YIY s]) +- X ~ Y , ordered([YIY s]) .
The predicate insert can be defined in terms of Program 3.19 for select.
sort ( Xs , Ys ) + -
The list Y s is an ordered permutation of the list Xs .
ordered ( [X ] ) .
sort ( Xs , Ys ) + -
The list Y s is an ordered permutation of the list Xs .
insert ( X , [ ] , [X ] ) .
The " naive " sorting program , which we call permutation sort , is collected
The problem of sorting lists is well studied . Permutation sort is not a good
method for sorting lists in practice . Much better algorithms come from applying
a " divide and conquer " strategy to the task of sorting . The insight is to sort a list
by dividing it into two pieces , recursively sorting the pieces , and then joining the
two pieces together to give the sorted list . The methods for dividing and joining
the lists must be specified . There are two extreme positions . The first is to make
the dividing hard , and the joining easy . This approach is taken by the quicksort
algorithm . We give a logic program for quicksort below . The second position is
making the joining hard , but the dividing easy . This is the approach of merge
sort , which is posed as exercise ( iv ) at the end of the section , and insertion sort ,
shown in Program 3 . 21 .
In insertion sort , one element ( typically the first ) is removed from the list .
The rest of the list is sorted recursively ; then the element is inserted , preserving
the orderedness of the list .
56 Recursive Programming 3.3
sort ( Xs , Ys ) +-
The list Y s is an ordered permutation of the list Xs .
partition
([XIXs],Y,[XILs],Bs) +- X SY , partition(Xs,Y ,Ls,Bs).
partition
([XIXs],Y,Ls,[XIBs]) +- X > Y, partition(Xs,Y ,Ls,Bs).
partition
([],Y,[],[]).
Program3.22:Quicksort
The insight in quicksort is to divide the list by choosing an arbitrary element
in it , and then to split the list into the elements smaller than the chosen element
and the elements larger than the chosen element . The sorted list is composed
of the smaller elements , followed by the chosen element , and then the larger
elements . The program we describe chooses the first element of the list as the
basis of partition .
Program 3.22 defines sort using the quicksort algorithm . The recursive rule
for sort reads : " Ys is a sorted version of [.X] Xs ] if Littles and Bigs are a result
of partitioning Xs according to X , Ls and Bs are the result of sorting Littles and
Bigs recursively , and Ys is the result of appending [.X] Bs] to Ls .
Partitioning a list is straightforward , and is similar to the program fordeleting
elements . There are two cases to consider : when the current head of the list
is smaller than the element being used for the partitioning , and when the head is
larger than the partitioning element . The declarative reading of the first partition
clause is : "Partitioning a list whose head is X and whose tail is Xs according to
an elementY gives the lists [Xl Littles ) and Bigs , if X is less than or equal to Y
and partitioning Xs according to Y gives the lists Littles and Bigs ." The second
clause for partition has a similar reading . The base case is that the empty list is
partitioned into two empty lists .
(i) Write a program for substitute (X , Y,L1 ,L2 ) where L2 is the result of substituting
Y for all occurrences of X in L1 , e.g. substitute ( a, x, [ a, b, a,c],[x,b,x,c])
3.4 Binary trees 57
(iii ) Write a program for no_doubles(L1,L2) where L2 is the result of removing all
duplicate elements from Ll , e.g. no_doubles
([a, b, c, b],[a, c, b]) is true.
(Hint : Use member.)
(iv ) Write programs for even_permutation (Xs , Ys) and odd_permutation (Xs , Ys)
which find Ys, the even and odd permutations respectively , of a list Xs .
For example , even-permutation ([l ,2,3],[2, 3,1]) and odd_permutation ([l ,2,3],
[2,1,3]) are true .
(v ) Write a program for merge sort .
3 .4 Binary trees
The next recursive data type we consider is binary trees . These structures
have an important place in many algorithms .
Binary trees are represented by the ternary functor tree(Element ,Left ,Right ) ,
where Element is the element at the node , and Left and Right are the left and
right subtrees respectively . The empty tree is represented by the atom void . For
example , the tree
a
/ \
b c
would be represented as
tree(a,tree( bivoid ,void) ,tree ( c,void ,void) ) .
isotree(void,void).
isotree(tree (X ,Left 1,Right l ) ,tree(X ,Left2 ,Right2) +-
isotree(Leftl ,Left2) , isotree(Rightl ,Right2)).
isotree(tree(X ,Leftl ,Rightl ),tree(X ,Left2 ,Right2) +-
isotree(Leftl ~Right2), isotree(Rightl ,Left2)).
Program 3 .25 : Determining when trees are isomorphic
The relation is true if Element is one of the nodes in the tree . Program 3.24
contains the definition . The declarative reading of the program is "X is a member
of a tree if it is the element at the node (by the fact) or if it is a member of the
left or right subtree (by the two recursive rules) ."
The two branch es of a binary tree are distinguishable , but for many applications
the distinction is not relevant . Consequently a useful concept is isomor -
phism , which defines when unordered trees are essentially the same. Two binary
trees Tl and T2 are isomorphic if T2 can be obtained by reordering the branch es
of the subtrees of T1. Figure 3.6 shows three simple binary trees . The first two
are isomorphic ; the third is not ~
right subtrees are isomorphic , or the left subtree of one is isomorphic with the
right subtree of the other , and the two other subtrees are isomorphic .
Program 3.25 defines a predicate isotree ( Treel , Tree2) which is true if Treel
and Tree2 are isomorphic . The predicate is symmetric in its arguments .
Programs related to binary trees involve double recursion , one for each branch
of the tree . The double recursion can be manifest in two ways . Programs can
have two separate cases to consider , as in Program 3.24 for tree_member. In
contrast , Program 3.12 testing membership of a list has only one recursive case.
Alternatively the body of the recursive clause has two recursive calls , as in each
of the recursive rules for isotree in Program 3.25.
The task in Exercise 3.3(i ) is to write a program for substituting for elements
in lists . An analogous program can be written for substituting elements in binary
trees . The predicate substitute (X , Y, Old Tree,New Tree) is true if New Tree is the
result of replacing all occurrences of X by Y in Old Tree. An a:xiomatization of
substitute / 4' is given as Program 3.26.
Many applications involving trees require access to the elements appearing
as nodes . Central is the idea of a tree traversal which is a sequence of the nodes
of the tree in some predefined order . There are three possibilities for the linear
order of traversal : preorder , where the value of the node is first , then the nodes in
the left subtree , followed by the nodes in the right subtree , inorder , where the left
nodes come first followed by the node itself , and the right nodes , and postorder
where the node comes after the left and right subtrees .
A definition of each of the three travers als is given in Program 3.27. The
recursive structure is identical ; the only difference between the programs is the
order the elements are composed by the various append goals .
. 3.4
60 Recursive Pro gram mmg
preorder ( Tree,Pre) +-
Pre is a preorder traversal of the binary tree Tree.
(iii ) Define the relation ordered( TreeD/ Integers ) , which holds if Tree is an ordered
tree of integers , that is , for each node in the tree the elements in the left
subtree are smaller than the element in the node , and the elements in the
right subtree aloelarger than the element in the node ,
(Hint : Define two auxiliary relations , ordered_left (X , Tree) , and ordered
_right (X , Tree) , which hold if both X is smaller (larger ) than the root of
Tree, and Tree is ordered ,)
(iv ) Define the relation tree_insert (X , Tree, Treel ) , which holds if Treel is an ordered
tree resulting from inserting X into the ordered tree Tree. If X already
occurs in Tree, then Tree and Treel are identical .
(Hint : Four axioms suffice .)
The logic programs illustrated so far in this chapter have manipulated natural
numbers , lists , and binary trees . The programming style is applicable more generally
. This section gives four examples of recursive programming - a program
for defining polynomials , a program for symbolic differentiation , a program for
solving the Towers of Hanoi problem , and a program for testing the satisfiability
of Boolean formulae .
The fact polynomial (X ,X) says that a term X is a polynomial in itself . The
rule
polynomial(Terml+Term2,X) +-
polynomial(Terml,X), polynomial(Term2,X)
62 RecursiveProgramming 3.5
polynomial ( Expression , X ) + -
Expression is a polynomial in X .
polynomial ( X , X ) .
polynomial ( Term , X ) + -
constant ( Term ) .
polynomial ( Term iN , X ) + -
says that the sum Terml + Term2 is a polynomial in X if both Terml and Term2
are polynomials in X .
Other conventions used in Program 3 . 28 are the use of the unary predicate
constant for recognizing constants , and the binary functor i to denote exponentiation
The next example is a program for taking derivatives . The relation scheme
respect to X .
derivative ( X , X , s ( O ) ) .
reads : " The derivative of sin ( X ) with respect to X is cos ( X ) . " Natural mathematical
derivative (X ,X ,s ( 0 ) ) .
derivative (Xjs (N ) ,X ,s (N ) * XjN ).
derivative ( sin (X ) ,X ,cos (X ) ) .
derivative ( cos ( X ) ,X , - sin (X ) ) .
derivative ( ejX ,X ,ejX ).
derivative ( log (X ) ,x , 1 IX ) .
derivative ( F + G ,X ,DF + DG ) +-
derivative ( F ,X ,D F ) , derivative ( G ,X ,DG ) .
derivative ( F - G ,X ,DF - DG ) +-
derivative ( F ,X ,D F ) , derivative ( G ,X ,DG ) .
derivative ( F * G ,X ,F * DG + DF * G ) +-
derivative (F ,X ,DF ) , derivative ( G ,X ,DG ) .
derivative ( l / F ,X , - DF / (F * F ) ) +-
derivative ( F ,X ,DF ) .
derivative ( F / G ,X , ( G * DF - F * DG ) / ( G * G ) ) +-
derivative ( F ,X ,DF ) , derivative ( G ,X ,DG ) .
Sums and products of terms are differentiated using the sum rule and product
rule , respectively . The sum rule states that the derivative of a sum is the sum of
derivatives . The appropriateclauseis:
The product rule is a little more complicated, but the logical clause is just the
mathematical definition :
derivative(F*G,X ,F *DG+ DF * G) +-
derivative(F ,X ,DF ), derivative(G,X ,DG).
hanoi(N,A ,BiG,Moves) +-
Moves is a sequence of moves for solving the towers of
Hanoi puzzle with N disks and three pegs, A , Band G.
Nonetheless , a version of the chain rule is possible for each particular function .
For example, we give the rule for differentiating XN and sin(X) :
derivative(Ujs (N) ,X ,s(N) * UrN *DU) ~ derivative(U ,X ,DU ).
derivative(sin(U),X ,cos(U)*DU ) ~ derivative(U ,X ,DU).
The difficulty of expressing the chain rule for differentiation arises from our
choice of representation of terms . Both Programs 3.28 and 3.29 use the "natural "
representation from mathematics where terms represent themselves . A term such
as sin (X ) is represented using a unary structure sin . If a different representation
were used, for example , unary _term ( sin ,X) where the name of the structure is
made accessible, then the problem with the chain rule disappears . The chain rule
can then be formulated as
Note that all the rules in Program 3.29 would have to be reformulated in terms
of this new representation , and would appear less natural .
People take for granted the automatic simplification of expressions when differentiating
expressions . Simplification is missing from Program 3.29. The answer
to the query derivative (9* x + 2,x,D ) ? is D = ( 9* 1 + 0* x) + O. We would immediately
simplify D to 9, but it is not specified in the logic program .
The next example is a solution to the "Towers of Hanoi " problem , a standard
introductory example in the use of recursion . The problem is to move a tower of
n disks from one peg to another , with the help of an auxiliary peg . There are
two rules . Only one disk can be moved at a time , and a larger disk can never be
placed on top of a smaller disk .
There is a legend associated with the game. Somewhere hidden in the surroundings
of Hanoi , an obscure eastern village when the legend was first told ,
3.5 Manipulating symbolic expressions 65
satisfiable(Formula) i -
There is a true instance of the Boolean formula Formula .
satisfiable (true).
satisfiable (X /\ Y ) i - satisfiable(X ), satisfiable(Y ).
satisfiable (XVY ) i - satisfiable(X ).
satisfiable (XVY ) i - satisfiable(Y ).
satisfiable ("'-'X ) i - invalid (X ) .
invalid(Formula) +-
There is a false instance of the Boolean formula Formula .
invalid (false) .
invalid (XVY ) i - invalid (X ), invalidY ).
invalid (X /\ Y ) i - invalid (X ).
invalid (X /\ Y ) i - invalidY ).
invalid ("""Y ) i - satisfiable(Y ).
Program 3.31 : Satisfiability of Boolean formulae
(iv ) Write a program for the relation negation - inwards (Fl ,F2) which is true if F2
is the logical formula resulting from moving all negation operators occurring
in the formula Fl inside conjunctions and disjunctions .
3 . 6 Background
Many of the programs in this chapter have been floating around the logic
programming community , and their origins have become obscure . For example ,
several appear in Clocksin and Mellish ( 1984 ) and the uneven collection of short
The classic reference for binary trees is Knuth ( 1968 ) and for sorting Knuth
( 1975 ) .
Many of the basic programs for arithmetic and list processing have a simple
see , for example , Boyer and Moore ( 1979 ) and Sterling and Bundy ( 1982 ) .
The computation model used in the first three chapters of the book has a
severe restriction . All goals appearing in the proof trees are ground . All rule
instances used to derive the goals in the proof trees are also ground . The abstract
interpreter described assumes that the substitutions giving the desired ground
instances can be guessed correctly . In fact the correct substitutions can be computed
rather than guessed.
This chapter presents the full computation model of logic programs . The
first section presents the unification algorithm which removes the guesswork in
determining instances of terms . The second section presents an appropriately
modified abstract interpreter , and gives example computations of logic programs .
4.1 Unification
terms. For example, member(X ,tree(Left,X ,Right)) and memberY ,tree(Left, Y,Z) )
are alphabetic variants .
A unifier of two terms is a substitution making the terms identical . If two
terms have a unifier , we say they unify . There is a close relationship between
unifiers and common instances . Any unifier determines a common instance , and
conversely any common instance determines a unifier .
For example, append([1,2,3],[3,4],List ) and append([.X] Xs], Ys,[.X] Zs]) unify . A
unifying substitution is { X = l , Xs= [2,3], Ys= [3,4], List = [lIZs ]} . Their common
instance, determined by this unifying substitution , is append([1,2,3],[3,4],[lizs ]).
A most general unifier or mgu of two terms is a unifier such that the associated
common instance is most general . If two terms unify , then there is a unique most
general unifier . The uniqueness is up to renaming of variables . Equivalently , two
terms have a unique most general common instance , up to alphabetic variants .
A unification algorithm computes the most general unifier of two terms , if it
exists , and reports failure otherwise .
The algorithm for unification presented here is based on solving equations .
The input for the algorithm is two terms Tl and .T2. The output of the algorithm
is the mgu of the two terms if they unify , or failure if the terms do not unify . The
algorithm uses a pushdown stack for storing the equations that need to be solved ,
and a location , (), for collecting the substitution comprising the output .
The location e is initially empty , and the stack is initialized to contain the
equation Tl = T2. The algorithm consists of a loop of popping an equation from
the stack and processing it . The loop terminates if failure occurs in processing an
invalid equation , or when the stack becomes empty .
We consider the possible actions for dealing with the popped equationS = T.
The simplest case is if S and T are identical constants or variables . This equation
is correct , and nothing further needs to be done . The computation continues by
popping the next equation from the stack .
If S is a variable , and T is a term not containingS , the following happens .
The stack is searched for all occurrences of S, which are replaced by T. Similarly
all occurrences of S in (J are replaced by T. Then the substitutionS = T is added
to (J. It is significant that S does not occur in T. The test embodied by the phraBe
"not containing " is known aB the occurs check.
If T is a variable , and S is a term not containing T, i .eT satisfies the occurs
check with respect to S, the symmetric sequence of actions happens .
Equations are added to the stack if S and T are compound terms with the
sameprincipal functor and arity , J( 81,. . ., 8n) and J( T1,. . ., Tn) say. For the terms
70 The Computation Model of Logic Programs 4.1
to unify , each of the argument pairs must simultaneously unify . This is achieved
by pushing the n equations , Si = Ti , onto the stack .
In any other case, failure is reported , and the algorithm terminates . If the
stack is emptied , the terms unify , and the unifier can be found in (). The complete
algorithm is given as Figure 4.1. The occurs check is embodied in the phrase
"that does not occur in ."
We do not prove the correctness of this algorithm , nor analyze its complexity .
The interested reader is referred to the literature in the background section at
the end of the chapter .
Consider attempting to unify the terms append( [a,b], [c, dJ,Ls) and append(
[X] Xs ], Ys, [X] Zs]) . The stack is initialized to the equation
These two terms have the same functor , append, and arity , 3, and so we add the
three equations relating the subterms of the two terms . These are [a, b] = [.)[jXs ],
[c,d] = Ys, and Ls = [X I Zs].
The next equation , [a, b] = [X IXs] , is popped from the stack . These two compound
terms have the same functor , " ." , and arity , 2, and so two equations , a= X
and [b] = Xs are added to the stack . Continuing , the equation a= X is popped .
This is covered by the second case in Figure 4.1. X is a variable not occurring in
the constant , a. All occurrences of X in the stack are replaced by a. One equation
is affected , namely Ls = [XlZs ], which becomes Ls = [aIZs] . The equation X = a is
added to the initially empty substitution , and the algorithm continues .
The second case also covers [c, dj = Ys. Another substitution , Ys= [c,dj, is
added to the collection , and the final equation , Ls = [ al Zs], is popped . This is
handled by the symmetric first case. Ls does not occur in [aIZs] , so the equation
is added as is to the unifier , and the algorithm terminates successfully . The unifier
is { X = a, Xs = [b], Ys= [c,dj, Ls = [aIZs]} . The common instance produced by the
unifier is append( [ a,b],[ c, dj, [ aIZs]) . Note that in this unification , the substitutions
were not updated .
Most Prolog implementations omit the occurs check from the unification
algorithm , for pragmatic reasons . This issue is discussed further in Section 6.1.
4.1 Unification 71
is replaced by
make X a reference to Y.
We revise the abstract interpreter of Section 1.8 in the light of the unification
algorithm . The result is the full computation model of logic programs . All the
concepts introduced previously , such as goal reductions and computation traces ,
have their analogue in the full model .
The computation progress es via goal reduction . At each stage there is some
resolvent , a conjunction of goals to be proved . A goal in the resolvent and clause
in the logic program are chosen such that the clause's head unifies with the goal .
The computation proceeds with a new resolvent , obtained by replacing the chosen
goal by the body of the chosen clause in the resolvent , and then applying the most
general unifier of the head of the clause and the goal . The computation terminates
when the resolvent is empty . In this case, we say the goal is solved by the program .
To describe computations more formally we introduce some useful concepts .
A computation of a goal Q= Qo by a program Pis a (possibly infinite ) sequence of
triples ( Qi , Gi , Ci ) ' Qi is a (conjunctive ) goal , Gi is a goal occurring in Qi , and Ci
is a clause A +- B1,. . .,Bk in Prenamed so that it contains new variable symbols
not occurring in Qj , 0 $ j $ i . For all i > 0, Qi+ l is the result of replacing Gi by
the body of Ci in Qi , and applying the substitution Oi, the most general unifier
of Gi and Ai , the head of Ci ; or the constant true if Gi is the only goal in Qi
and the body of Ci is empty ; or the constant fail , if Gi and the head of Ci do not
unify .
4.2 An abstract interpreter for logic programs 73
Gi is the parent of any goal it invokes . Two goals with the same parent goal are
sibling goals .
pairs ( GiJ ( J ~ ) , where ( J ~ is the subset of the mgu ( Ji computed at the " th reduction ,
restricted to variables in Gi .
of the interpreter for ground goals ( Figure 1 . 1 ) . The restriction to using ground
is applied to the chosen goal and head of the chosen clause to find the correct
Care needs to be taken with the variables in rules to avoid name clashes .
Variables are local to a clause . Hence variables in different clauses that have
the same name are , in fact , different . This is ensured by renaming the variables
appearing in a clause each time the clause is chosen to effect a reduction . The
new names must not include any of the variable names used previously in the
computation .
The policy for adding and removing goals from the resolvent is called the
scheduling policy of the interpreter . The abstract interpreter leaves the scheduling
policy unspecified .
Consider solving the query append([a,b],[c,dJ,Ls) ? by Program 3.15 for append
using the abstract interpreter of Figure 4.2. The resolvent is initialized to
be append([a,b],[c,dJ,Ls) . It is chosen as the goal to reduce, being the only one.
The rule chosen from the program is
The unifier of the goal and the head of the rule is { X = a, Xs= [b], Ys= [c,dj,
Ls= [alZs]} . A detailed calculation of this unifier appearedin the previous section.
The new resolvent is the instance of append(Xs, Ys,Zs) under the unifier , namely
append([b],[c,dj,Zs). This goal is chosen in the next iteration of the loop. The
same clause for append is chosen, but variables must be renamed to avoid a clash
of variable names . The version chosen is
The unifier of the head and goal is { Xl = b, Xsl = [ ], Ysl = [c,dj, Zs= [bIZsl]} . The
new resolvent is append([ ],[c,dj,Zsl ). This time the fact, append([ ],Zs2,Zs2),
is chosen; we again rename variables as necessary. The unifier this time is
{ Zs2= [c,dj, Zsl = [c,dj} . The new resolvent is empty and the computation terminates
.
To compute the result of the computation , we apply the relevant part of the
mgu 's calculated during the computation . The first unification instantiated Ls to
[alZs]. Zs was instantiated to [blZsl ] in the second unification , and Zsl further
became [c,dj. Putting it together, Ls has the value [al[bl[c,dj]], or more simply,
[a, b, c, dj.
The computation can be represented by a trace . The trace of the append
computation described above is presented in Figure 4.3. To make the traces
clearer , goals are indented according to the indentation of their parent . A goal
has an indentation depth of d+ 1 if its parent has indentation depth d.
As another example, consider solving the query son(S,haran) by Program 1.2.
It is reduced using the clause son(X , Y) +- fatherY ,X) , male(X) . A most general
4.2 An abstract interpreter for logic programs 75
son(S,haran) son(S,haran )
father (haran,S) S= lot male (S) S= lot
male(lot ) father (haran ,lot )
true true
0
~- ~
(
.
'~
~ g
~
oq j
- ~
rn .
~ rn ~
(
(
g i
0 ( tJ M ~ ;
CD1 CDM
~ e ' ~ " ~P
0 ~ (
= ~
CD tJ ~
oqo 1
~
, ~
76 The Computation Model of Logic Programs 4.2
The two traces in Figure 4. 4 illustrate two successful computations , where the
choice of goal to reduce at the second step of the computation differs .
The choice of the clause to effect the reduction is nondeterministic . Not every
choice will lead to a successful computation . For example , in both of the traces
in Figure 4.4, we could have gone wrong . If we had chosen to reduce the goal
father(haran,S) with the fact father(haran,yiscah), we would not have been able
to reduce the invoked goal male(yiscah) . For the second computation , had we
chosento reduce male(S) with male(isaac), the invoked goal father( haran, isaac)
could not have been reduced .
hanoi(s(s(s(O))),a,b,c,Ms)
hanoi(s(s(O)) ,a,c,b,Msl )
hanoi (s(0) ,a,b,c,Ms 11)
hanoi(O,a,c,b,Mslll ) Mslll = [ ]
hanoi(0,c,b,a,Ms I12) Ms112= [ ]
append([ ],[a to b],Msll ) Msll = [a to b]
hanoi(s(O) ,b,c,a,Ms12)
hanoi(0,b,a,c,Ms121) Ms121= [ ]
hanoi(0,a,c,b,Ms I22 ) Ms I22= [ ]
append([ ],[b to c],Ms12) Ms I2 = [b to c)
append([a to b],[a to c,b to c],Msl ) Msl = [a to blXs]
append([ ],[a to c,b to c],Xs) Xs= [a to c,b to c)
hanoi(s(s(O) ),c,b,a,Ms2)
hanoi(s(0),c,a,b,Ms21)
hanoi(0,c,b,a,Ms211) Ms211= [ ]
hanoi(0,b,a,c,Ms212) Ms212= [ ]
append([ ],[c to a],Ms21) Ms21= [c to a]
hanoi(s(O) ,a,b,c,Ms22)
hanoi(0,a,c,b,Ms221) Ms221= [ ]
hanoi(0,c,b,a,Ms222) Ms222= [ ]
append([ ],[a to b],Ms22) Ms22= [a to b)
append([c to a],[c to b,a to b],Ms2) Ms2= [c to alYs]
append([ ],[c to b,a to b],Ys) Ys= [c to b,a to b)
append([a to b,a to c,b to c],[a to b,c to a,c to b,a to b],Ms)
append([a to c,b to c],[a to b,c to a,c to b,a to b],Xs2)
append([b to c],[a to b,c to a,c to b,a to b],Xs3)
append([ ],[a to b,c to a,c to b,a to b],Xs4)
true
Output : Ms= [a to b,a to c,b to c,a to b,c to a,c to b,a to b)
Figure 4.5: Solving the Towers of Hanoi
access to fields in records , parameter passing , and more . We defer the subject till
the computation model for Prolog is introduced in Chapter 6.
A computation of G by P terminates if Gn = true or fail for some n ~ o. Such
a computation is finite and of length n. Successful computations correspond to
terminating computations which end in true . Failing computations end in fail .
All the traces given so far have been of successful computations .
Recursive programs admit the possibility of nonterminating computations .
The query append(Xs ,[c, dj, Ys) with respect to append can be reduced arbitrarily
78 The Computation Model of Logic Programs 4.2
many times using the rule for append . In the process Xs becomes a list of arbitrary
All the traces presented so far have an important feature in common . If two
goals Gi and Gj are invoked from the same parent , and Gi appears before Gj in
the trace , then all goals invoked by Gi will appear before Gj in the trace . This
scheduling policy makes traces easier to follow , by solving queries depth first .
before their values are needed for other parts of the computation . A good ordering
hanoi ( s ( s ( s ( 0 ) ) ) , a , b , c , Ms )
If the append goal is now chosen , the append fact could be used ( incorrectly ) to
reduce the goal . By reducing the two hanoi goals first , and all the goals they
invoke , the append goal has the correct values for Msl and Ms2 .
Prolog , Parlog , and GHC , have been designed in order to exploit this potential
parallelism .
(ii ) Give a trace for the goal derivative ( 9* sin (x) - 4* cos(x) ,x,D) using Program
3.29 for derivative.
(iii) Practice tracing your favorite computations.
4 .3 Background
Unification plays a central role in automated deduction and the use of logical
of Robinson ( 1965 ) . Algorithms for unification have been the subject of much
investigation : see , for example , Martelli and Montanari ( 1982 ) , Paterson and
A proof that the choice of goal to reduce from the resolvent is arbitrary can
be found in Apt and van Emden (1982) or in the text of Lloyd (1984).
A method for replacing the runtime occurs checkwith compile-time analysis
wassuggested
by Plaisted(1984).
Attempts have been made to make unification without the occurs check more
than a necessary expedient for practical implementations of Prolog . In particular
, Colmerauer (19S2b) proposes a theoretical model for such unifications that
incorporates computing with infinite terms .
A novel use of unification without the occurs check appears in Eggert and
Chow (1983) where Escher -like drawings which gracefully tend to infinity are
constructed .
Chapter 5
Theory of Logic PrograIns
5 . 1 Semantics
to describe more formally the relation a program computes . The first chapter
informally describes the meaning of a logic program P as the set of ground instance
~ that are deducible from P via a finite number of applications of the rule
program . The operational meaning of a logic program P is the set of ground goals
that are instances of queries that are solved by P using the abstract interpreter
theoretic semantics of first - order logic . In order to define it , some new terminology
is needed .
the set of all ground terms that can be formed from the constants and function
There is one constant symbol , 0 , and one unary function symbol , 8. The Her -
The H erbrand base , denoted B ( P ) , is the set of all ground goals that can
be formed from the predicates in P and the terms in the Herbrand universe .
The Herbrand .bMe is infinite if the Herbrand universe is . For our example program
there is one predicate natural _ number . The Herbrand base , B ( P ) , equals
For our example , natural - number ( 0) must be in every model , and natural
_numbers ( . X) ) is in the model if natural _number ( . X) is . Any model of Program
3 . 1 thus includes the whole Herbrand base .
It is easy to see that the intersection of two models for a logic program P
is again a model . This property allows the definition of the intersection of all
models . The model obtained as the intersection of all models is known as the
minimal model and denoted M ( P ) . The minimal model is the declarative meaning
of a logic program .
The declarative meaning of the program for natural _ number , its minimal
The Herbrand universe is [ ],[[ ]],[[ ],[ ]],. . ., namely all lists that can be built using
the constant [ ]. The Herbrand base is all combinations of lists with the append
predicate. The declarative meaning is all ground instances of append([ ],Xs,Xs),
that is, append([ J,[ ]J[ ])Jappend([ ]J[[ ]],[[ ]]),. . ., together with goals such as append
([[ ]],[ ],[[ ]]) which are logically implied by applications ) of the rule. This is
82 Theory of Logic Programs 5.1
only a subset of the Herbrand base. For example , append([ ],[ ],[[ ]]) is not in the
meaning of append but is in the Herbrand base.
Denotational semantics assigns meanings to programs based on associating
with the program a function over the domain computed by the program . The
meaning of the program is defined as the least fixpoint of the function , if it exists .
The domain of computations of logic programs is interpretations .
Given a logic program P, there is a natural mapping Tp from interpretations
to interpretations , defined as follows :
5 .2 Program correctness
Every logic program has a well -defined meaning as discussed in Section 5.1.
This meaning is neither correct nor incorrect .
The meaning of the program , however , mayor may not be what was intended
by the programmer . Discussions of correctness must therefore take into consideration
the intended meaning of the program . Our previous discussion of proving
correctness and completeness similarly was with respect to an intended meaning
of a program .
We recall the definitions from Chapter 1. An intended meaning of a program
P, is a set of ground goals. We use intended meanings to denote the set of goals
intended by the programmer for his program to compute . A program P is correct
5.2 Program correctness 83
Consider Program 3.1 defining the natural numbers . This program is terminating
over its Herbrand base. However the program is nonterminating over the
domain { natural_number(X) } . This is causedby the possibility of the nonterminating
computation depicted in the trace in Figure 5.1.
For any logic program , it is useful to find domains over which they are terminating
. This is usually difficult for recursive logic programs . We need to describe
recursive data types in a way which allows us to discuss termination .
We illustrate the use of these definitions to find termination domains for the
recursive programs using recursive data types in Chapter 3. Specific instances of
the definitions of complete and incomplete types are given for natural numbers
and lists. A (complete) natural number is either the constant 0, or a term of the
form sn(o). An incomplete natural number is either a variable, X , or a term of
84 Theory of Logic Programs 5.2
the form sn(X) where X is a variable . Program 3.2 for ::$: is terminating for the
domain consisting of goals where the first and / or second argument is a complete
natural number .
5 .3 Complexity
The multiple uses of logic programs slightly changes the nature of complexity
measures. Instead of looking at a particular use and specifying complexity in
terms of the sizes of the inputs , we look at goals in the meaning and see how
. they were derived . A natural measure of the complexity of a logic program is the
length of the proofs generated for goals in its meaning .
We begin discussion with a new definition , the size of a goal . The size of
a term is the number of symbols in its textual representation . Constants and
variables , consisting of a single symbol , have size one. The size of a compound
term is one more than the sum of the sizes of its arguments . For example , the
list [b] has size 3, [a,b] has size 5, and the goal append( [a,b],[c,dj,Xs ) has size 12.
In general , a list of n elements has size 2' n + l .
5.3 Complexity 85
(i ) Show that the size of a goal in the meaning of append joining a list of length
n to one of length m to give a list of length n + m is 4 -n + 4 -m + 1- Show that
a proof tree has m + 2 nodes - Hence show that append has linear complexity -
Would the complexity be altered if the type condition were added ?
86 Theory of Logic Programs 5.3
(ii ) Show that Program 3.3 for plus has linear complexity.
(iii ) Discuss the complexity of other logic programs.
5 .4 Search trees
There are in general many search trees for a given goal with respect to a
program. Figure 5.2 shows two search trees for the query son(SJharan) ? with
respect to Program 1.2. The two possibilities correspond to the two choices of
goal to reduce from the resolvent father(haranJ S
) Jmale(S) . The trees are quite
distinct , but both have a single success branch corresponding to the solution of
the query S= lot . The respective success branch es are given as traces in Figure
4 .4 .
We adopt some conventions when drawing search trees . The leftmost goal
of a node is always the selected one. This implies that the goals in derived goals
may be permuted so that the new goal to be selected for reduction is the first
goal . The edges are labeled with substitutions that are applied to the variables
in the leftmost goal . These substitutions are computed as part of the unification
algorithm .
Search trees contain multiple success nodes if the query has multiple solu-
5.4 Search trees 87
son(S,haran) son(S,haran)
, +
ather (haran ,S) ,male (S) male(S),father (haran,S)
S= lot J S= yiscaht S= milcal1 \ S= isaacl S= lot \
male (lot ) male (yiscah ) male (milcal1 ) father (haran ,isaac) father (haran,lot )
t t
true true
append(As,Bs,[a,b,c])
+ As= [aIAsl ]I ""'~ As= [ ],Bs= [a,b,c]
append(Asl ,Bs,[b,c]) ~............... true
+AS1
=[bIAs2ff
append(As2,Bs,[c])
"""""""""""""""", Asl
true
=[ ],Bs=[b,c]
. AS2
=[cIAS
append(As3,Bs,[ ])
~I """""""""",~"""" As2
true
=[ ],Bs=[c]
~ As3= [ ],Bs= [ ]1
true
tions . Figure 5.3 contains the search tree for the query append(As ,Bs,[a, b, c]) with
respect to Program 3.15 for append, asking to split the list [a,b,c] into two . The
solutions for As and Bs are found by collecting the labels of the edges in the
branch leading to the success node . For example , in the figure , following the
leftmost branch gives the solution { As = [a,b,c),Bs = [ )} .
The number of success nodes is the same for any search tree of a given goal
with respect to a program .
Search trees can have infinite branch es, which correspond to nonterminating
computations . Consider the goal append(Xs ,(c,d] , Ys) with respect to the standard
program for append. The search tree is given in Figure 5.4. The infinite branch
is the nonterminating computation given in Figure 4.6.
. .
. .
There is a deeper point lurking . The relationship between search trees and
proof trees is the relationship between deterministic computations and nondeterministic
computations . Whether the complexity classes defined via proof trees
are equivalent to complexity classes defined via search trees is a reformulation of
the classic P = NP question in terms of logic programming .
(i) Transform the traces of Figure 4.3 and 4.5 into search trees.
(ii ) Draw a searchtree for the query sort([2,4,1,S,3],Xs) using permutation sort.
Logic programs are collections of rules and facts describing what is true .
Untrue facts are not expressed explicitly ; they are omitted . In this section we
describe an extension to the logic programming computation model that allows a
limited use of negative information in programs .
We define a relation not G, and describe its meaning . It is only a partial form
of negation from first -order logic . The relation not uses the negation as failure
rule . A goal not G will be assumed to be a consequence of a program P if G is
not a consequence of P .
Let us see a simple example . Consider the program comprised of two facts :
The goal not likes ( sarah , pomegranates ) follows from the program by negation as
failure . The search tree for the goallikes ( sarah , pomegranates ) has a single failure
node .
Using negation as failure allows easy definition of many relations . For example
, a declarative definition of the relation disjoint (Xs , Ys) that two lists , Xs and
Ys, have no elements in common is possible as follows .
5.6 Background
The classic paper on the semantics of logic programs is of van Emden and
Kowalski (1976) . Important extensions were given by Apt and van Emden (1982) .
In particular , they showed that the choice of goal to reduce from the resolvent
is arbitrary by showing that the number of success nodes is an invariant for the
search trees .
90 Theory of Logic Programs 5.6
In Shapiro ( 1984) complexity measures for logic programs are compared with
the complexity of computations of alternating Turing machines . It is shown that
goal-size is linearly related to alternating space, the product of length and goal-size
is linearly related to alternating tree- size, and the product of depth and goal-size
is linearly related to alternating time .
The claBsic name for search trees in the literature is SLD trees . The name
SLD WaB coined by research in automatic theorem proving which preceded the
birth of logic programming . SLD resolution is a particular refinement of the resolution
principle introduced in Robinson (1965) . Computations of logic programs
can be interpreted as a series of resolution steps , and in fact SLD resolution steps,
and are still commonly described thus in the literature . The acronym SLD stands
for Selecting a literal , using a Linear strategy , restricted to Definite clauses.
The first proof of the correctness and completeness of SLD resolution , albeit
under the name LUSH -resolution , was given by Hill (1974) .
The subject of negation has received a large amount of attention and interest
since the inception of logic programming . The fundamental work on the semantics
of negation - as-failure is by Clark ( 1978) . Clark 's results were extended by Jaffar
et al . (1983) who proved the soundness and completeness of the rule .
The concept of negation as failure is a restricted version of the closed world
assumption as discussed in the database world . For more information see Reiter
(1978) . The exact relationship between different formulations of negation
is discussed in Lloyd (1984) , which also covers many of the issues raised in this
chapter .
Leonardo Da Vinci , Portrait of the Florentine poet Bernardo
Bellinzone , engaged at the Court of Ludovico Sforza. Woodcut ,
based on a drawing by Leonardo . From Bellinezone 's Rime . Milan
1493
P art II
The Prolog Language
The notation previously used for traces must be extended to handle failure
and backtracking . An f after a goal denotes that a goal failed , that is there
was no clause in the program whose head unified with the goal . The next goal
after a failed goal is where the computation proceeds on backtracking . It already
appears as a previous goal in the trace at the same depth of indentation , and can
be identified by the variable names . We adopt the Edinburgh Prolog convention
96 Pure Prolog 6.1
son(X ,haran)?
father (haran,X ) X = lot
male(lot )
true
Output : X = lot
,
that a 'j ' typed after a solution denotes a continuation of the computation to
search for more solutions . Unifications are j ' 1dicated as previously .
Trace facilities provided by particular Prolog implementations vary from our
description . For example , some Prolog implementations always give all solutions ,
while others wait for a user response after each solution .
The trace of append([a,b], [c, dj,Ls) ? giving the answer Ls = [a, b, c, dj is precisely
the trace given in Figure 4.3. Figure 4.5 giving the trace for solving the
Towers of Hanoi with 3 discs is also a trace of the hanoi program considered as
a Prolog program solving the query hanoi (s(s(s( O) ) ) a,b,c,Moves) ? The trace of
a deterministic computation is the same when considered as a logic program or a
Prolog program , provided the order of goals is preserved .
The next example is answering the query append(Xs , Ys,[a, b, c]) ? with respect
to Program 3.15 for append. There are several solutions of the query . The search
tree for this goal was given as Figure 5.3. Figure 6.2 gives the Prolog trace .
Tracing computations is a good way to gain understanding of the execution
model of Prolog . We give a slightly larger example , sorting a list with the quick -
6.1 The execution model of Prolog 97
no (more) solutions
sort program (Program 3.22 reproduced here). Computations using quicksort are
essentially deterministic , and show algorithmic behavior of a Prolog program .
Figure 6.3 gives a trace of the query quicksort([2,1,9],Xs) ? Arithmetic comparisons
are assumedto be unit operations, and the standard program for appendis
used .
quicksort([X IXs],Ys) +-
partition (Xs,X ,Littles ,Bigs) ,
quicksort(Littles ,Ls),
quicksort (Bigs,Bs) ,
append(Ls,[X IBs],Ys).
quicksort([ ],[ ]).
partition ([X IXs],Y ,[X ILs],Bs) +-
x ~ Y , partition (Xs,Y ,Ls,Bs).
partition ([X IXs],Y ,Ls,[X IBs]) +-
X > Y , partition (Xs,Y ,Ls,Bs).
partition ([ ],Y ,[ ],[ ]).
quicksort([2,1,3] ,Qs)
partition ([1,3],2,Ls,Bs)
152 Ls= [lILsl ]
partition ([3] ,2,Ls1,Bs)
352 f Lsl = [3ILs2]
partition ([3] ,2,Lsl ,Bs)
3 > 2 Bs= [3IBsl]
partition ([ ],2,Ls1,Bs1) Lsl = [ ]=Bsl
. quicksort([1],Qs1)
partition ([ ],1,Ls2,Bs2) Ls2 = [ ] = Bs2
quicksort([3] ,Qs4)
partition ([ ],3,Ls3,Bs3) Ls3 = [ ] = Bs3
append([ ],[2,3],Ys) Ys = [2 ,3 ]
true
An example is Figure 6.3 and the choice of a new clause for the goal partition
([9],2,Lsl ,Bs).
6.2 Comparison to conventional programming languages 99
compare the control flow and data manipulation of Prolog to that of Algol - like
languages .
procedure invocation , and the ordering of goals in the body of clauses corresponds
procedure A
call Bl ,
call B2 ,
.
.
.
call Bn ,
end .
The recursive goal invocation in Prolog is similar in its behavior and its
proceed ( e . g . , all branch es of a case statement are false ) , a runtime error occurs . In
Prolog the computation is simply undone to the last choice made , and a different
~
general record structures in conventional programming languages . The handling
of logical variables . Logical variables refer to individuals rather than memory locations
. Consequently , having specified a particular individual , the variable cannot
be made to refer to another individual . In other words , logic programming does
not support destructive aBsignment where the contents of an initialized variable
can change.
Record creation can be seen with the unification of the goal partition
([1,9],.2,Ls,Bs) with the head of the partition procedure partition ([X] Ys],Z,
[X] Ls1],Bs1). As a result Ls is instantiated to [1ILs1]. Specifically, Ls is made
into a list and its head is assigned the value 1, namely record creation and field
assignment via unification .
the computation .
This brief discussion of the different way of manipulating data does not help
with the more interesting question : How does programming in Prolog compare
with programming in conventional programming languages ? That is an implicit
topic in the rest of this book .
6 .3 Background
The origins of Prolog are shrouded in mystery . All that is known is that
the two founders Robert Kowalski , then at Edinburgh , and Alain Colmerauer at
Marseille worked on similar ideas during the early 70's, and even worked together
during one summer . The results were the formulation of the logic programming
philosophy and computation model by Robert Kowalski (1974), and the design
and implementation of the fIrst logic programming language , Prolog , by Alain
Colmerauer and his colleagues(1973).
A major force behind the realization that logic can be the basis of a practical
programming language has been the development of efficient implementation
techniques) as pioneered by Warren (1977). Warrens compiler identified special
cases of unification and translated them into efficient sequences of conventional
memory operations .
Variations of Prolog with extra control features, such as IC-Prolog (Clark
and McCabe, 1979), have been developed, but have proved too costly in run-
time overhead to be seriously considered as alternatives to Prolog . We will refer
to particular interesting variations that have been proposed in the appropriate
sections .
Warren et al . adapted Marseille Prolog for the DEC - 10, and their decisions
102 Pure Prolog 6.3
have been very influential . Many systems adopted most of the conventions of
Prolog - 10 (Warren et al ., 1979) , which has become known more generically as
Edinburgh Prolog . Its essential features are described in the widespread primer
on Prolog (Clocksin and Mellish , 1984) . This book draws mainly on Edinburgh
Prolog as described in its manual (Bowen et al ., 1981) .
A recent paper by Cohen ( 1985) delves further on the relation between Prolog
and conventional languages .
Chapter 7
PrograIn Ining
in Pure Prolog
This chapter discusses the consequences of Prolog 's execution model for the
logic programmer . New aspects of the programming task are introduced . Not
only must the programmer come up with a correct and complete axiomatization
of a relationship , he must also consider its execution according to the model .
7 .1 Rule order
Two syntactic issues, irrelevant for logic programs , are important to consider
when composing Prolog programs . The rule order , or clause order , of clauses in
each Drocedure must be decided . Also the goal order of goals in the bodies of each
~
For the query ancestor(terach,X) '?with respect to Program 7.1, the solutions
will be given in the order , X = abraham, X = isaac, X = jacob and X = benjamin . If
the order of the two rules defining ancestor is swapped , the solutions will appear
in a different order , namely X = benJ.amin , X = jacob , X = isaac and X = abraham.
The different order of ancestor clauses changes the order of searching the
implicit family tree . In one order , Prolog outputs solutions as it goes along . With
the order of the rules swapped , Prolog travels to the end of the family tree and
gives solutions on the way back . The desired order of solutions is determined by
the application , and the rule order of ancestor chosen accordingly .
When the search tree for a given goal has an infinite branch , the order of
clausescan determine if any solutions are given at all. Consider the query append
(Xs,(c,dj, Ys) ? with respect to append. As can be seenfrom the search tree
in Figure 5. 4, no solutions would be given . If , however , the append fact appeared
7.2 Termination 105
before the append rule , an infinite number of pairs Xs , Ys satisfying the query
would be given .
Clause order is more important for general Prolog programs than it is for
pure Prolog programs . Other control features , notably the cut to be discussed in
Chapter 11, depend si~ ficantly on the clause order . When such constructs are
used, clauses lose their independence and modularity , and clause order becomes
significant .
In the book we follow the convention that the recursive clauses precede the
base clauses.
(i ) Verify the order of solutions for the query ancestor ( abraham ,X) with respect
to Program 7 .1, and its variant with different rule order for ancestor , claimed
in the text .
(ii ) What is the order of solutions for the query ancestor (X , benjamin ) with re -
spect to Program 7.1? What if the rule order for ancestor is swapped ?
7 . 2 Termination
Prolog 's depth -first traversal of search trees has a serious problem . If the
search tree of a goal with respect to a program contains an infinite branch , the
computation will not terminate . Prolog may fail to find a solution to a goal , even
though the goal has a finite computation .
married ( X ,Y ) + - married ( Y , X ) .
no computation involving married would ever terminate . For example , the trace
of the goal married ( abraham ,sarah) ? is given in Figure 7.1.
Recursive rules which have the recursive goal as the first goal in the body are
known as left recursive rules . The problematic married axiom is an example . Left
recursive rules are inherently troublesome in Prolog . They cause nonterminating
computations if called with inappropriate arguments .
The best solution to the problem of left recursion is avoidance . The married
relationsllip used a left recursive rule to express commutativity . Commutative
relationships are best handled differently , by defining a new predicate that has a
clause for each permutation of the arguments of the relationship . For the relationship
married , a new predicate , are_married (Personl ,Person2 ) say, would be
defined using two rules :
also stated in terms of incomplete lists . A query does not terminate if the second
parent ( X , Y ) + - child ( Y , X ) .
child ( X , Y ) + - parentY , X ) .
Any computation involving parent or child , for example , parent ( haran , lot ) ? , will
not terminate . The search tree necessarily contains an infinite branch , due to the
circular i ty .
7 . 3 Goal order
Goal order is more significant than clause order . It is the principal means of
specifying sequential flow of control in Prolog programs . The programs for sorting
lists , e . g . , Program 3 . 22 for quicksort , exploit goal order to indicate the sequence
The order of goals can affect the order of solutions . Consider the query daughter
facts female ( milcah ) and female ( yiscah ) is interchanged . The two solutions are
given in the order X = milcah , X = yiscah . H the goal order of the daughter rule
solutions to the query , given the same database , would be X = yiscah , X = milcah .
The reason that the order of goals in the body of a clause affects the order
of solutions to a query is different from the reason that the order of rules in a
procedure affects the solution order . Changing rule order does not change the
search tree that must be traversed for a given query . The tree is just traversed in
Goal order affects the amount of searching the program does in solving a
query by determining which search tree is traversed . Consider the two search
trees for the query son ( X , haran ) ? given in Figure 5 .2 . They represent two different
ways of finding a solution . In the first case , solutions are found by searching
for children of haran and checking if they are male . The second case corresponds
to the rule for son being written with the order of the goals in its body swapped ,
namely as son ( X , Y) +- male ( X) , parentY , X) . Now the query is solved by searching
through all the males in the program and checking if they are children of
haran . If there were many male facts in the program , more search would be
involved . For other queries , for example , son ( sarah ,X) ? , the reverse order has
advantages . Since sarah is not male , the query would fail more quickly .
The optimal goal order of Prolog programs varies with different uses . Consider
the definition of grandparent . There are two possible rules .
If you wish to find someone ' s grandson with the grandfather relationship with
a query such as grandparent ( abraham i X) ?, the first of the rules search es more
directly . If looking for someone ' s grandparent with a query such as grandparent
( X , isaac ) ?, the second rule finds the solution more directly . If efficiency is
important , then it is advisable to have two distinct relationships , grandparent
and grandchild , to be used appropriately at the user ' s discretion .
If the goals in the body are swapped , the ancestor program becomes left recursive ,
and all Prolog computations with ancestor are nonterminating .
The goal order is also important in the recursive clause of the quicksort
algorithm in Program 3 .22 .
The list should be partitioned into its two smaller pieces before recursively sorting
the pieces . If , for example , the order of the partition goal and the recursive sorting
goal is swapped , no computations terminate .
7.3 Goal order 109
The goal order is significant . As written , the program terminates with goals
where the first argument is a complete list . Goals where the first argument is an
incomplete list give nonterminating computations . If the order of the goals in the
recursive rule is swapped , the determining factor of the termination of goals is
the second argument . Calls to reverse with the second argument a complete list
terminate . They do not terminate if the second argument is an incomplete list .
A subtler example comes from the definition of the predicate sublist in terms
of two appendgoals, specifying the sublist as a suffix of a prefix , as given in Program
3.14e. Consider the query sublist([2,9],[1,2,9,4]) ? with respect to the program
. The query is reduced to append(A Xs,Bs,[1,2,9,4]),append(As,[2,9],A Xs) ?
This has a finite search tree , and the initial query succeeds. If Program
3.15e had its goals reversed, the initial query would be reduced to append
(As,[2,9],A Xs),append(A Xs,Bs,[1,2,9,4]) ? This leads to a nonterminating
computation due to the first goal , as illustrated in Figure 5.4.
A useful heuristic for goal order can be given for recursive programs with
tests such as arithmetic comparisons , or determining whether two constants are
different . The heuristic is to place the tests as early as possible . An example
comes in the program for partition , which is part of Program 3.22. The first
recursive rule is
The test X :::; Yshould go before the recursive call . This leads to a smaller search
tree .
(i ) Consider the goal order for Program 3.14d defining a sublist of a list as a
suffix of a prefix . Why is the order of the append goals in Program 3.14d
preferable ?
(Hint : Consider the query sublist(Xs,[a,b,c]) ?)
(ii ) Discuss the clause order, goal order and termination behavior for substitute,
posed as Exercise 3.3(i) .
110 Programming in Pure Prolog 7.4
program is the set of ground goals deducible from it . No distinction was made
between whether a goal in the meaning could be deduced uniquely from the program
is important for Prolog when considering the efficiency of searching for solutions .
Each possible deduction means an extra branch in the search tree . The bigger
the search tree , the longer a computation will take . It is desirable in general to
is solved , and each goal has one redundant solution , then in the event of backtracking
One way for redundancy to occur in Prolog programs is by covering the same
case with several rules . Consider the following two clauses defining the relation
. .
m ~ n ~ mum .
minimum ( X , Y , X ) + - X ~ Y .
minimum ( X , Y , Y ) + - Y ~ X .
The query minimum ( 2 , 2 , M ) with respect to these two clauses has a unique solution
Careful specification of the cases can avoid . the problem . The second clause
can be changed to
minimum ( X , Y , Y ) + - Y < X .
Now only the first rule covers the case when the two numbers have equal values .
3 . 22 for quicksort . The programmer must ensure that only one of the recursive
clauses for partition covers the case when the number being compared is the same
cases . Some of these can be motivated by efficiency . An extra fact can be added
merge ( Xs , Ys , Zs ) +-
merge ( [X I Xs ] , [ YIYs ] , [X I Zs ] ) +-
merge ( [X I Xs ] , [ YIYs ] , [X , X I Zs ] ) +-
X = Y , merge ( Xs , Y siZs ) .
merge ( [X I Xs ] , [ YIYs ] , [X I Zs ] ) +-
x > Y , merge ( [X I Xs ] , Ys , Zs ) .
merge ( [ ] , [X I Xs ] , [X I Xs ]) .
merge ( Xs , [ ] , Xs ) .
member - check ( X , Xs ) +-
X is a member of the list Xs .
member _ check ( X , [X I Xs ] ) .
each of the other clauses for append would have to only cover lists with at lea Bt
There are three separate recursive clauses . They cover the three possible
cases ; when the head of the first list is less than , equal to or greater than the head
of the second list . We discuss the predicates < , = , and > in Chapter 8 . Two
cases are needed when the elements in either list have been exhausted . Note that
we have been careful that the goal merge ( [ ] , [ ] , [ ] ) is only covered by one fact , the
bottom one .
We restrict use of Program 7.3 to queries where both arguments are ground .
This is due to the way =;f is implemented in Prolog , to be discussed in Section
11.3.
Lists are a very useful data structure for many applications written in Prolog .
In this section we revise several logic programs of Sections 3.2 and 3.3 concerned
with list processing . The chosen clause and goal orders are explained , and their
termination behavior presented . The section also discuss es some new examples .
Their properties are analyzed , and a reconstruction offered of how they are composed
.
Programs 3.12 and 3.15, for member and append respectively , are correct
Prolog programs as written . They are both minimal recursive programs , so there
is no issue of goal order . They are in their preferred clause order , the reasons for
which have been discussed earlier in the chapter . The termination of the programs
7.5 Recursive programming in pure Prolog 113
select - first ( X , Xs , Ys ) ~
select Jirst ( X , [ X I Xs ] , Xs ) .
select ( X , [ X I Xs } , Xs ) .
of goal order as the program is minimal recursive . The clause order is chosen
the result of choosing the first element , and so forth . The program terminates
unless both the second and third arguments are incomplete lists .
have the same meaning . Program 7 . 4 , in contrast , has a different meaning from
clauses , analogously to the clause order for append , reflects the more likely mode
of use :
permutation ( ( ] , ( ] ) .
The goal order and the termination behavior of permutation are closely related .
will terminate . The query calls select with its second argument a complete list ,
which terminates generating a complete list as its third argument . Thus there
is a complete list for the recursive permutation goal . If the first argument is an
114 Programming in Pure Prolog 7.5
nonmember ( X , Xs ) + -
members(Xs , Ys) +-
Each element of the list Xs is a member of the list Ys.
members ( [X IXs ],Ys ) +- member (X ,Ys ) , members (Xs ,Ys ) .
members ( [ ],Ys ) .
Program 7.6 : Testing for a subset
incomplete list , the permutation query will not terminate , because it calls a select
goal that will not terminate . If the order of the goals in the recursive rule for
permutation is swapped , the second argument of a permutation query becomes
the significant one for determining termination . If it is an incomplete list , the
computation will not terminate ; otherwise it will .
selects(Xs,
- Ys)- +-
The list Xs is a subset of the list Ys.
Program 7.6 is also restrictive with respect to termination . If either the first
or the second argument of a members query is an incomplete list the program will
not terminate . The second argument must be a complete list due to the call to
member, while the first argument must also be complete , since that is providing
the recursive control . The query members(Xs ,[1,2,9]) ? asking for subsets of a
given set does not terminate . Since multiple copies of elements are allowed in
Xs , there are an infinite number of solutions , and hence the query should not
terminate .
Both these limitations are avoided by Program 7.7. The revised relation is
selects( X sY s) . Goals in the meaning of Program 7.7 have at most as many copies
of an element in the first list as appear in the second. Related to this property ,
Program 7.7 terminates whenever the second argument is a complete list . A query
such as selects(Xs ,[ a,b, c]) has as solution all the subsets of a given set.
We now consider a different example : translating a list of English words , word
for word , into a list of French words . The relationship is translate ( Words,Mots )
where Words is a list of English words , and Mots the corresponding list of
French words . Program 7. performs the translation . It assumes a dictionary
of pairs of corresponding English and French words , the relation scheme being
dict ( Word,Mot ) . The translation is very naive , ignoring issues of number , gender ,
subject -verb agreement , and so on . Its range is solving a query such as translate
( [the,dog,chases,the,cat]) ,X) ? with solution X = [le,chien ,chasse,le, chat]. This
program can be used in multiple ways . English sentences can be translated to
French , French ones to English , or two sentences can be checked if they are correct
mutual translations .
We conclude the section with a discussion of the use of data structures in Prolog
programs . Data structures are handled somewhat differently in Prolog than
116 Programming in Pure Prolog 7.5
translate( Words,Mots) i --
Mots is a list of French words which is the
translation of the list of English words Words.
Consider appending the list [c, dj to the list [a, b] as illustrated in Figure
4.3. The output Ls = [a, b, c,dj is constructed in stages, as Ls = [aIZs], Zs= [bIZsl ],
and finally Zsl = [c, dj, when the base fact of append is used. Each recursive call
partially instantiates the originally incomplete list . Note that the recursive calls
to append do not have access to the list being computed . This is a top -down
construction of recursive structures , and is typical of programming in Prolog .
The top -down construction of recursive data structures has one limitation .
Pieces of the global data structure cannot be referred to deeper in the computation
. This is illustrated in a program for the relation no_doubles(XXs ,Xs ) which is
true if Xs is a list of all the elements appearing in the list XXs with all duplicates
removed .
7.5 Recursive programming in pure Prolog 117
no_doubles(Xs, Ys) +-
Ys is the list obtained by removing
duplicate elements from the list Xs .
no_doubles([X IXsJ,Ys) +-
member(X ,Xs), no_doubles(Xs,Ys).
no_doubles([X IXs],[X IYs]) +-
non- member(X ,Xs), no_doubles(Xs,Y s).
no_doubles([ J,[ ]).
nonmember(X ,Xs) +- SeeProgram 7.5.
Program 7.9 : Removing duplicates from a list
Consider trying to compose no_doubles top -down . The head of the recursive
clause
will be
no_doubles([X IXs],' . .) +-
where we need to fill in the blank . The blank is filled by calling no_doubles
recursively on Xs with outputY s and integrating Ys with X . If X has not appeared
in the output so far , then it should be added, and the blank will be [XI VB]. If X
has appeared , then it should not be added and the blank is Ys. This cannot be
easily said . There is no way of knowing what the output is so far .
The program for no_doubles is composed by thinking differently about the
problem . Instead of determining whether an element has already appeared in the
output , we can determine whether it will appear . Each element X is checked to
see if it appears again in the tail of the list Xs . If X appears , then the result is
Y s, the output of the recursive call to no _doubles . If X does not appear , then it
is added to the recursive result . This version of no_doubles is given as Program
7.9. It uses Program 7.5 for nonmember .
A problem with Program 7.9 is that the list without duplicates may not have
the elementsin the desired order. For example, no_doubles([a,b,c,b],Xs) ? has the
solution Xs= [a,c,b], where the solution Xs= [a,b,c] may be preferred. This latter
result is possible if the program is rewritten . Each element is deleted from the
remainder of the list as it is found . In terms of Program 7.9 this is done by
replacing the two recursive calls by a rule
no_doubles([X IXs],[X IYs]) +- delete(X ,Xs,Xsl ), no_doubles(Xsl ,Ys).
The new program builds the output top -down . However it is inefficient for large
lists , as will be discussed in Chapter 13. Briefly each call to delete rebuilds the
whole structure of the list .
118 Programming in Pure Prolog 7.5
nd _ reverse ( Xs , Ys ) + -
Ys is the reversal of the list obtained by
removing duplicate elements from the list Xs .
reverse ( [ a ,b ,c ] ,Xs )
reverse ( [ a ,b ,c ] , [ ] ,Xs )
reverse ( [b ,c ] , [a ] ,Xs )
reverse ( [c ] , [b , a ] ,Xs )
reverse ( [ ] , [c ,b , a ] ,Xs ) Xs = [c ,b , a ]
true
reverse ( Xs , Y s ) + - reverse ( Xs , [ ] , Y s ) .
An extra argument is added to reverse / 2 and used to accumulate the values of the
reversed list as the computation proceeds . This procedure for reverse builds the
output list bottom - up rather than top - down . In the trace in Figure 7 .3 solving
the goal reverse ( [ a , b , c ] , Xs ) , the successive values of the middle argument of the
builds the result bottom up . This argument is checked to see whether a particular
element appears , rather than checking the tail of the list as in Program 7 .6 for
in the recursive call , so that the more complex version is in the body of
the clause rather than in its head . This contrasts with top - down construction ,
where the more complex version of the data structure being built is in the head
of the clause . Another argument is used solely for returning the output , namely
the final value of the accumulator . It is instantiated with the satisfaction of the
base fact . The argument is explicitly carried unchanged in the recursive call .
It is used in the next chapter discussing Prolog programs for arithmetic . Accu -
7 . 6 Background
would be superseded by further research . Its control has always been acknowledged
" Algorithm = Logic + Control ." The particular control provided in pure Prolog
was intended as just one solution on the path to declarative programming and
intelligent control . Time has shown otherwise . The control of Prolog has proven
adequate for a large range of applications , and the language has not only endured ,
of control . For example , LOGLISP ( Robinson and Sibert , 1982 ) has breadth -
first traversal of the search tree , and IC - Prolog ( Clark and McCabe , 1978 ) has
The logic programs for performing arithmetic presented in Section 3.1 are
very elegant , but they are not practical . Any reasonable computer provides very
efficient arithmetic operations directly in hardware , and practical logic programming
languages cannot afford to ignore this feature . Computations such as addition
take unit time on most computers independent of the size of the addends (as
long as they are smaller than some large constant ) . The recursive logic program
for plus (Program 3.3) takes time proportional to the first of the numbers being
added . This could be improved by switching to binary or decimal notation , but
still won 't compete with direct execution by dedicated hardware .
Operators are used in order to make programs more readable . People are
very flexible and learn to adjust to strange surroundings - they can become accustomed
to read Lisp and Fortran programs , for example . We believe nonetheless
that syntax is important ; the power of a good notation is well known from mathematics
. An integral part of a good syntax for Prolog is the ability to specify and
use operators .
122 Arithmetic 8.1
Operators have been used in earlier chapters , for example , i = and < . We assume
Prolog provides several operators , and introduce them as they arise . Most
Prologs give the user the ability to define his own binary infix , and unary prefix
the relative precedence , name , and associative behavior of each operator . The
mechanism for specifying this information varies . The form of operator declarations
list of all operators used in this book and their relative precedences .
The basic query to the evaluator has the form Value : = Expression ? , and is
Here are some examples of simple addition , illustrating the use and behavior
9 + 5 ) ? fails because the left - hand argument , 9 + 5 , does not unify with 8 , the
The evaluator allows the standard operators for addition , subtraction , multiplication
restrict ourselves in this book to integer arithmetic . Thus f denotes integer division
An expression can be invalid for one of two reasons , which should be treated
The semantics of any logic program is completely defined , and , in this sense ,
logic programs Ca Iillot have runtime " errors . " For example , the goal X : = 3 + Yhas
the limitations of the machine should be taken into account . A runtime error
occurs when the machine cannot determine the result of the computation due
goals that simply fail . Extensions to Prolog and other logic languages handle such
" errors " by suspending until the values of the concerned variables are known . The
goal which that succeed if ' Ywere instantiated to an arithmetic expression . Here
are provided by the Prolog system for use by the programmer . Another term for
Further system predicates for arithmetic are the comparison operators . Instead
of the logically defined < , , 5 , > , 2 , Prolog directly calls the underlying
arithmetic . We describe the behavior of < ; the others are virtually identical . To
answer the query ( A < B ) ' I , A and B are evaluated as arithmetic expressions .
The two resultant numbers are compared , and the goal succeeds if the result of
arithmetic expression the goal will fail , and an error condition should result if A
Here are some simple examples . The goal ( 1 < 2 ) ? succeeds , as does the
goal ( 3 - 2 < 2 * 3 + 1 ) ? On the other hand , ( 2 < 1 ) ? fails , and ( N < 1 ) ? generates
Tests for equality and inequality of values of arithmetic expressions are implemented
can certainly be done more efficiently . For example , finding the minimum of two
numbers can use the underlying arithmetic comparison . The program syntactically
need not change from Program 3 . 7 . Similarly , the greatest common divisor
of two integers can be computed efficiently using the usual Euclidean algorithm ,
given as Program 8 . 1 . Note that the explicit condition J > 0 is necessary to avoid
multiple solutions when J = O and errors from calling mod with a zero argument .
124 Arithmetic 8.2
Two features of logic programs for arithmetic are missing from their Prolog
counterparts . First , multiple uses of programs are restricted . Suppose we wanted
a predicate plus (X , Y,Z) that performed as before , built using " := " . The obvious
definition is
plus(X,Y ,Z) f - Z := X+ Y.
This works corre Gtly if X and Yare instantiated to integers . However , we cannot
use the same program for subtraction with a goal such as plus ( 9,X ,8) ?, which
raises an error condition . Metalogical tests are needed if the same program is
to be used for both addition and subtraction . We defer this until metalogical
predicates are introduced in Chapter 10.
Programs effectively become specialized for a single use, and it is tricky to
understand what happens when the program is used differently . Program 3.7 for
minimum , for example , can be used reliably only for finding the minimum of two
integers .
The other feature missing from Prolog programs for arithmetic is the recursive
structure of numbers . In logic programs , the structure is used to determine
which rule applies , and to guarantee termination of computations . Prugram 8.2 is
a Prolog program for computing factorials closely corresponding to Program 3.6.
The recursive rule is more clumsy than before . The first argument in the recursive
call of factorial must be calculated explicitly , rather than emerging as a result
of unification . Furthermore , the explicit condition determining the applicability
of the recursive rule , N > 0, must be given . This is to prevent nonterminating
computations with goals such as factorial ( - 1,N) ? or even factorial (3,F) ? Previously
, in the logic program , unification with the recursive structure prevented
nonterminating computations .
Program 8.2 corresponds to the standard recursive definition of the factorial
function . Unlike Program 3.7, the program can be used only to calculate the
factorial of a given number . A factorial query where the first argument is a
variable will cause an error condition .
8.3 Transforming recursion into iteration 125
jactorial (N,F) +-
F is the integer N factorial .
factorial(N,F) +-
N > 0, Nl := N- l , factorial(Nl ,Fl ), F := N*Fl .
factorial(Oil).
Program 8.2: Computing the factorial of a number
(i) The Nfh triangular number is the sum of the numbers up to and including N.
Write a program for the relation triangle (N, 1) where T is the Nfh triangular
number .
In Prolog there are no iterative constructs as such, and a more general concept
, namely recursion , is used to specify both recursive and iterative algorithms .
The main advantage of iteration over recursion is in efficiency , mostly space efficiency
. In the implementation of recursion , a data structure (called a stack -frame )
has to be maintained for every recursive call that has not terminated yet . A recursive
computation involving n recursive procedure calls would require , therefore ,
space linear in n. On the other hand , an iterative program typically uses only a
constant amount of memory , independent of the number of iterations .
126 Arithmetic 8.3
factorial (N);
1 .. -- O., T .-
. -
1 .,
while 1 < N do
1 := 1+ 1 ; T := T * 1 end ;
return T
Prolog does not have storage variables , which can hold intermediate results of
the computation and can be modified as the computation progress es. Therefore to
implement iterative algorithms , which require the storage of intermediate results ,
Prolog procedures are augmented with additional arguments , called accumulators .
Typically , one of the intermediate values constitutes the result of the computation
upon termination of the iteration . This value is unified with the result variable
using the unit clause of the procedure .
This technique is demonstrated by Program 8.3, which is a Prolog definition
of factorial that mirrors the behavior of the while loop in Figure 8.1. It uses
factorial (I ,N, T,F) which is true if F is the value of N factorial , and I and Tare
the values of the corresponding loop variables before the (1+ 1)th iteration of the
loop .
The basic iterative loop is performed by the iterative procedure factorial/ 4.
8.3 Transforming recursion into iteration 127
factorial (N ,F)
factorial ( N ,F ) +- factorial ( O ,N , I ,F ) .
factorial ( I ,N ,T ,F ) +-
I < N , 11 := 1+ 1 , Tl := T * Il , factorial ( Il , N , Tl ,F ) .
factorial ( N ,N ,F ,F ) .
factorial ( N ,F) +-
factorial ( N ,F ) +- factorial ( N , 1 ,F ) .
factorial ( N ,T ,F ) +-
value is passed between iterations , not an address . Since logical variables are
" write - once , " the updated value , a new logical variable , is passed each time . Stylis -
tically , we will use variable names with the suffix 1 , for example , T1 and 11 , to
The computation terminates when the counter I equals N . The rule for factorial
/ 4 in Program 8 . 3 no longer applies , and the fact succeeds . With this successful
reduction , the value of the factorial is " returned . " This happens as a result of the
unification with the accumulator in the base clause . Note that the logical variable
throughout the whole computation to be set on the final call of factorial . This
between(I ,J,K) +-
K is an integer between the integers I and J, inclusive .
between(I ,J ,1) +- I ~ J.
between(I ,J,K ) +- I < J, 11 := 1+ 1, between(ll ,J,K ).
Program 8 .5: Generating a range of integers
Program 8.3 shows the exact reflection of the while loop for factorial given in
Figure 8.1. Another iterative version of factorial can be written by counting down
from N to 0 , rather than up from 0 to N . The basic program structure remains
the same, and is given as Program 8.4. There is an initialization call that sets the
value of the accumulator , and recursive and base clauses implementing the while
loop .
Program 8.4 is marginally more efficient than Program 8.3. In general , the
fewer arguments a procedure has , the more readable it becomes, and the faster it
runs .
sumlist ( ls , Sum ) + -
sumlist ( ( ] ,0 ) .
sumlist ( Is , Sum ) + -
recursive inner _product that Program 8.6b for sumlist bears to Program 8.6a.
Both Programs 8.7a and 8.7b are correct for goals inner _product (Xs , Ys,Zs)
where Xs and Y s are lists of integers of the same length . There is a built -in check
that the vectors are of the same length . The programs fail if Xs and Y 8 are of
different lengths .
The similarity of the relationship between Programs 8.6a and 8.6b , and between
Programs 8.7a and 8.7b suggests that one may be automatically transformed
to the other . The transformation of recursive programs to equivalent
iterative programs is an interesting research question . Certainly it can be done
for the simple examples shown here .
The sophistication of a Prolog program depends on the underlying logical
relationship it axiomatizes . Here is a very elegant example of a simple Prolog
program solving a complicated problem .
Consider the following problem : Given a closed planar polygon chain
{ Pl ,P2,. . .,Pn } , compute the area of the enclosed polygon , and the orientation of
the chain . The area is computed by the line integral
1/ 2 Jxdy - ydx
The solution is given in Program 8.8, which defines the relation area( Chain ,
130 Arithmetic 8.3
inner _ product ( [ ] , [ ] ,0 ) .
inner - product ( [ ] , [ ] , IP , IP ) .
Area is the area of the polygon enclosed by the list of points Points ,
of integers .
area ( [ Tuple ] ,O ) .
area ( [ ( Xl , Yl ) , ( X2 , Y2 ) IX Ys ] , Area ) + -
Area := ( Xl * Y2 - Yl * X2 ) / 2 + Areal .
magnitude of Area is the area of the polygon bounded by the chain . The sign of
The polygon gains opposite orientation by reversing the order of the tupies . The
maximum ( Xs , N ) ~
maximum ( [X I Xs ] , M ) + - maximum ( Xs ,X , M ) .
maximum ( [X I Xs ] ,Y , M ) + - X ~ Y , maximum ( Xs ,Y , M ) .
maximum ( [ ] , M , M ) .
length ( Xs , N ) + -
Xs is a list of length N .
length ( [ ] , 0 ) .
length ( Xs , N ) + -
length ( [X I Xs ] , N ) + - length ( Xs , Nl ) , N : = Nl + l .
length ( [ ] , 0 ) .
range ( M , NiNs ) + -
range ( N , N , [N ] ) .
The relation scheme is maximum ( Xs , Max ) , and the program is given as Program
Max is the maximum of X and the elements in the list Xs . The second argument
of maximum / 3 is initialized to be the first element of the list . Note that the
The standard recursive program for finding the maximum of a list of integers
maximum of the tail of the list , and compares it to the head of the list , to find the
132 Arithmetic 8.3
maximum element . In contrast , Program 8.9 keeps track of the running maximum
as the list is traversed .
Program 3.17 for finding the length of a list is interesting , affording several
ways of translating a logic program into Prolog , each of which has its separate features
. One possibility is Program 8.10, which is iterative . Queries length (Xs ,N) ?
are handled correctly if N is a natural number , either testing if the length of
a list is N , generating a list of N uninstantiated elements , or failing . The program
is unsuitable , however , for finding the length of a list with a call such as
length ( [1,2, 9],N) ? This query generates an error .
Finding the length of a list can be done using Program 8.11. This program
cannot be used , however , to generate a list of N elements .. In contrast to Program
8.10, the computation does not terminate if the first argument is an incomplete
list . Different programs for length are needed for the different uses.
Similar considerations about the intended use of a program occur when trying
to define the relationship range(M ,NiNs ) , where Ns is the list of integers
between M and N inclusive . Program 8.12 has a specific use: generating a list
of numbers with the desired range . The program is totally correct over all goals
range(M ,NiNs ) where M and N are instantiated . The program cannot be used,
however , to find the upper and lower limits of a range of integers , due to the
test M < N . Removing this test would allow the program to answer a query
range(M ,N ,[1,.2,9]) if, but then it would not terminate for the intended use, solving
queries such as range(1,3,Ns) ?
(viii ) Rewrite Program 8.12 so that the range of integers is built bottom -up rather
than top -down .
8.4 Background
9 .1 Type predicates
Type predicates are unary relations concerning the type of a term . The predicates
test whether a given term is a constant or a structu ,re . Further distinctions
are made between particular constants , such as integers and atoms . Four type
predicates are assumed in this book : integer / l , atom / l , constant / l and compound
an infinite table of facts . A table of integers : integer (0) , integer (1) , integer (
- 1) , " " ; a table of the atoms in the program : atom ( foo ) , atom ( bar ) , . . . ; and
a table of the function symbols in the program with variable arguments : compound
constant (X ) + - integer (X ) .
constant (X ) +- atom (X ) .
be used by goals that have only a finite number of solutions . If such a predicate
9.1 Type predicates 135
integer ( X) +- X is an integer .
atom (.x) +- X is an atom .
constant ( X) +- X is a constant (integer or atom ) .
compound ( X) +- X is a compound term .
is called with a goal that has an infinity of solutions , an error condition occurs .
Consider the goal integer(X) 'I. If Xis an integer the call succeeds;if it is an atom
or structure the call fails . If X is a variable , an error condition is reported . This
is analogous to evaluating arithmetic expressions which contain variables . Note
that most Prologs do not follow this convention, and in them integer(X) , where
X is a variable , simply fails .
It is tempting to use a query such as atom(X) ? to give all the atoms known in
the system on backtracking . This way of using atom is usually not implemented .
The only terms not covered by the predicates in Figure 9.1 are variables .
Prolog doesprovide system predicates relating to variables. The use of such predicates
, however, is conceptually very different from the use of structure inspection
predicates described in this chapter. Metalogical predicates (their technical
name) are the subject of the next chapter.
We give an example of the use of a type predicate as part of a program for
flattening a list of lists . The relation flatten (Xs, Ys) is true if Ys is the list of
elements occurring in the list of lists Xs . The elements of Xs can themselves be
lists or elements , so elements can be arbitrarily deeply nested . An example of a
goal in the meaning of flatten is flatten ([[a],[b,[c,dJ],e],[a,b,c,die]).
The simplest program for flattening uses double recursion . To flatten an
arbitrary list [.X1Xs
] where X can itself be a list , flatten the head of the list X ,
flatten the tail of the list Xs and concatenate the results :
What are the base cases? The empty list is flattened to itself . A type predicate
is necessary for the remaining case. The result of flattening a constant is a list
containing the constant :
The condition constant(X) is necessaryto prevent the rule being used when X is
a list . The complete program for flatten is given as Program 9.la .
136 StructureInspection 9.1
ftatten ( Xs , Ys ) + -
Y s is a list of the elements of Xs .
constant (X ) , X # [ ] .
Flatten ( ( ] ,[ ]) .
jlatten ( Xs , Ys ) + -
Ys is a list of the elements of Xs .
flatten ( Xs , Y s ) + - fiatt en ( Xs , [ ] , Y s ) .
flatten ( [ ],[ ] ,[ ]) .
list ( [X IXs ] ) .
Program 9 . la , although very clear declaratively , is not the most efficient way
of flattening a list . In the worst case , which is a left - linear tree , the program
would require a number of reductions whose order is quadratic in the number of
elements in the flattened list .
A program for flatten which constructs the flattened list top - down is a little
more involved than the doubly recursive version . It uses an auxiliary predicate
The call of flattenS by flatten / 2 initializes the stack to the empty list . We
discuss the cases covered by flatten / So The general case is flattening a list [- XlXs ]
where X is itself a list . In this case Xs is pushed onto the stack , and X is recursively
When the head of the list is a constant other then the empty list , it is added to
When the end of the list is reached , there are two possibilities , depending on the
state of the stack . If the stack is nonempty , the top element is popped , and the
flattening continues :
flatten ( [ ] , [ ] , [ ] ) .
stack is managed by unification . Items are pushed onto the stack by recursive
calls to a " consed " list . Items are popped by unifying with the head of the list
and recursive calls to the tail . Another application of stacks appears in Programs
The reader can verify that the revised program requires a number of reductions
further aspect is provided by predicates that give access to the functor name ,
arity and arguments of a compound term . One system predicate for delving
into compound terms is functor ( Term , F , Arity ) . This predicate is true if Term
is a term whose principal functor has name F and arity Arity . For example ,
Calls to functor can fail for various reasons. A goal such as functor
(father(X , Y) ,son,2) does not unify with an appropriate fact in the table . Also,
there are type restrictions on the arguments of functor goals . For example , the
third argument of functor , the arity of the term , cannot be an atom or acom -
pound term . If these restrictions are violated , the goal fails . A distinction can
be made between calls that fail , and calls that should give an error because there
are infinitely many solutions, such as functor (X , Y,2) ?
The predicate functor is commonly used in two ways . The first use finds
the functor name and arity of a given term . For example, the goal functor
(Jather(haran,lot),X , II) ? has the solution { X =fatherY = 2} . The second use
builds a term with a particular functor name and arity . A sample query is functor
( T,father, 2) ? with solution T=father(X , II) .
The companion system predicate to functor is arg(N, Term,Arg) which ac-
cesses the arguments of a term rather than the functor name . The goal
arg(N, Term,Arg) is true if Arg is the Nth argument of Term. For example,
arg(l ,father (haran,lot),haran) is true .
Like junctor / !], arg/ !] is commonly used in two ways. One use finds a
particular argument of a compound term . A query exemplifying this use is
arg(2,Lather(haran,lot),X) ? with solution X = lot. The other use instantiates a
variable argument of a term . For example, the goal arg(l ,Lather(X,lot),haran) ?
succeeds, instantiating X to haran .
The predicate arg is also defined as if there is an infinite table of facts . A
fragment of the table is
Calls to arg fail if the goal does not unify with the appropriate fact in the table , for
example, arg(1,/ather(haran, lot), abraham). They also fail if the type restrictions
are violated , for example , if the first argument is an atom . An error is reported
with a goal such as arg(l ,X , Y) .
Let us consider an example of using functor and arg to inspect terms. Program
9.2 axiomatizes a relation subterm( Tl , T2), which is true if Tl is a subterm
of T2 . For reasons that will become apparent later , we restrict Tl and T2 to be
ground .
9.2 Accessing compound terms 139
The first clause of Program 9.2 defining subterm / 2 states that any term is a
subterm of itself . The second clause states that Sub is a subterm of a compound
term Term if it is a subterm of one of the arguments . The number of arguments ,
i .e., the arity of the principal functor of the term , is found and used as a loop
counter by the auxiliary subterm / 3, which iteratively tests all the arguments .
The first clause of subterm / 9 decrements the counter and recursively calls
subterm . The second clause covers the case when Sub is a subterm of the Nth
argument of the term .
The subterm procedure can be used in two ways : to test whether the first
argument is indeed a subterm of the second; and to generate subterms of a given
term . Note that the clause order determines the order in which subterms are
generated . The order in Program 9.2 gives subterms of the first argument before
subterms of the second argument , and so on. Swapping the order of the clauses
changes the order of solutions .
Consider the query subterm ( a,J( X , Y) ) '?, where the second argument is not
ground . Eventually the subgoal subterm ( a, X) is reached . This succeeds by the
first subterm rule , instantiating X to a. The subgoal also matches the second
subterm rule , invoking the goal compound (X) which generates an error . This is
undesirable behavior .
Program 9.2 is typical code for programs that perform structure inspection .
We look at another example , substituting for a subterm in a term .
140 Structure Inspection 9.2
-
arg(2,owns(jane,cat) ,Arg ) Arg= cat}
substitute (cat,dog,cat,Arg1) Argl = dog}
arg(2,owns(Xl ,X2) ,dog) X2= dog}
ni := 2- 1 Nl = l }
substitute ( 1,cat,dog,owns(jane,cat) ,owns(xi ,dog))
1 > 0
Output : X = owns(jane,dog)
Figure 9 .2 : Tracing the substitute predicate
is interesting . Name and Arity have been instantiated to owns and .2, respectively ,
in the previous call of functor , so this call builds a term that serves as the answer
template to be filled in as the computation progress es. This explicit term
building has been achieved by implicit unification in previous Prolog programs .
The call to substitute/ 5 successivelyinstantiates the arguments of Terml . In our
example, the second argument of owns(Xl ,X .2) is instantiated to dog, and then
Xl is instantiated to jane .
The two calls to arg serve different tasks in substitute/ 5. The first call selects
an argument , while the second call of arg instantiates an argument .
Substitution in a term is typically done by destructive assignment in con-
ventionallanguages . Destructive assignment is not possible directly in Prolog .
Program 9.3 typifies how Prolog handles changing data structures . The new term
142 Structure Inspection 9.2
subterm (Sub,
. Term ,) +-
Sub is a subterm of the ground term Term.
subterm ( Term , Term ) .
is recursively built as the old term is being traversed , by logically relating the
corresponding subterms of the terms .
Note that the order of the second arg goal and the recursive call to substitute
/ .5 can be swapped. The modified clausefor substitute/ 5 is logically equivalent
to the previous one, and givesthe sameresult in the context of Program 9.3. Pro-
cedurally, however
, they are radically different.
Another system predicate for structure inspection is a binary operator = ..,
called , for historical reasons, univ . The goal Term = .. List succeeds if List is a list
whose head is the functor name of the term Term , and whose tail is the list of ar-
guments of Term . For example, the goal (father( haran, lot) =.. [father
,haran
,lot])?
succeeds .
Like functor and arg, univ has two uses. Either it builds a term given a list ,
for example, (X = .. [father,haran,lot]) ? with solution X =father(haran,lot), or it
builds a list given a term , for example, (father(haran,lot) = .. Xs) ? with solution
Xs= [father ,haran,lot].
In general, programs written using functor and arg can also be written with
univ. Program 9.4 is an alternative definition of 8ubterm, equivalent to Program
9.2. As in Program 9.2, an auxiliary predicate investigates the arguments; here
it is subterm _list . Univis used to access the list of arguments, Args , of which
subterms are recursively found by subterm _list .
Programs using univ to inspect structures are usually simpler . However ,
programs written with functor and ara are in general more efficient than those
using univ , since they avoid building intermediate structures .
A np~.t 11~P ()f "1.'11
.i ." i~ f ()rml11~.tin !T
-
thp rh ~in r111pfor ~vmh
-
()lir r1iffprpT1ti~tioT1
The chain rule states that dfdx { f (g (x ) } = dfdg (x ) { f (g (x ) } x dfdx { g (x ) } . In
9.2 Accessing compound terms 143
Term = . . List +-
List is a list containing the functor of Term followed
by the arguments of Term .
Term = .. [FIArgs] +-
functor (Term,F ,N), args(O,N ,Term,Args).
args(I ,N,Term,(Arg IArgs]) +-
I < N , 11 := 1+ 1, arg(11,Term,Arg ), args(11,N ,Term,Args).
args(N,N ,Term,( ]) .
Program 9 .5a : Constructing the list corresponding to a term
Term = . . List +-
The functor of Term is the first element of the list List ,
and its arguments are the rest of List ' s elements .
Term = .. [FIArgs] ~
length(Args,N), functor (Term,F ,N), args(Args,Term,l ).
args([Arg IArgs],Term,N) +-
arg(N,Term,Arg ), ni := N+ 1, args(Args,Term,ni ).
args([ ],Term,N).
length(Xs,N) +- SeeProgram 8.11
Program 9 .Sb : Constructing the term corresponding to a list
Section 3 .5 we noted that this rule could not be expressed as a single clause of
rule is
derivative ( F _ G . x , X , DF * DG ) t -
F _G . x = .. [F ,G . x ] ,
derivative ( F _ G . x , G . x , DF ) ,
derivative ( G . x ,X , DG ) .
The function F- G_X is split up by univ into its function F and argument G_X ,
checkingthat is a function of one argument at the.sametime . The derivative of F
with respect to its argument is recursively calculated, as is the derivative of G_X .
These are combined to give the solution.
Univ can be defined in terms of functor and argoTwo different definitions are
necessary, however, to cover both building lists from terms, and building terms
from lists . One definition does not suffice due to errors causedby uninstantiated
144 Structure Inspection 9.2
variables . Other system predicates are similarly precluded from flexible use.
Program 9.5a behaves correctly for building a list from a term . The functor
F is found by the call to functor , and the arguments are recursively found by the
predicate args. The first argument of args is a counter that counts up , so that
the arguments will appear in order in the final list . If Program 9.5a is called
with Term uninstantiated , an error will be generated due to an incorrect call of
functor .
Program 9.5b behaves correctly for constructing a term from a list . The
length of the list is used to determine the number of arguments . The term template
is built by the call to functor , and a different variant of args is used to fill
in the arguments . Program 9.5b results in an error if used to build a list , due to
the goal length ( A rgs,N) being called with uninstantiated arguments .
(ii ) Define a predicate position (Subterm , Term ,Position ) where Position is a list
of argument positions identifying Subterm within Term . For example , the
position of X in 2.sin (X) is [2,lJ , since sin (X) is the second argument of the
binary operator " ." , and X is the first argument of sin (X) .
(Hint : Add an extra argument for Program 9.2 for subterm , and build the
position list top -down .)
(iii ) Rewrite Program 9.5a so that it counts down .
(Hint : Use an accumulator .)
(iv ) Define functor and arg in terms of univ . How can the programs be used?
(v ) Rewrite Program 9.3 for substitute so that it uses univ .
9.3 Background
The standard Prolog approach does not distinguish between object - and
meta -level type predicates . We have taken a different approach , by defining the
type test predicates to work only on instantiated terms , and by treating the metalogical
test predicates (e.g., var/ l to be discussed in Section 10.1) separately . The
predicates for accessing and constructing terms , functor , arg, and = .., originate
from the Edinburgh family . The origin of the = .. is in the old Prolog - 10 syntax
9.3 Background 145
for lists, which usedthe operator ",.." instead of the current "I" in lists, e.g.,
[a,b,c,..Xs] instead of [a,b,cIXs]. The ".." on the right-hand side suggestedor
remindedthat the right-hand sideof the equalityis a list.
Severalof the examplesin this sectionwereadaptedfrom O'Keefe(1983).
Exercises(i) and (ii ) will be usedin the equationsolverin Chapter22.
Chapter 10
MetaLogical Predicates
The basic metalogical type predicate is var ( Term ) which tests whether a
given term is at present an uninstantiated variable . Its behavior is similar to the
10.1 Metalogical type predicates 147
plus (X , Y, Z) +-
The sum of the numbers X and Y is Z .
type predicates discussedin Section 9.1. The query var( Term) ? succeedsif Term
is a variable and fails if Term is not a variable. For example, var(X) ? succeeds,
whereaB both var( a) ? and var([XjXs ]) fail .
The predicate vaT is an extension to pure Prolog programs . A table cannot
be used to give all the variable names. A fact var(X) means that all instances
of X are variables , rather than meaning that the letter " X" denotes a variable .
Being able to refer to a variable name is outside the scope of first -order logic in
general or pure Prolog in particular .
The predicate nonvar( Term) has the opposite behavior to vaT. The query
nonvar( Term) succeedsif Term is not a variable and fails if Term is a variable.
The metalogical type predicates can be used to restore some flexibility to
programs using system predicates , and can also be used to control goal order . We
demonstrate this by revising some programs from earlier chapters .
Consider the relation plus(X , Y,Z) . Program 10.1 is a version of plus which can
be used for subtraction as well as addition . The idea is to check which arguments
~1
- -'P- ---
in .qt,;=I,ntiated before calling the arithmetic evaluator . For example , the second
rule says that if the first and third arguments , X and Z , are not variables , the
second argumentY , can be determined as their difference . Note that if the
arguments are not integers , the evaluation will fail , the desired behavior .
The behavior of Program 10.1 is closer to Program 3.3, the logic program
for plus . Further , it does not generate any errors . Nonetheless , it does not have
the full flexibility of the recursive logic program : it cannot be used to partition a
number into two smaller numbers , for example . To partition a number involves
generating numbers , for which a different program is needed . It is posed as an
exercise at the end of the section .
length(Xs,N) +-
The list Xs has length N.
length(Xs,N) +- nonvax(Xs), lengthl (Xs,N).
length(Xs,N) +- vax(Xs), nonvax(N), length2(Xs,N).
lengthl (Xs,N) +- SeeProgram 8.11
length2(Xs,K ) +- SeeProgram 8.10
Program 10.2: A multipurpose length program
grandparent (X ,Z) +-
X is the grandparent of Z .
For example , in Edinburgh Prolog , the goal integer (X) fails if X is a variable ,
rather than giving an error . This enables the rules from Program 10 . 1 to be
written using the system predicate integer rather than nonvar , for example ,
first - order operation , from metalogical tests , which are a much stronger tool .
Another relation that can have multiple uses restored is length ( Xs ,N) determining
the length N of a list Xs . Separate Prolog programs ( 8 . 10 and 8 . 11 ) are
needed to find the length of a given list and to generate an arbitrary list of a
given length , despite the fact that one logic program ( 3 . 17 ) performs both functions
. Program 10 . 2 uses meta - lo .gical tests to define a single length relation . The
program has an added virtue over Programs 8 . 10 and 8 . 11 . It avoids the nonterminating
behavior present in both , when both arguments are uninstantiated .
Metalogical tests can also be used to make the best choice of the goal order
The optimum goal order changes depending on whether you are searching for the
ground( Term) +-
Term is a ground term .
ground(Term) +-
nonvar(Term) , constant(Term).
ground(Term) +-
nonvar(Term) ,
compound(Term) ,
functor (Term,F ,N),
ground (N ,Term).
ground(N,Term) +-
N > 0,
arg(N ,Term,Arg ) ,
ground(Arg ),
Nl := N - l ,
ground(N l ,Term).
ground (0,Term) .
Program 10 .4 : Testing if a term is ground
umfy (X ,X ).
type predicates . Although more cumbersome and less eflicient , this definition
unification with occurs check as described in the next section . Another example
unify ( Terml , Term2 ) is true if Terml unifies with Term2 . The clauses of unify
outline the possible cases . The first clause of the program says that two variables
unify . The next clause is an encapsulation of the rule for unification that if X is
terms , as given in the predicate term _ unify ( X , Y ) . This predicate checks that the
two terms X and Y have the same principal functor and arity , and then checks
that all the arguments unify , using unify _ args , in a way similar to the structure
Write a version of range Program 8.12 that can be used to in multiple ways .
Write a version of Program 10.1 for plus that partitions a number as well as
performing addition and subtraction .
(Hint: Use between to generate numbers .)
There is also a system predicate that has the opposite behavior to = = . The
goal X \ = = Y ? succeeds unless X and Yare identical terms .
The predicate \ = = can be used to define a predicate not _occurs_in (Sub, Term)
which is true if Sub does not occur in Term , the relationship that is needed in
the unification algorithm with the occurs check. not _occurs_in (Sub, Term ) is a
metalogical structure inspection predicate . It is used in Program 10.6, a variant
of Program 10.5, to implement unification with the occurs check.
Note that the definition of not _occurs_in is not restricted to ground terms .
Lifting the restriction on Program 9.2 for subterm is not as easy. Consider the
query subterm (X , Y) ? This would succeed using Program 9.2, instantiating X to
Y.
We define a metalogical predicate occurs_in (Sub, Term ) that has the desired
behavior .
not _occurs - in ( X , Y ) + -
var (Y ) , X \ = = Y .
not _occurs Jn ( X , Y ) + -
nonvar ( Y ) , constantY ).
not _occurs Jn ( X , Y ) + -
nonvar ( Y ) , compoundY ) , functorY , F ,N ) , not _occurs - in ( N ,x , V ) .
not _occurs - in ( Nl ,X , Y ) .
not _occursin ( O ,X , Y ) .
tested to see if they are identical to the variable . The code is given in Program
10.7a.
covers this behavior . However , the goal would fail because a variable is not a
constant .
Adding the above two clauses to Program 9.3 for substitute , and adding other
metalogical tests allows it to handle nonground terms . However , the resultant
program is inelegant . It is a mixture of procedural and declarative styles , and demands
of the reader an understanding of Prolog 's control flow . To make a medical
analogy , the symptoms have been treated (undesirable instantiation of variables ) ,
but not the disease (inability to refer to variables as objects ) . Additional metalogical
primitives are necessary to cure the problem .
The difficulty of mixing object and metalevel manipulation of terms stems
from a theoretical problem . Strictly speaking metalevel programs should view
object -level variables as constants , and be able to refer to them by name .
Prolog can be extended with two system predicates , freeze( Term ,Frozen) and
melt (Frozen, Thawed) , to partially solve these problems . Freezing a term Term
makes a copy of the term , Frozen , where all the uninstantiated variables in the
term become unique constants . A frozen term looks like , and can be manipulated
as, a ground term .
154 MetaLogical Predicates 10.3
occursJn (Sub,Term) +-
Sub is a subterm of the (possibly non-ground term ) Term.
a: Using = =
occurs-in (X ,Term) +-
subterm(Sub,Term), X = = Sub.
b : Using freeze
occursJn (X ,Term) +-
freeze(X ,Xf ), freeze(Term,Termf), subterm(Xf ,Termf ).
subterm(X ,Term) +- SeeProgram 9.2
Program 10 .7 : Occurs in
Frozen variables are regarded as ground atoms during unification . Two frozen
variables unify if and only if they are identical . Similarly , if a frozen term and an
uninstantiated variable are unified , they become an identical frozen term . The
behavior of frozen variables in system predicates is the behavior of the constants .
For example , arithmetic evaluation involving a frozen variable will fail .
The predicate freeze is metalogical in a similar sense to var . It enables the
state of a term during the computation to be manipulated directly .
The predicate freeze allows an alternative definition of occurs- in from the
previous section . The idea is to freeze the term so that variables become ground
objects . This makes Program 9.2 for subterm , which works correctly for ground
terms , applicable . The definition is given as Program 10.7b.
Freezing gives the ability to tell whether two terms are identical . Two frozen
terms , X and Y, unify if and only if their unfrozen versions are identical , that is ,
X = = Y. This property is essential to the correct behavior of Program 10.7b .
The difference between a frozen term and a ground term is that the frozen
term can be "melted back " into a nonground term . The companion predicate to
freeze is melt ( Frozen, Thawed) . The goal melt (X , Y) produces a copy Y of the term
X where frozen variables become "regular " Prolog variables . Any instantiations
to the variables in X during the time when X has been frozen are taken into
account when melting Y.
The combination of freeze and melt allows us to write a variant of substitute ,
non _ground _substitute , where variables are not accidentally instantiated . The procedural
view of non _ground _substitute is as follows . The term is frozen before
substitution ; the substitution is performed on the frozen term using the version
10.4 The metavariable facility 155
of substitute which works correctly on gr .ound terms ; and then the new term is
melted :
The frozen term can also be used as a template for making copies . The
system predicate melt _ new ( Frozen , Term ) makes a copy Term of the term Frozen
One use of melt _ new is to copy a term . The predicate copy ( Term , Copy )
copy ( Term , Copy ) +- freeze ( Term , Frozen ) , melt Jlew ( Frozen , Copy ) .
The primitives freeze , melt and melt _ new are useful in expressing and explaining
the behavior of the extra - logical predicates to be introduced in Chapter
12 .
treated as data , and data must be transformed into programs . In this section we
mention a facility which allows a term to be converted into a goal . The predicate
the time it is called , the variable must be instantiated to a term . It will then get
x ,. y ~
X or Y .
X ; y + - X .
XjY + - Y .
logical disjunction , denoted by the binary infix operator ": ' . The goal ( Xi Y) is
true if X or Y is true . The definition is given as Program 10 . 8 .
10 .5 Background
Shapiro ( 1983 ).
The predicates free , melt and melt _new are introduced in N aka Bhima and
The name freeze has been suggested for other additions to pure Prolog . Most
notable is Colmerauer ' s geler ( Colmerauer , 19S2a ) , which allows the suspension
of a goal and gives the programmer more control over goal order .
Chapter 11
Cuts and Negation
Prolog provides a single system predicate , called cut , for affecting the procedural
behavior of programs . Its main function is to reduce the search space
of Prolog computations by dynamically pruning the search tree . The cut can
be used to prevent Prolog from following fruitless computation paths that the
programmer knows could not produce solutions .
The cut can also be used, inadvertently or purpose fully , to prune computation
paths that do contain solutions . By doing so, a weak form of negation can be
effected . '.
The use of cut is controversial . Many of its uses can only be interpreted
procedurally , in a contrast to the declarative style of programming we encourage
. Used sparingly , however , it can improve the efficiency of programs without
compromising their clarity .
Consider ' the program merge(Xs , Ys,Zs) (Program 11.1) which merges two
sorted lists of numbers Xs and Y s into the combined sorted list Zs.
merge(Xs, Ys,Zs) +-
Zs is an ordered list of integers obtained from merging
the ordered lists of integers Xs and Ys.
merge([X IXs],[YIYs],[X IZs]) +- X < Y , merge(Xs,[YIYs],Zs).
merge([X IXs],[YIYs],[X ,YIZs]) +- X = Y , merge(Xs,Ys,Zs).
merge([X IXs],[YIYs],[YIZs]) +- x > Y , merge([X IXs],Ys,Zs).
merge(Xs,[ ] ,Xs).
merge([ ],Ys,Ys).
Program 11.1: Merging ordered lists
Operationally , the cut is handled as follows . The goal succeeds and commits
Prolog to all the choices made since the parent goal was unified with the head of
the clause the cut occurs in .
Although this definition is complete and precise , its ramifications and implications
are not always intuitively clear or apparent .
Misunderstandings concerning the effects of a cut are a major source for bugs
for experienced and inexperienced Prolog programmers alike . The misunderstandings
fall into two categories : assuming that the cut prunes computation paths it
does not , and assuming that it does not prune solutions where it actually does.
The following implications may help clarify the cryptic definition above:
. First , a cut prunes all clauses below it . A goal p unified with a clause
containing a cut that succeeded would not be able to produce solutions
using clauses that occur below that clause.
. Second, a cut prunes all alternative solutions to the conjunction of goals
which appear to its left in the clause, that is , a conjunctive goal followed
by a cut will produce at most one solution .
. On the other hand , the cut does not affect the goals to its right in the
clause. They can produce more than one solution , in the event of backtracking
. However , once this conjunction fails , the search proceeds from
the last alternative prior to the choice of the clause containing the cut . Let
us consider a fragment of the search tree of the query merge([1,3,5],[2,3],
Xs) ? with respect to Program 11.2, a complete version of mergewith cuts
added . The fragment is given as Figure 11.1. The query is first reduced
to the conjunctive query 1< 2,!,merge([3,5],[2,3],Xs1) ?, the goal 1< 2 is
11.1 Green cuts: expressingdeterminism 159
1< 2,!,merge(f3,51,r2,31,Xs1)
~(a) ~
1= 2,!,merge( [3,5], [3] ,Xs1)
(b)
1> 2,!,merge([1,3,5] ,[3] ,Xs1)
~
(*) !,merge([3,5],[2,3],Xsl )
~
merge([3,5],[2,3] ,Xsl )
success fully solved, reaching the node marked (*) in the searchtree. The
effect of executing the cut is to prune the branches marked (a) and (b).
We return to discussing Program 11.2. The placement of the cuts in the
three recursive clauses of merge is after the test . ! The two base cases of merge
are also deterministic . The correct clause is chosen by unification , and thus a
cut is placed as the first goal (and in fact the only goal) in the body of the rule .
Note that the cuts eliminates the redundant solution to the goal merge([ ],[ ],Xs) .
Previously, this was accomplishedmore awkwardly, by specifying that Xs (or Ys)
had at least one element .
We restate the effect of a cut in a general clause C= A +- B1,. . .,Bk , !,Bk + 2' . ' "
Bn in a procedure defining A . If the current goal G unifies with the head of C,
and Bl ,. . .,Bk further succeed, the cut has the following effect . The program is
committed to the choice of C for reducing G; any alternative clauses for A that
might unify with G are ignored . Further should Bi fail for i > k, backtracking goes
back only as far as the I. Other choices remaining in the computation of Bi , i ~ k,
are pruned from the search tree . H backtracking actually reaches the cut , then
the cut fails , and the search proceeds from the last choice made befor ~ G chose
C.
The cuts used in the merge program express that merge is deterministic .
That is , for every applicable goal , only one of the clauses can be used successfully
for proving it . The cut commits the computation to such a clause, once the
computation has progressed enough to determine that this is the only clause to
1 The cut after the third merge clause is unnecessary in any practical sense . Procedurally , it
will not cause any reduction of search . But it makes the program more symmetric , and like
the old joke says about chicken soup , it doesn ' t hurt .
160 Cuts and Negation 11.1
merge(Xs, Ys,Zs) +-
Zs is an ordered list of integers obtained from merging
the ordered lists of integers Xs and Ys.
merge([X IXs],[YIYs],[X IZs]) t -
X<Y, !, merge (Xs,[YIYs],Zs).
merge([XIXs],[YIYs],[X,YIZs]) t -
X=Y, f, merge (Xs,Ys,Zs).
merge([X IXs],[YIYs],[YIZs]) t -
x >Y, I, merge ([X IXs],Ys,Zs).
merge (Xs ,[ ] ,Xs ) +- !.
merge ( [ ],Ys ,Ys ) +- !.
Program 11 .2 : Merging with cuts
minimum (X
. ,- Y,Min ). +-
Min is the minimum of the numbers X and Y.
minimum
(X,Y,X) t- X~Y, t.
minimum
(X,Y,Y) t- X>Y, !.
Program 11.3: Minimum with cuts
be used.
The information conveyed by the cut prunes the search tree , and hence shortens
the path traversed by Prolog , which reduces the computation time . In practice
, using cuts in a program is even more important for saving space. Intuitively ,
knowing that a computation is deterministic means that less information needs
to be kept for use in the event of backtracking . This can be exploited by Prolog
implementations with tail recursion optimization , discussed below .
Let us consider some other examples . Cuts can be added to the program for
computing the minimum of two numbers (Program 3.7) in precisely the same way
as to merge. Once an arithmetic test succeeds there is no possibility for the other
test succeeding . Program 11.3 is the appropriately modified version of minimum .
A more substantial example where cuts can be added to indicate that a
program is deterministic is provided by Program 3.28. The program defines the
relation polynomial ( Term ,X) for recognizing if Term is a polynomial in X . A
typical rule is
polynomial( Term,X) +-
Term is a polynomial in X .
polynomial (X ,X ) ~ !.
polynomial (Term,X ) ~
constant(Term), !.
polynomial (Term 1+ Term2,X ) ~
I, polynomial (Terml ,X ), polynomial (Term2,X ).
polynomial (Terml - Term2,X ) ~
I, polynomial (Terml ,X ), polynomial (Term2,X ).
polynomial (Terml *Term2,X ) ~
!, polynomial (Terml ,X ), polynomial (Term2,X ).
polynomial (Termlj Term2,X ) ~
I, polynomial (Terml ,X ), constant(Term2).
polynomial (TermiN ,X ) ~
!, natural -ilumber (N), polynomial (Term,X ).
Program 11.4: Recognizing polynomials
Once the term being tested has been recognized as a sum (by unifying with
the head of the rule ) , it is known that none of the other polynomial rules will
be applicable . Program 11.4 gives the complete polynomial program with cuts
added . The result is a deterministic program , which has a mixture of cuts after
conditions and cuts after unification .
When discussing the Prolog programs for arithmetic , which use the underlying
arithmetic capabilities of the computer rather than a recursive logic program ,
we argued that the increased efficiency is often at the price of flexibility . The
logic programs lost their multiple uses when expressed as Prolog programs . Prolog
programs with cuts also have less flexibility than their "cut -free" equivalents .
This is not a problem if the intended use of a program is one- way to begin with ,
as is often the case.
The examples so far have demonstrated pruning useless alternatives for the
parent goal . We give an example where cuts greatly aid efficiency by removing
redundant computations of sibling goals. Consider the recursive clause of an
interchange sort program ,
sort(Xs,Ys) +-
append(As,[X ,YIBs],Xs),
X>Y.
append(As,[Y,X IBs],Xsl ),
sort(Xsl ,Ys).
162 Cuts and Negation 11.1
sort(Xs,Ys) +-
append(As,[X,YIBs],Xs),
X>Y,
,.,
append(As,[Y,X IBs],Xsl ),
sort(Xsl ,Ys).
sort(Xs,Xs) +-
ordered(Xs) ,
!.
ordered(Xs) +- SeeProgram3.20
Program 11.5: Interchangesort
The program search es for a pair of adjacent elements which are out of order ,
swaps them , and continues until the list is ordered . The base clause is
As noted before , the main difference from a performance point of view between
more elegant and pleasing then their iterative counterparts , defined in terms of
recursive programs , precisely those that can be translated directly into iterative
The implementation technique which achieves this space saving is called tail
an iterative one .
A ' + - Bl , B2 ' . . . , Bn .
with most general unifier (J 0 The optimization is potentially applicable to the last
call in the body of a clause , Bno It re - uses the area allocated for the parent goal
The key precondition for this optimization to apply is that there are no choice
points left from the time the parent goal A reduced to this clause , to the time
the last goal Bn is reduced . In other words , that A has no alternative clauses for
reduction left , and that no choice points left in the computation of goals to the
left of Bn , namely the computation of the conjunctive goal ( Bl , B2 ' " . , Bn - l ) {) '
was deterministic .
technique , clause indexing , also interacts closely with tail recursion optimization ,
and enhances the ability of the implementation to detect that this precondition
occurs . Indexing performs some analysis of the goal , to detect which clauses
164 Cuts and Negation 11.2
If it is used to append two complete lists , then by the time the recursive
append goal is executed , the preconditions for tail recursion optimization hold .
No other clauseis applicable to the parent goal (if the first argument unifies with
[XlXs ], it certainly won't unify with [ ], since we assumedthat the first argument
is a complete list ). There are no other goals in the body besides append, so that
the second precondition holds vacuously .
However , for the implementation to know that the optimization applies , it
needs to know that the second clause , although not tried yet , is not applicable .
Here index.ing comes into play . By analyzing the first argument of append, it is
possible to know that the second clause woUld fail even before trying it , and to
apply the optimization in the recursive call to append.
Not all implementations provide indexing , and not all cases of determinism
can be detected by the indexing mechanisms available . Therefore it is in the interest
of the programmer to help an implementation , which supports tail recursion
optimization , to recognize that the preconditions for applying it hold .
There is a sledgehammer technique for doing so: add a cut before the last
goal of a clause, in which tail recursion optimization should always apply , as in
Al +- BI ,B2 ,. . .,!,Bn .
This cut prunes both alternative clauses left for the parent goal A , and any alter -
natives
leftforthecomputation
of (Bl,B2'" .,Bn- l )()'
In general , it is not possible to answer if such a cut is green or red , and the
programmer 's judgment should be applied .
It should be noted that the effect of tail recursion optimization is enhanced
greatly when accompanied with a good garbage collector . Stated negatively , the
optimization is not very significant without garbage collection . The reason is
that most tail recursive programs generate some data structures on each iteration .
Most of these structures are temporary , and can be reclaimed (see, e.g., the editor
in Program 12.5) . Together with a garbage collector , such programs can run ,
in principle , forever . Without it , although the stack space they consume would
remain constant , the space allocated to the uncollected temporary data structures
would overflow .
11.3 Negation 165
not X +-
X is not provable .
not X +- X , ! , fail .
not X .
11.3 Negation
The use of green cuts does not change the declarative meaning of Prolog
programs . However , by considering the procedural behavior of cut , it can be used
to express negative information to a limited extent .
The cut is the basis of implementing a limited form of negation in Prolog
called negation as failure . Program 11.6 is the standard definition of not ( GoaQ,
which is true if Goal fails . It uses the metavariable facility , and a system predicate
fail that fails (i .e., there are no clauses defined for it ) . A conjunction of cut and
fail is referred to as a cut -fail combination . We assume that not has been defined
as a prefix operator .
Let us consider the behavior of Program 11.6 in answering the query not
G ? The first rule applies and G is called using the metavariable facility . If G
succeeds, the cut is encountered . The computation is then committed to the first
rule , and not G fails . If the call to G fails , then the second rule of Program 11.6
is used, which succeeds. Thus not G fails if G succeeds and succeeds if G fails .
The rule order is essential for Program 11.6 to behave as intended . This introduces
anew , not entirely desirable , dimension to Prolog programs . Previously ,
changing the rule order only changed the order of solutions . Now the meaning
of the program can change . Procedures where the rule order is critical in this
sense must be considered as a single unit , rather than as a collection of individual
clauses.
The query not married ( abraham,sarah) ? terminates (with failure ) eyen though
married ( abraham,sarah) ? does not terminate .
166 Cuts and Negation 11.3
failure arises from Prolog ' s incompleteness in realizing the computation model of
logic programs . The definition of negation as failure for logic programs is in terms
one , even if it exists . There are goals that could fail by negation as failure , that
do not terminate under Prolog ' s computation rule . For example , the query not
p ( s ( X ) ) + - p ( X ) .
q ( a ) .
The query would succeed if the q ( X ) goal were selected first , since that gives a
The inadequacy of Program 11 . 6 also stems from the order of traversal of the
search tree , and arises when not is used in conjunction with other goals . Consider
using not to define a relationship unmarried _ student ( X ) for someone who is both
student ( bill ) .
married ( joe ) .
The query unmarried _ student ( X ) ? fails with respect to the data above , ignoring
that X = bill is a solution logically implied by the rule and two facts . The failure
occurs in the goal not married ( X ) since there is a solution X = joe . The problem
can be avoided here by swapping the order of the goals in the body of the rule .
The implementation of negation as failure using the cut - fail combination does
not work correctly for nonground goals , as the examples above demonstrate . In
to ensure that negated goals are ground before they are solved . This can be
done either by a static analysis of the program , or by a runtime check , using the
X :i=Y +-
X and Y are not unifiable.
x :/: x +- !, fail.
X :/: Y.
Program 11.7: Implementing =1=
For example , consider a predicate disjoint (Xs , Ys) true if two lists Xs and Ys have
no elements in common . It can be defined as
Many other examples of using not will appear in the programs throughout the
book .
Some cuts in a cut -fail combination are green cuts . That is , the program has
the same mea Jling if the clause containing the cut -fail combination is removed . For
example , consider Program 10 .4 defining the predicate ground . An extra clause
CaJl be added , which CaJl reduce the search , without affecting the meaning .
The use of cut in Program 11 .6 implementing not is not green , but red . The
program does not behave as intended if the cut is removed .
The cut -fail combination is used to implement other system predicates involving
negation . For example , the predicate =1= can be simply implemented via
unification and cut -fail , rather than via an infinite table , with Program 11 .7 . This
program also works correctly only for ground goals .
The argument for its correctness follows : " If the arguments to same _vaT are the
same variable , binding X to foo will bind the second argument as well , so the first
168 Cuts and Negation 11.3
clause will fail , and the second clause will succeed . If either of the arguments is
not a variable , both clauses will fail . If the arguments are different variables , the
first clause will fail , but the cut stops the second clause from being considered "
(0 'Keefe 83).
Prolog 's sequential choice of rules and its behavior in executing cut are the
key features necessary to compose the program for not . The programmer can
take into account that Prolog will only execute a part of the procedure if certain
conditions hold . This suggests anew , and misguided , style of programming in
Prolog , where the explicit conditions governing the use of a rule are omitted .
The prototypical (bad) example in the literature is a modified version of
Program 11.3 for minimum . The comparison in the second clause of the program
can be discarded to give the program
minimum (X ,Y ,X ) +- X :$:Y , !.
minimum (X ,Y ,Y ).
The reasoning offered to justify the program is as follows : "If X is less than or
equal to Y, then the minimum is X . Otherwise the minimum is Y, and another
comparison between X and Y is unnecessary ." Such a comparison is performed ,
however , by Program 11.3.
There is a severe flaw with this reasoning . The modified program has a
different meaning from the standard program for minimum . It succeeds on the
goal minimum (2,5,5) . The modified program is a false logic program.
The incorrect minimum goal implied by the modified program can be avoided .
It is necessary to make explicit the unification between the first and third arguments
, which is implicit in the first rule . The modified rule is
This technique of using the cut to commit to a clause after part of the unification
has been done is quite general . But for minimum the resultant code is contrived .
11.4 Red cuts : omitting explicit conditions 169
It is far better to simply write the correct logic program , adding cuts if efficiency
is important , as done in Program 11.3.
The only effect of the green cuts presented in Section 11.1 is to prune branch es
from the search tree , which are known to be useless . Cuts , whose presence in a
program changes the meaning of that program , are called red cuts. The removal
of a red cut from a program changes its meaning , i .e., the set of goals it can prove .
A standard Prolog programming technique using red cuts is the omission of
explicit conditions . Knowledge of the behavior of Prolog , the order it uses rules in
a program , is relied on to omit conditions that could be inferred to be true . This
is sometimes essential in practical Prolog programming , since explicit conditions ,
especially negative ones, are cumbersome to specify , and inefficient to run . But
making such omissions is error -prone .
Consider Program 11.5 for interchange sort. The first (recursive) rule applies
whenever there is an adjacent pair of elements in the list that are out of order .
When the second sort rule is used, there are no such pairs and the list must be
sorted. Thus the condition ordered(Xs) can be omitted , leaving the second rule
as the fact sort(Xs,Xs) . As with minimum , this is an incorrect logical statement.
Once the ordered condition is removed from the program , the cut changes
color from green to red . Removing the cut from the variant without the ordered
condition leaves a program which gives false solutions .
Let us consider another example of omitting an explicit condition . Consider
Program 3.18 for deleting elements in a list . The two recursive clauses cover
distinct cases, corresponding to whether or not the head of the list is the element
to be deleted . The distinct nature of the cases can be indicated with cuts as given
in Program 11.8a.
By reasoning that the failure of the first clause implies that the head of the
170 Cuts and Negation
11.4
delete(Xs,X , Ys)
Y s is the result of deleting all occurrences of X from the list Xs .
delete(Xs,X , Ys) . -
Y s is the result of deleting all occurrences of X from the list Xs .
if-then_else(P, Q,R) . -
Either P and Q, or not P and R .
if _then_else(P,Q,R) . - P, !, Q.
if _then_else(P,Q,R) . - R.
Program 11 .9 : If then else statement
list is not the same as the element to be deleted , the explicit inequality test can
be omitted from the second clause. The modified program is given as Program
11.8b . The cuts in Program 11.8a are green in comparison to the red cut in the
first clause of Program 11.8b .
Let us investigate the use of cut to express the if -then - else control structure .
Program 11.9 defines the relati ~n if-then_else(P,Q,R) . Declaratively, the relation
is tru ,e if P and Q are true , or not P and R are true . Operationally , we prove P
and , if successful, prove Q, else prove R .
The utility of a red cut to implement this solution is self-evident . The alternative
to using a cut is to make explicit the condition under which R is run . The
second clause would read :
11.5 Default rules 171
We have seen so far two kinds of red cuts . One kind is " built - into " the
program , as in the definitions of not and ~ . A second kind was a green cut that
became red when conditions in the programs were removed . However , there is a
third kind of red cut . A cut that is introduced into a program as a green cut that
just improves efficiency , can turn out to be a red cut that changes the program ' s
.
mean Ing .
For example , consider trying to write an efficient version of member that does
not succeed several times when there are multiple copies of an element in a list .
Taking a procedural view , one might use a cut to avoid backtracking once an
member ( X , [X I Xs ] ) + - ! .
Adding the cut indeed changes the behavior of the program . However it is now not
7 . 3 , with the explicit condition X : f = Yomitted , and hence the cut is red .-
whether a cut - fail rule would be useful , and whether explicit conditions can
be omitted . /
( ii ) Analyze the relation between Program 3 . 19 for select and the program obtained
select ( X , (X I Xs ] , Xs ) + - ! .
11 . 5 Default rules
Logic programs with red cuts are essentially comprised of a series of special
cases , and a default rule . For example , Program 11 . 6 for not had a special case
172 Cuts and Negation 11.5
invalid ( mc _tavish ).
when the goal G succeeded , and a default fact not Gused otherwise .
The second
rule for if - then _ else in Program 11 .9 is
if - then _ else ( P , Q , R ) + - R .
person , Person , is entitled to . The first pension rule says that a person is entitled
to an invalid pension if he is an invalid . The second rule states that people over
the age of 65 are entitled to an old age pension if they have contributed to a
suitable pension scheme for long enough , in short they must be paid _up . People
who are not paid up are still entitled to supplementary benefit if they are over 65 .
Consider extending Program lI . 10a to include the rule that people receive
nothing if they do not qualify for one of the pensions . The procedural " solution "
11.6 Background 173
is to add cuts after each of the three rules , and an extra default fact
pension (X , nothing ).
which people are entitled , for example , pension ( mc _ tavish , X ) ? The program is
not correct , though . The query pension ( mc _ tavish , nothing ) ? succeeds which
mc _ tavish wouldn 't be too happy about , and pension ( X , old _ age _ pension ) ? has
the erroneous unique answer X = mc _ tavish . The cuts prevent alternatives being
found . Program 11 . 10b only works correctly to determine the pension a given
person is entitled to .
for pension :
entitlement (X ,Y ) + - pension (X ,Y ) .
This program has all the advantages of Program 11 . 10b , and neither of the
as the default rule is really a new concept and should be presented as such .
11 . 6 Background
and was perhaps one of the most influential design decisions in Prolog . Colmerauer
The terminology of green cuts and red cuts was introduced by van Emden
( 1982 ) , in order to try and separate between legitimate and illegitimate uses of
cuts . Alternative control structures , which are more structured then the cut , are
constantly being proposed , but the cut still remains the workhorse of the Prolog
programmer . Some of the extensions are if - then - else constructs ( O ' Keefe , 1985 ) ,
as , " weak - cuts ," " snips ," remote - cuts ( Chikayama , 1984 ) , and not itself , which ,
The cut is also the ancestor of the commit operator of concurrent logic languages
, which was first introduced by Clark and Gregory ( 1981 ) in their Relational
174 Cuts and Negation 11.6
Language . The commit cleans up one of the major drawbacks of the cut , which
is destroying the modularity of clauses. The cut is asymmetric , as it eliminates
alternative clauses below the clause in which it appears , but not above . Hence a
cut in one clause affects the meaning of other clauses . The commit , on the other
hand , is symmetric , and therefore cannot implement negation -as-failure , and does
not destroy the modularity of clauses.
Attempts have been made to give cut a semanticswithin Prolog. One treatment
can be found in Lloyd (1984).
Tail recursion optimization was first described by Warren (1981) and implemented
in Prolog-10. It was implemented concurrently by Bruynooghe (1982) in
his Prolog system .
There is a class of predicates in Prolog that lie outside the logic programming
model , and are called extra-logical predicates . These predicates achieve a side effect
in the course of being satisfied as a logical goal . There are basically three
types of extra -logical system predicates : predicates concerned with I / O, predicates
for accessing and manipulating the program , and predicates for interfacing
with the underlying operating system . Prolog I / O and program manipulation
predicates are discussed in this chapter . The interface to the operating system
is too system dependent to be discussed in the text . Appendix B lists system
predicates for Wisdom Prolog running under Unix .
A very important class of predicates that produces side effects is that concerned
with I / O . Any practical programming language must have a mechanism
for both input and output . The execution model of Prolog , however , precludes
the expression of I / O within the pure component of the language .
The basic predicate for input is read(. X) . This goal reads a term from the
current input stream , usually from the terminal . The term that has been read is
unified with X , and read succeeds or fails depending on the result of unification .
The basic predicate for output is write (. X) . This goal writes the term X on
the current output stream , as defined by the underlying operating system , usually
to the terminal . Neither read nor write give alternative solutions on backtracking .
The normal use of read is with a variable argument X , which acquires the
value of the first term in the current input stream . The instantiation of X to
176 Extra -Logical Predicates 12.1
something outside the program lies outside the logical model , since each time the
Read attempts to parse the next term on the input stream . If it fails , it prints
an error message on the terminal , and attempts to read the next term .
There is an asymmetry between the extra - logical nature of read and write . If
all calls to write were replaced with a goal true which always succeeds once , the
Different Prolog systems have different extra utilities that are system dependent
The goal writeln ( Xs ) writes the list of terms Xs as a line of output . It is defined
in Program 12 . 1 . It uses the system predicate nl , which causes the next output
to be on a new line .
Character strings are inserted by quoting them . For example , the goal
The value of X is 9
if X were instantiated to 3 .
lists of character codes . Assuming ASCII codes , the list [ 80 , 114 , 111 , 108 , 111 , 103 ]
represents " Prolog . " The ASCII code for P is 80 , for r 114 , etc . Doubly quoted
strings are an alternative notation for lists of ASCII values , for example , " Prolog . "
Such strings are just syntactic sugar for the list . Manipulating strings can be done
character strings , and vice versa . The goal name ( X , Y ) succeeds if X is an atom ,
and Y is the list of ASCII codes corresponding to the characters in X , for example ,
A lower level of I / O than the term level , characterized by read and write , is
the character level . The basic output predicate at the character level is put ( N ) ,
which places the character corresponding to ASCII code N on the current output
12.1 Input / Output 177
get ( C ) ,
word _ char ( C ) ,
read _ word ( C , W , C1 ) ,
fill _ char ( C ) ,
get ( C1 ) ,
read _ word ( C , W , C1 ) + -
word _ chars ( C , Cs , Cl ) ,
name ( W , Cs ) .
word _ char ( C ) ,
,
. ,
get ( C1 ) ,
word _ chars ( C 1 , as , CO ) .
word _ chars ( C , ( ] , C ) + -
fill _ char ( 32 ) .
% Blank
stream . The basic input predicate is get(X) 1 which returns in X the ASCII code
of the first character on the input stream .
Program 12.2 is a utility predicate read_word_list ( Words) that reads in a list
of words , Words. It is built using get. The words can be separated by arbitrarily
many blanks (ASCII code 32) , and may contain any mixture of uppercase and
- --
1 This is slightly different from Edinburgh Prolog.
178 Extra -Logical Predicates 12.1
lowercase letters , and underscores . The words are terminated by a full -stop .
The predicate read_ward_list reads a character , C, and calls read_word- list ( C,
Words) . This predicate does one of three actions , depending on what C is . If C
is a word character , then the next word is found , and recursively the rest of the
words are found that is an uppercase letter , a lowercase letter or an underscore .
The second action is to ignore filling characters , and so the next character is read ,
and the program continues recursively . Finally , if the character denoting ends of
words is reached , the program terminates and returns the list of words .
It is important that the program must always read a character ahead , and
then test what it should do . If the character is useful , for example , a word
character , it must be passed down to be part of the word . Otherwise characters
can get lost on the event of backtracking . Consider the following read and process
loop :
process ( [ ]) +-
get (C) , end_of- words _char ( C) .
process( [W IWords ]) +-
get (C ) , word _char (C) , get _word (C ,W ) , process (Words ) .
If the first character in a word is not an end_of_words_char , the first clause will
fail , and the second clause will cause the reading of the next character .
Returning to Program 12.2, the predicate read_word ( O, W, Ol ) reads a word
Wgiven the current character Cand returns the next character after the word , 01 .
The list of characters comprising the word are found by word_chars/ 9 (with the
same arguments as read _word ) . The word is created from the list of characters
using the system predicate name . In word _chars there is the same property of
looking ahead one character at a time , so that no character is lost .
Predicates such as.fill _char/ l and word_char/ l exemplify data abstraction in
Prolog .
(i) Extend Program 12.2 so that it handles apostrophes (39) and numbers (48-
57) as word characters , and question marks (63) and exclamation marks (33)
as terminators . What would be involved in using it to read in sentences, with
commas , etc .?
12.2 Program access and manipulation 179
So far our programs have been assumed to be resident in the computer 's
memory , without discussion of how they are represented or how they got there .
deleting ) clauses .
The system predicate for accessing the program is clause ( Head , Body ) . The
goal clause ( Head , Body ) ? must be called with Head instantiated . The program is
searched for the first clause whose head unifies with Head . The head and body
of this clause are then unified with Head and Body . On backtracking , the goal
succeeds once for each unifiable clause in the procedure . Note that clauses in the
Facts have the atom true as their body . Conjunctive goals are represented
using the binary functor " ," . The actual representations can be easily abstracted
away , however .
member ( X , [X I Xs ] ) .
The goal clause ( member ( X , Ys ) , Body ) has two solutions : { Ys = [X ] Xs ] , Body = true }
and { Ys = [ 11 Ysl ] , Body = member ( X , Ysl ) } . Note that a fresh copy of the variables
the metalogical primitives freeze and melt , the clause is stored in frozen form in
the program . Each call to clause causes a new melt of the frozen clause . This is
System predicates are provided both to add clauses to the program , and to
remove clauses . The basic predicate for adding clauses is assert ( Clause ) , which
adds Clause as the last clause of the corresponding procedure . For example ,
assert ( father ( haran , lot ) ) ? adds the father fact to the program . When describing
rules an extra level of brackets is needed for technical reasons concerning the
correct syntax .
There is a variant of assert , assert a , that adds the clause at the beginning of
a procedure .
The predicate retract ( 0 ) removes from the program the first clause in the
180 Extra -Logical Predicates 12.2
program unifying with C . Note that to retract a clause such as a ~ b , c , d , you need
to specify retract (( a ~ C) ) . A call to retract may only mark a clause for removal ,
rather than physically removing it , and the actual removal would occur only when
Prolog ' s top - level query is solved . This is due to implementation reasons , and may
lead to anomalous behavior in some Prologs .
Asserting a clause freezes the terms appearing in the clause . Retracting the
same clause melts a new copy of the terms . In many Prologs this is exploited to
be the ea Biest way of copying a term . The predicate copy aBsumed in Chapter 10
can thus be defined aB
The predicates assert and retract introduce to Prolog the possibility of programming
with side effects . Code depending on side effects for its successful
execution is hard to read , hard to debug , and hard to reason about formally .
Hence these predicates are somewhat controversial , and using them is sometimes
can be written using assert and retract , but the result is less clean and less efficient
. Further , as Prolog compiler technology advances , the inefficiency in using
logically follows from the program . In such a case adding it will not affect the
meaning of the program , since no new consequences can be derived from it , but
perhaps only its efficiency , as some consequences could be derived faster . This
use is exemplified in the lemma construct , introduced in Section 12 . 3 below .
We identify a few other legitimate uses for assert and retract . One is setting
up and using global switch es that affect program execution . This will be discussed
in Section 13 . 2 on programming hacks . Another is for solving problems which by
hanoi ( N , A , B , C , Moves ) ~
Moves is the sequence of moves required to move N discs
from peg A to peg B using peg C as an intermediary
according to the rules of the Towers of Hanoi puzzle .
hanoi ( 1 ,A ,B ,C , [A to B ] ) .
hanoi ( N ,A ,B ,C ,Moves ) +-
N > 1,
N1 := N - 1 ,
lemma (hanoi ( ni ,A ,C ,B ,Ms1 ) ) ,
hanoi ( ni ,C ,B ,A ,Ms2 ) ,
append ( Ms1 , [A to BIMs2 ] ,Moves ) .
lemma ( P ) +- P , assert a ( ( P +- I) ) .
Testing
12 . 3 Memo ~functions
lemma ( P ) +- P , assert a ( ( P +- I) ) .
The next time the goal P is attempted , the new solution will be used , and there
will be no unnecessary recomputation . The cut is present to prevent the more
general program being used . Its use is justified only if P does not have multiple
solutions .
in terms of Program 3.30, 1023 calls of hanoi(l ,A ,B, C,Xs) . The overall number
of general calls of hanoi/ 5 is significantly more.
The solution to the Towers of Hanoi repeatedly solves subproblems moving
the identical number of discs . A memo - function can be used to recall the moves
made in solving each subproblem of moving a smaller number of discs. Later
attempts to solve the subproblem can use the computed sequence of moves rather
than recomputing them .
The idea is seen with the recursive clause of hanoi in Program 12 .3 . The first
call to solve hanoi with N - 1 discs is remembered , and can be used by the second
call to hanoi with N - 1 discs.
The program is tested with the predicate test_hanoi (N ,Pegs,Moves) . Nis the
number of discs, Pegs is a list of the three peg names , and Moves is the list of moves
that must be made . Note that in order to take advantage of the memo -functions ,
a general problem is solved first . Only when the solution is complete , and all
memo -functions have recorded their results , are the peg names instantiated .
(i ) Two players take turns to say a number between 1 and 3 inclusive . A sum is
kept of the numbers , and the player who brings the sum to 20 wins . Write a
program to play the game to win , using memo-functions .
12 .4 Interactive programs
The read / echo loop is invoked by the goal echo. The heart of the program
12.4 Interactive programs 183
is the relation echo(X) , where Xis the term to be echoed. The program assumes
a predicate end_of-file (X) which is true if X is the end-of-file marker . What is
the end-of-life marker is system dependent . If the end-of-file marker is found , the
loop terminates ; otherwise the term is written and a new term is read .
Note that the testing of the term is separate from its reading . This is necessary
to avoid losing a term : terms cannot be reread . The same phenomenon
occurred in Program 12.2 for processing characters . The character was read and
then separately processed .
We give two examples of programs using the basic cycle of reading a term
then processing it . The first is a line editor . The second interactive program is a
shell for Prolog commands , which is essentially a top- level interpreter for Prolog
in Prolog .
The first decision in writing a simple line editor in Prolog is how to represent
the file . Each line in the file must be accessible, together with the cursor position ,
that is the current position within the file . We use a structure file (Before ,After )
where Before is a list of lines before the cursor , and After is a list of lines after the
cursor . The cursor position is restricted to be at the end of some line . The lines
before the cursor will be in reverse order to give easier access to the lines nearer
the cursor . The basic loop accepts a command from the keyboard , and applies it
to produce a new version of the file . Program 12.5 is the editor .
An editing session is invoked by edit, which initializes the file being processed
to the empty file , file ( [ ],[ ]) ) . The interactive loop is control led by edit (File ) . It
writes a prompt on the screen, using edit_prompt , then reads and process es a
command . The processing uses the basic predicate edit ( File , Command ) which
applies the command to the file . The application is performed by the goal apply
( Command ,File ,Filel ) where Filel is the new version of the file after the command
has been applied . The editing continues by calling edit/ l on Filel . The
third edit/ 2 clause handles the case when no command is applicable , indicated by
the failure of apply. In this case an appropriate message is printed on the screen
and the editing continues . The editing session is terminated by the command
exit , which is separately tested for by edit/ 2.
Let us look at a couple of apply clauses, to give the flavor of how commands
are specified . Particularly simple are commands for moving the cursor . The clause
apply(up,file([XI:XS
] ,Ys),file(:XS
,[X I Ys])).
184 Extra -Logical Predicates 12.4
edit(File,exit) +- !.
edit(File,Command ) +-
apply(Command,File,Filel ), !, edit(Filel ).
edit(File,Command ) +-
writeln([Command ,' is not applicable']), !, edit(File).
apply(up,file([X IXsJ,Ys),file(Xs,[X IYsJ)).
apply(up(N) ,file(Xs,Ys) ,file(Xs1,Y si )) ~
N > 0, up(NiXs,Ys,Xs1,Ys1).
apply(down,file(Xs,[YIYs]),file([YIXsJ,Ys)).
apply(insert(Line),file(Xs,Ys) ,file(Xs,[LineIYs])).
apply(delete,file(Xs,[YIYsJ),file(Xs,Ys)).
apply(print,file([X IXsJ,Ys),file([X IXs],Ys)) ~
write(X), nl.
apply(print (* ),file(Xs,Ys),file(Xs,Ys)) ~
reverse(Xs,Xs1), write..file(Xs1), write le(Ys).
up ( N , [ ] , Ys , [ ] , Ys ) .
up ( O , Xs ,Y siXs ,Y s ) .
up ( N , [X I Xs ] , Ys , Xsl , Ysl ) + -
N > 0, Nl is N- l , up(Nl,Xs,[XIYs],Xsl,Ysl).
write - fiIe ( [X I Xs ] ) + -
write - file ( [ ] ) .
says that we move the cursor up by moving the line immediately above the cursor
to be immediately below the cursor . The command fails if the cursor is at the
top of the me . The command for moving the cursor down is analogous to moving
the cursor up , and is also in Program 12.5.
Moving the cursor up N lines , rather than a single line , involves using an
auxiliary predicate up/ 5 to change the cursor position in the file . Issues of robustness
surface in its definition . Note that apply tests that the argument to up
is sensible , i .e., a positive number of lines , before up is invoked . The predicate up
itself handles the case when the number of lines to be moved up is greater than
12.4 Interactive programs 185
the number of lines in the file . The comm ~~nd succeeds with the cursor placed at
the top of the file . Moving a cursor down N lines is requested in the exercises.
Other commands given in Program 12.5 insert and delete lines . The command
for insert , in ~ert (Line ) , contains an argument , namely the line to be inserted
. The command for delete is straightforward . It fails if the cursor is at the
bottom of the screen. Also in the editor are commands for printing the line above
the cursor , print , and for printing the whole file , print ( * ) .
The editor commands are mutually exclusive . Only one apply clause is applicable
for any command . This is indicated by the cut in the second edit/ 2
clause. As soon as an apply goal succeeds, there are no other possible alternative
paths . This method of imposing determinism is a little different than described
in Section 11.1 where the cuts would have been applied directly to the apply facts
themselves . The difference between the two approach es is merely cosmetic .
A possible extension to the editor is to allow each command to handle its
own error messages. For example , suppose you wanted a more helpful message
than " Command not applicable " when trying to move up when at the top of the
file . This would be handled by ertending the apply clause for moving up in the
file .
shell-solve(Goal) +-
Goal, write (Goal), nl , fail .
shell-solve(Goal) +-
write ('No (more) solutions'), nl .
shell-solve_ground(Goal) +-
Goal, !, write ('Yes'), nl .
shell-solve_ground(Goal) +-
write ('No'), nl .
shell-prompt +- write ('Next command? ').
Program 12.6: An interactive shell
The shell can be used as a basis for a logging facility to keep a record of a
session with Prolog . Such a facility is given as Program 12 . 7 . This new shell is
invoked by log , which calls the basic interactive predicate shell ( Flag ) with Flag
initialized to log . The Hag takes one of two values , log or nolog , and indicates
whether the output is currently being logged , or not .
being that the principal predicates take an extra argument , indicating the current
state of logging . Two extra commands are added , log and nolog , to turn logging
on and off .
The Hag is used by the predicates concerned with I / O . Each message written
on the screen must also be written in the logging file . Also each goal read is
inserted in the log to increase the log ' s readability . Thus calls to read in Program
12 .6 are replaced by a call to shell _read , and calls to write replaced by calls to
shell _ write .
shell _write ( X ,log ) + - write ( X ) , file _write ( [X ] , ' prolog .log ' ) .
If the flag is currently nolog , the output is written normally to the screen . If
12.4 Interactive programs 187
! , shell ( nolog )
! , shell ( log ) .
file _ write ( ( ' Next command ? ' , X ] , ' prolog . log ' ) .
file _ write ( X , File ) + - telling ( Old ) , tell ( File ) , write ( X ) , ill , tell ( Old ) .
the flag is log , an extra copy is written to the file prolog . log . The predicate
Only two of the predicates in Program 12 . 7 are system dependent ; file _ write
and close _ logging - file . They depend on additional system predicates for dealing
188 Extra-LogicalPredicates 12.4
with files . Their definition uses the Edinburgh Prolog primitives tell , told and
telling which are discussed in Appendix B . The other assumption built into the
code is that the logged output will be recorded in a file prolog .log.
(i) Extend Program 12.5, the editor , to handle the following comlI lands
a. Move tne cursor down N lines .
b . Delete N lines .
c. Move to a line containing a given term .
d . Replace one term by another .
e. Any command of your choice .
(ii ) Modify the loggihg facility , Program 12.7, so the user can specify the destination
file of the logged output .
The interactive programs in the previous section were all based on tail recursive
loops . There is an alternative way of writing loops in Prolog that are
analogous to repeat loops in conventional languages . These loops are driven by
failure and are called failure -driven loops. These loops are useful only when used in
conjunction with extra -logical predicates which cause side effects . Their behavior
can be understood only from an operational point of view .
A simple example of a failure-driven loop is a query Goal, write ( GoaQ, nl,
fail ? which causes all solutions to a goal to be written on the screen. Such a loop
is used in the shells of Programs 12.6 and 12.7.
A failure-driven loop can be used to define the system predicate tab(N) for
printing N blanks on the screen. It uses Program 8.5 for between:
tab (N) +- between(liN ,1), put (32), fail .
Failure -driven loops that use repeat are called repeat loops, and are the analogue
of repeat loops from conventional languages . Repeat loops are useful in
Prolog for interacting with the outside system to repeatedly read and/ or write .
Repeat loops require a predicate that is guaranteed to fail (the goal echo(X) in
Program 12.8), which causesthe iteration to continue. This predicate only succeeds
when the loop should be terminated . A useful heuristic for building repeat
loops is that there should be a cut in the body of the clause with the repeat
goal , which prevents a non -terminating computation in case the loop is being
backtracked into .
We use a repeat loop to define the system predicate consult(File) for reading
in a file of clauses and asserting them . Program 12.9 contains its definition . The
system predicates see(File) and seenare used for opening and closing a input file,
respectively .
Tail recursive loops are preferable to repeat loops because the latter have
no logical meaning . In practice , repeat loops are often necessary to run large
computations, especially on Prolog implementations without tail recursion optimization
and/ or without garbage collection. Explicit failure typically initiates
some implementation dependent reclamation of space.
190 Extra -Logical Predicates 12.5
(i ) Define the predicate abolish(FiN) that retracts all the clauses for the procedure
F of arity N .
12 .6 Background
Input / output has never really blended well with the rest of the language
of Prolog . Its standard implementation , with side effects , relies solely on the
procedural semantics of Prolog , and has no connection to the underlying logic
programming model . For example , if an output is issued on a failing branch of a
computation , it is not undone upon backtracking . If an input term is read , it is
lost on backtracking , as the input stream is not backtrackable .
Concurrent logic languages can and do eliminate this feature altogether (Silverman
et al ., 1986) , since global , modifiable , data structures can be implemented
in them by monitors , which have a pure logical definition as a perpetual recursive
process (Shapiro , 1984) .
12.6 Background 191
In Edinburgh Prolog there are two system predicates for reading characters ,
get O ( X ) and get ( X ) . The distinction between them is that get O returns the next
character , while get gets the next printable character , that is one with an ASCII
code greater than 32 . Only one , the more general , is necessary , which we call get
The program for the Towers of Hanoi was shown to us by Shmuel Sarra .
Programs in the previous chapters on pure Prolog and its extensions emphasized
The logic programming folklore has a large body of techniques and tricks that are
from any other language in its need for a methodology to build and maintain
development methodology .
discuss this issue , we need to set out the criteria for evaluating different programs .
The main factor is the number of unifications performed and attempted in the
aborts . In practice this is a major problem . The third issue is the number of data
algorithm into Prolog would preserve the expected performance of the algorithm .
13.1 Efficiency of Prolog programs 193
Typically , the resulting Prolog programs would rely neither on general unification
nor on deep backtracking .
A difficulty may arise in the implementation of algorithms that rely heavily
on destructive manipulation of data structures , e.g., destructive pointer manipulation
and arrays . Such data structures can be simulated directly in Prolog , with a
logarithmic overhead , using standard techniques (e.g., simulation by trees ) . However
, in many c~ es it would be more natural to modify the algorithm itself , to
accommodate for the single -~ signment nature of the logical variable .
Issues in ordering goals are mentioned in Section 7.3. How to use green cuts
to express determinism of a program is discussed in Section 11 . 1 .
numbervars('$VA R'(N),N,Nl ) +-
Nl := N+ l .
numbervars(Term,Nl ,N2) +-
nonvar(Term) , functor (Term,Name,N),
numbervars(O,N,Term,Nl ,N2).
numbervars(N ,N,Term,Nl ,Nl ) .
numbervars(I ,N,Term,Nl ,N3) +-
1 < N,
11 := 1+ 1,
arg(ll ,Term,Arg ),
numbervars(Arg ,Nl ,N2) ,
numbervars(ll ,N,Term,N2,N3).
Program 13.2: Numbering the variables in a term
13 .2 Programming tricks
given as Program 13.2. The program counts up rather than counts down so that
the variables will be numbered in increasing order from left to right . In order not
to give an error , calls to numbervars must have the second argument instantiated .
Numbervars can be used in a backhanded way to define metalogical predicates
. For example ,
'X2 ' 'Y2 ' 'Z2' 'U2' 'V2' 'W2' 'X3 ' 'Y3' 'Z3' 'U3' 'V3 ' 'W3'])
, , , , , , , , , " .
set~ ag(Name,X ) +-
nonvar(Name),
retract (flag(Name,Val)), !,
asserta(flag(Name,X ) ).
set~ ag(Name,X ) +-
nonvar(Name), asserta(flag(Name,X )).
Program 13 .5: Using global flags
utility that writes variables with names rather than numbers . The predicate
lettervars(X) given in Program 13. transforms the variables in X to alphanumeric
198 Pragmatics 13.2
gensym(Prefix ,V ) +-
var (V ),
atom(Prefix),
old-value(Prefix ,N),
Nl := N+ l ,
set-Hag(gensym(Prefix),Nl ),
string _concatenate(Prefix ,N 1,V) ,
t.
old-value(Prefix ,N) +- flag(gensym(Prefix) ,N), !.
old_value(Prefix ,O).
string _concatenate(X ,Y ,XY ) i -
name(X ,Xs), name(Y ,Ys), append(Xs,Ys,XYs ), name(XY ,XYs).
set- Hag(Flag ,Value ) +- See Program 13.5
Program 13 .6 : Generating new symbols
Another programming trick uses assert and retract to simulate global variables
. The predicate flag (Name , Value) is used to maintain the current value of
the flag , while set_flag (Name , Value) sets the value of the flag . The definition of
set-flag is given as Program 13.5.
One basic concern in composing the programs in this book has been to make
them ''as declarative as possible " to increase program clarity and readability .
13.3 Programming style and layout 199
physical layout , and by the choice of names appearing in it . This section discuss es
the various objects in the program . The choice of all predicate names , variable
names , constants and structures appearing in the program affect its clarity . The
between objects in the program , rather than describes what the program
is doing . Coining a good declarative name for a procedure does not come easily .
rather than declaratively ( and programs with procedural names usually ran
faster ) . Once the program works , however , we often revise the predicate names
lists .
Variables that appear only once in a clause can be handled separately . They
member ( X , [ XI - ] ) .
member ( X , [ - I Xs ] ) + - member ( X , Xs ) .
The advantage of the convention is to highlight the significant variables for unification
names and predicate functors . For variables , composite words are run together
, each new word starting with a capital letter . Multiple words in predicate
names are linked with underscores . Syntactic conventions are a matter of taste ,
The layout of individual clauses also has an effect on how easily programs
foo( (Arguments)) +-
bart ((Argumentsl ))'
barz((Argumentsz)),
.
barn ( (Argumentsn}).
The heads of all clauses are aligned , the goals in the body of a clause are
indented and occupy a separate line each. A blank line is inserted between procedures
, but there is no space between individual clauses of a procedure .
Layout in a book and the typography used are not entirely consistent with
actual programs . If all the goals in the body of a clause are short , then have them
on one line . Occasionally we have tables of facts with more than one fact per line .
13 .4 Program development
difference between low -level and high -level languages , then , is only the threshold
after which simple examination of the program is insufficient to determine its
correctness .
Given this situation , there are weaker alternatives . One is to prove that one
Prolog program , perhaps more efficient though more complex , is equivalent to a
simpler Prolog program , which , though less efficient , could serve as a specification
for the first . Another is to prove that a program satisfies some constraint , such
as a "loop invariant ," which , though not guaranteeing the program 's correctness ,
increases our confidence in it .
A third answer is that the properties of the high -level formalism of logic may
eventually lead to a set of practical program development tools that is an order of
magnitude more powerful then what is known today . Examples of such tools are
automatic program transformers , partial -evaluators , type inference programs , and
algorithmic debuggers . The latter are addressed in Section 19.3, where program
diagnosis algorithms and their implementation in Prolog are described .
Unfortunately , practical Prolog programming environments incorporating
these novel ideas are not yet widely available . In the meantime , a simple tracer ,
such as explained in Section 19.1, is most of what one can expect . Nevertheless ,
large and sophisticated Prolog programs can be developed even using the current
Prolog environments , perhaps with greater ease than in other available languages .
The current tools and systems do not dictate or support a specific program
development methodology . However , as with other symbolic programming languages
, rapid prototyping is perhaps the most natural development strategy . In
this strategy , one has an evolving , usable prototype of the system in most stages
of the development . Development proceeds by either rewriting the prototype
program or extending it . Another alternative , or complementary , approach to
program development is "think top -down , implement bottom -up ." Although the
design of a system should be top -down and goal driven , its implementation proceeds
best if done bottom up . In bottom -up programming each piece of code
written can be debugged immediately . Global decisions , such as representation ,
can be tested in practice on small sections of the system , and cleaned up and
made more robust before most of the programming has been done . Also , experience
with one subsystem may lead to changes in design decisions regarding other
subsystems .
The size of the chunks of code that should be written and debugged as a whole
varys and grows as the experience of the programmer grows . Experienced Prolog
programmers can write programs consisting of several pages of code , knowing
that what is left after writing is done is mostly simple and mundane debugging .
Less experienced programmers might find it hard to grasp the functionality and
interaction of more then a few procedures at a time .
We would like to conclude this section with a few moralistic statements . For
every programming language , no matter how clean , elegant , and high level , one
can find programmers who will use it to write dirty , contorted , and unreadable
programs . Prolog is no exception . However , we feel that for most problems that
have an elegant solution , there is an elegant expression of their solution in Prolog .
It is a goal of the book to convey both this belief and the tools to realize it
in concrete cases, by showing that aesthetics and praticality are not necessarily
opposing or conflicting goals.
13.5 Background 203
13.5 Background
The expressive power and high -level nature of logic programming can be
exploited to write programs that are not easily expressed in conventional programming
languages . Different problem solving paradigms can be supported , and
alternative data construction and access mechanisms can be used.
The simple Prolog programs of the previous part are examples of the use
of basic programming techniques , reinterpreted in the context of logic programming
. This part collects more advanced techniques that have evolved in the logic
programming community and exploit the special features of logic programs . We
show how they can be used to advantage .
Chapter 14
Nondeterministic
Programming
technique for optimizing generate and test programs is to try and "push " the tester
inside the generator , as "deep" as possible . Ultimately , the tester is completely
intertwined with the generator , and only correct solutions are generated to begin
with .
It is easy to write logic programs that , under the execution model of Prolog
, implement the generate - and-test technique . Such programs typically have a
conjunction of two goals, in which one acts as the generator and the other tests
whether the solution is acceptable , as in the following clause:
program .
verb(Sentence, Verb) +-
Verb is a verb in the list of words Sentence.
element in common :
The first member goal in the body of the clause generates members of the
first list , which are then tested by the second member goal whether they are in
the second list . Thinking nondeterministic ally , the first goal guesses an X in Xs ,
while the second verifies that the guess is a member of Ys.
Note that when executed as a Prolog program , this clause effectively implements
two nested loops . The outer loop iterates over the elements of the first
list , and the inner loop checks whether the chosen element is a member of the
second list . Hence this nondeterministic logic program achieves, under the execution
model of Prolog , a behavior very similar to the standard solution one would
compose for this problem in Fortran , Pascal , or Lisp .
The definition of member in terms of append,
is itself essentially generate- and- test program . The two stages however are amalgamated
by the use of unification . The append goal generates splits of the list ,
and immediately a test is made whether the first element of the second list is X .
Let us consider optimizing generate - and-test programs by pushing the tester
into the generator . Program 14.2 for permutation sort is another example of a
generate and test program . The top level is as follows :
The program has been well studied in the recreational mathematics literature .
There is no solution for N = 2 and N = 9, and a unique solution up to reflection for
N = 4 shown in Figure 14.1. There are 88 (or 92 depending on strictness with
symmetries ) solutions for N = 8.
Program 14.2 is a simplistic program solving the N queens problem . The re-
210 Nondeterministic Programming 14.1
queens(N, Queens) +-
Queens is a placement that solves the N queens problem ,
representedas a permutation of the list of numbers [1,2,. . .,N] .
queens(N,Qs) +-
range(liN ,Ns) , permutation (Ns,Qs) , safe(Qs).
safe(Qs) +-
The placement Qs is safe.
The program behaves as follows . The predicate range creates a list N s of the
numbers from 1 to N . Then a generate- and-test cycle begins . The permutation
predicate generates a permutation Qs of Ns , which is tested whether it is a solution
to the problem with the predicate safe( Qs). This predicate is true if Qsis a correct
placement of the queens. Since two queens are not placed on the same row or
column , the predicate need only check whether two queens attack each other
along a diagonal . Safe is defined recursively . A list of queens is safe if the queens
represented by the tail of the list are safe, and the queen represented by the head
of the list doesnot attack any of the other queens. The definition of attack(Q, Qs)
uses a neat encapsulation of the interaction of diagonals . A queen is on the same
diagonal as a second queen N columns away if the second queen's row number is
N units greater than , or N units less than , the first queen 's row number . This
14.1 Generate - and - test 211
queens( NJ Queens) +-
Queens is a placement that solves the N
queens problem , represented
as a permutation of the list of numbers [1,2,...1'11.
Program 14.2 cannot recognize when solutions are symmetric . The program
gives two solutions to the query queens(4, Qs) '1, namely Qs= [2,4,1,9] and
Qs= [9,1,4,2].
Although a well written logic program , Program 14.2 behaves inefficiently .
Many permutations are generated that have no chance of being solutions . As with
permutation sort , we improve the program by pushing the tester , in this case safe,
into the generator .
Instead of generating the complete permutation , that is , placing all the queens
and then testing it , each queen can be checked as it is being placed . Program
14.3 computes solutions to the N queens problem by placing the queens one at
a time . It also proceeds by generating and testing , in contrast to insertion sort ,
which became a deterministic algorithm by the transformation . The generator in
the program is select and the tester is attack , or more precisely its negation .
The positions of the previously placed queens are necessary to test whether
a new queen is safe. Therefore the final solution is built upward using an accumulator
. This is an application of the basic technique described in Section 7.5. A
consequence of using an accumulator is that the queens are placed on the righthand
edge of the board . The two solutions to the query queens(4' , Qs) are given
in the opposite order to Program 14.2.
212 Nondeterministic Programming 14.1
The next problem is to color a planar map so that no two adjoining regions
have the same color . A famous conjecture , an open question for a hundred years ,
was proved in 1976 showing that four colors are sufficient to color any planar map .
Figure 14.2 gives a simple map requiring four colors to be colored correctly . This
can be proved by enumeration of the possibilities . Hence four colors are both
necessary and sufficient .
Program 14.4 which solves the map -coloring problem also uses the generate -
and-test programming technique extensively . The program implements the following
nondeterministic iterative algorithm .
choose a color ,
choose (or verify ) colors for the neighboring regions from the
remaining colors .
Test data
test_color(Name,Map) +-
map(Name,Map),
colors ( Name , Colors ) ,
b d
The sharing of variables is used to ensure that the same region is not colored with
The top - level relation is color _ map ( Map , Colors ) where the Map is represented
as above , and Colors is a list of colors used to color the map . Our colors
are red , yellow , blue and white . The heart of the algorithm is the definition of
Both the select and members goals can act a . s generators or testers depending on
214 Nondeterministic Programming 14.1
solve_puzzle(Puzzle,Solution ) +-
Solution is a solution of the Puzzle,
where Puzzle is puzzle( Clues, Queries ,Solution ) .
computed is solve _ puzzle ( Puzzle , Solution ) , where Solution is the solution to Puzzle .
The puzzle is represented by the structure puzzle ( Clues , Queries , Solution ) , where
the data structure being instantiated is incorporated into the clues and queries ,
The code for solve - puzzle is trivial . All it does is successively solve each
clue and query , which are expressed as Prolog goals and are executed with the
metavariable facility .
The clues and queries for our example puzzle are given in Program
Each person has three attributes , and can be represented by the structure
friend ( Name , Country , Sport ) . There are three friends whose order in the programming
The programs defining the conditions did _ better , name , nationality , sport , and
test . Each of the did - better and member goals access people , and the remaining
goals access attributes of the people . Whether they are generators or testers
depends on whether the arguments are instantiated or not . The answer to the
complete puzzle , for the curious , is that Michael is the Australian , and Richard
plays tennis .
216 NondeterministicProgramming 14.1
Test data
test_puzzle(Name,puzzle(Clues,Queries,Solution)) +-
structure (Name,Structure),
clues(Name,Structure ,Clues),
queries(Name,Structure ,Queries,Solution).
structure (test, [friend (Nl ,Cl ,Sl ) ,friend (N2,C2,S2) ,friend (N3,C3,S3))).
clues(test,Friends,
[(did _better (Manl Cluel ,Man2Cluel ,Friends) , % Clue 1
name(Manl Cluel ,michael), sport (Manl Cluel ,basketball),
nationality (Man2Cluel ,american) ),
(did_better (Manl Clue2,Man2 Clue2,Friends), %Clue2
name(Manl Clue2,simon) , nationality (Man 1Clue2,israeli),
sport (Man2 Clue2,tennis) ),
(first (Friends,Man Clue3), sport (Man Clue3,cricket)) %Clue3
J).
queries ( test , Friends ,
[ member ( Ql , Friends ) ,
name ( Ql , Name ) ,
name ( Q2 , richard ) ,
did_better (A ,C,[ A ,B ,C ] ) .
did_better (B ,C,[A ,B , C ] ) .
name ( friend ( A ,B , C ) ,A ) .
nationality ( friend ( A ,B , C ) ,B ) .
sport ( friend ( A ,B , C ) , C ) .
first ( [X I Xs ] ,X ) .
(ii ) Write a program to solve the stable marriage problem (Sedgewick , 1983)
stated as follows :
Suppose there are N men and N women who want to get married to each
other . Each man has a list of all the women in his preferred order , and each
woman likewise has a list of the men in preferred order . The problem IS to
find a set of marriages that is stable .
A set of marriages is unstable if two people who are not married both prefer
each other to their spouses. For example , suppose there are two men , A and
B , and two women , X and Y, such that A prefers X to Y, B prefersY to X ,
X prefers A to B , and Y prefers B to A . The pair of marriages A - Y and B - X
is unstable , since A prefers X to Y, while X prefers A to B .
Your program should have as input lists of preferences , and produce as output
a stable s'et of marriages , i .e., one that is not unstable . It is a theorem from
graph theory that this is always possible . Test the program on the following
5 men and 5 women with their associated preferences :
avraham : chana tamar zvia ruth sarah
binyamin : zvia chana ruth sarah tamar
chaim : chana ruth tamar sarah zvia
david : zvia ruth chana sarah tamar
elazar : tamar ruth chana zvia sarah
(iii ) Use Program 14.4 to color the map of Western Europe . The countries are
given in Program 14.5.
(iv ) Write a program to solve the following logic puzzle . There are five houses,
each of a different color and inhabited by a man of a different nationality ,
with a different pet , drink and brand of cigarettes .
Most examples of don 't -care nondeterminism are not relevant for the Prolog
programmer . A prototypical example is the code for minimum . Program 3.7
is the standard , incorporating a limited amount of don 't -care nondeterminism ,
namely when X and Yare the same:
In Section 7.4, we termed this redundancy and advised against its use.
14.2 Don 't -care and don 't -know nondeterminism 219
b
y z
u v
On the other hand , programs exhibiting don 't - know Il;<?ndeterminism are common
. Consider the program for testing whether two binary trees are isomorphic
(Program 3.25 reproduced below). Each clauseis independently correct, but given
two isomorphic binary trees , we dorr' t know which of the two recursive clauses
should be used to prove the isomorphism . Operationally , only when the computation
terminates successfully do we know the correct choice :
isotree(void,void).
isotree(tree(X ,Ll ,Rl ),tree(X ,L2,R2)) ~ isotree(Ll ,Rl ), isotree(L2,R2).
isotree(tree(X ,Ll ,Rl ),tree(X ,L2 ,R2)) +- isotree(Ll ,R2), isotree(L2,Rl ).
connected(X, Y) +-
N ode X is connected to node Y,
given an edge / 2 relation describing a DAG .
connected(A ,A ).
connected(A ,B) +- edge(A ,N), connected(NiB ).
Data
path(X, Y,Path) +-
Path lH a nath
~ between two nodes X and Y
in the DAG defined by the relation edge / f .
path ( X ,X , [X ] ) .
path ( X , Y , [XIP ]) +- edge ( X ,N ) , path ( N , Y ,F ) .
connected ( X , Y) +-
Node X is connected to node Y in the graph defined by edge / 2 .
connected ( X ,Y ) + - connectd ( X , Y , [X ] ) .
connected ( A ,A , Visited ).
connected ( A , B , Visited ) +-
edge ( A ,N ) , not member ( N ,Visited ) , connected ( NiB , [N IVisited ]).
acyclic graphs ( DAGs ) , behave better than graphs with cycles as we will see in
our example programs .
The solutions to the query connected(a,X) using the data from the left-hand
graph in Figure 14.3 gives the solutions b, c, / , h, i , g, d, j , e, k. Their order
constitutes a depth -first traversal of the tree .
Program 14.9 is an extension of this simple program that finds a path between
two nodes. The predicate pdlh(X , Y,Path) is true if Path is a path from the node
X to the node Y in a graph . Both endpoints are included in the path : The path
is built downward , which fits well with the recursive specification of the connected
relation . The ease of computing the path is a direct consequence of the depth -
first traversal . The equivalent extension of a breadth first traversal is much more
difficult , to be discussed in Sections 17 .2 and 18 . 1 .
Depth-first search, dfs, correctly traverses any finite tree or DAG (directed
acyclic graph). There is a problem, however, with traversing a graph with cycles.
The computation can becomelost iii an infinite loop (literally !) around one of the
cycles. For example, the query connected(x,Node) ?, referring to the right -hand
graph of Figure 14.3 gives the solutiony , z, and x repeatedly without reaching u
or v .
The section is completed with a program for building simple plans in the
blocks world . The program is written nondeterministic ally , essentially performing
a depth -first search. It combines the two extensions given above - keeping an
accumulator of what has been traversed , and computing a path .
The problem is to form a plan in the blocks world , that is , to specify a
sequence of actions for restacking blocks to achieve a particular configuration .
Figure 14.4 gives the initial state and the desired final state of a blocks world
problem . There are three blocks , a,b, and c, and three places , p, q, and r . The
actions allowed are moving a block from the top of a block to a place and moving
a block from one block to another . For the action to succeed , the top of the moved
block must be clear , and also the place or block to which it is being moved .
The top level procedure of Program 14.11, that solves the problem , is trans -
222 NondeterministicProgramming 14.2
on ( Block ,Y , State ) , clear ( Block , State ) , place ( Place ) , clear ( Place , State ) .
__ffi______EJ
p
- q r p q r
There are two possible actions , moving to a block and moving to a place . For
each, the conditions for which it is legal must be specified , and how to update it .
Program 14.11 successfully solves the simple problem given as Program 14.12.
The first plan it produces is horrendous , however , being
[to _place ( a,b ,q) ,to _block ( a,q,c) ,to _place (b ,p ,q) ,to _place ( a,c,p) ,
to _block ( a,p ,b ) ,to _place (c,rip ) ,to _place ( a,b ,r ) ,to _block (a,r ,c) ,
to _place (b ,q,r ) ,to _place ( a,c,q) ,to _block ( a,q,b) ,to _place ( c,p ,q) ,
to _place ( a,b ,p ) ,to _block (a,pic ) ,to _place (b ,rip ) ,to _place ( a,c,r ) ,
to _block (b ,p ,a) ,to -place ( c ,q,p ) ,to _block (b ,a,c) ,to _place ( a,r ,q) ,
224 Nondeterministic Programming 14.2
The first plan now produced is [to_place( a, b, q), to_block( b,p, c), to-block( a, q, b)].
acceptS ) +-
The string representedby the list S is accepted by
the NDFA defined by initial / l , delta/ 9, and final / l .
acceptS ) +- initial (Q), accept(Q,S).
accept(Q,(X IXs]) +- delta(Q,X ,Ql ) , accept(Ql ,Xs).
accept(Q,( ]) +- final (Q).
Program 14.13: An interpreter for a nondeterministic finite
automaton
initial (qO).
final(qO).
delta(qO,a,q 1).
delta(ql ,b,qO).
Program 14.14: An NDFA that acceptsthe language (ab).
~;J==: ~~~~~~
b
to state Ql on receipt of symbol A . The set of states and symbols are defined
implicitly as the constants that appear in the predicates .
Program 14.13 is an abstract interpreter for an NDFA . The basic predicate is
acceptS ) which is true if the stringS , represented as a list of symbols , is accepted
by the NDFA .
In order to use the interpreter to simulate the behavior of a particular finite
automaton , the automaton must be specified . That entails defining its initial
state , its final state , and the transition relation delta. Program 14.14 gives the
definitions for an NDF A which accepts the language ( ab) . . There are two states ,
qO and ql . If in state qO an a is read , the automaton moves to state ql , while the
transformation from ql to qO happens if a b is read . The automaton is pictured
in Figure 14.5.
226 Nondeterministic Programming 14.3
acceptS ) +-
The string represented by the list S is accepted by
the NPDA defined by initial / l , delta / 5 , and final / l .
initial ( qO ) . final ( q 1 ) .
automaton , the predicates determining the initial and final states of the automaton
need be given . The sets of symbols are defined implicitly . Program 14 . 15
a. ssumes that the stack is initially empty . The change relation delta ( Q , A , 8 , Ql , 81 )
is slightly different from before . It is true if in state Q on input symbol A and
stack state 8 the NPDA enters state Q1 and produces the stack state 81 .
palindrome (Xs ) +-
The string representedby the list Xs is a palindrome.
palindrome(Xs) +- palindrome(qO,Xs,[ I).
palindrome(qO,[X IXs],S) +- palindrome(qO,Xs,[XIS]).
palindrome(qo,[X IXs],S) +- palindrome(ql ,[X IXs],S).
palindrome(qO,[X IXs],S) +- palindrome(ql ,Xs,S).
palindrome(ql ,[X IXs],[XIS]) +- palindrome(ql ,Xs,S).
palindrome(ql ,[ ],[ ]).
Program 14.17: A programacceptingpalindromes
The automaton has two states : qO, when symbols are pushed onto the stack , and
ql , when symbols are popped from the stack and compared with the symbols on
the input stream . When to stop pushing and start popping is decided nondeter -
ministically - . There are two delta facts that change the state from qOto ql to allow
for palindromes of both odd and even length .
The combination of the intervreter v Ius automaton can be exvressed in a
single program . Program 14 . 17 is an amalgamation of Programs 14 . 15 and 14 . 16 .
languages .
accept (X ) ~ accept ( qO ,X , [ ] ) .
If the definition of a goal has several clauses , then the unfolding produces several
clauses , one for each in the definition . For example , unfolding the delta goal in
228 Nondeterministic Programming 14.3
the clause
The three programs chosen are the ANALOGY program of Evans for solving
geometric analogy questions from intelligence tests ; the ELIZA program of
Weizenbaum which simulates or , rather , parodies conversation , and McSAM , a
micro -version of SAM , a program for "understanding " stories from the Yale language
group . Each of the logical reconstructions are expressed very simply . The
nondeterminism of Prolog allows the programmer to ignore the issues of search.
Consider the task of solving the geometric analogy problems typically used in
intelligence tests . Several figures are presented in a prototypical problem . Figures
A , B and G are singled out from a list of possible answers and the following
question is posed : "A is to B as G is to which one of the ' answer ' figures ?" Figure
14.7 gives a simple problem of this type .
14.4 AI classics: ANALOGY , ELIZA , and McSAM 229
is to L
A B c
I 2
63
Figure 14.6: A geometric analogy problem
Here is an intuitive algorithm for solving the problem , where terms such as
find , apply and rule are left unspecified :
find a rule that relates A to B ,
apply the rule to C to give a figure X ,
find X , or its nearest equivalent , among the answers.
In the problem in Figure 14.6, the positions of the square and triangle are swapped
(with appropriate scaling ) between Figures A and B . The "obvious " answer is to
swap the square and the circle in Figure C. The resultant figure appears as No .
2 in the possible answers.
Program 14.18 is a simple program for solving analogy problems . The basic
relation is analogy(Pairl ,Pair2 ,Answers ) , where each Pair is of the form X is_to
Y. To parse the program , is_to must be declared as an infix operator . The two
elements in Pair1 bear the same relationship as the two elements in Pair2 , and the
second element in Pair2 appears in Answers . The definition of analogy implements
the intuitive algorithm :
An analogy holds between the two pairs of figures Pairl and Pair2 .
The " intelligence " of the program is greatly affected by this decision . In Program
( square , triangle ) .
invert is used to solve our example problem . The behavior of invert is to swap its
arguments .
The predicate match is used in two distinct ways in this program . The first
time it produces an operation matching two given figures . The second time it
produces a second figure given an operation and a figure . These details are ,
find is implemented simply , checking whether a given figure appears in the list of
14.4 AI classics
: ANALOGY, ELIZA, and McSAM 231
I am unhappy .
How long have you been unhappy .
Six months . Can you help me .
~ What makes you think I help you .
You remind me of my father and brother .
Can you tell me more about father .
I like teasing father .
Does anyone else in your family like teasing father .
No , only me .
Please go on .
answers .
real sense , responding to its input by recognizing word patterns and replying by
The heart of ELIZA is a procedure for matching the input sentence against
pair , where the input is matched against the stimulus , and the output generated
Using this pair , the response of the program to the input statement " I am unhappy
" will be the question " How long have you been unhappy ? " The ( statement )
algorithm :
generate the reply from the response and the above match ,
eliza +-
Simulates a conversation via side-effects.
The stimulus/ response pairs are represented as facts of the form pattern
(Stimulus,Response ) where both Stimulus and Responseare lists of words
and slots . Slots in the patterns are represented by integers . The predicate
match(Pattern, Table, Words) is used for both the second and third steps of the
above algorithm . It express es a relationship between a pattern Pattern , a list
of words Words and a table Table where the table records how the slots in the
pattern are filled . A central part of the match procedure is played by a nondeterministic
use of append to break up a list of words . The table is represented by
an incomplete data structure , a topic to be discussed in more detail in the next
chapter. The missing procedure lookup/ 3 will be given in Section 15.3. The reply
is generated by reply( Words) which is a modified version of writeln that leaves
spaces between words .
The first element in each list , ptrans and ingest , for example , is a term from
conceptual dependency theory . The representation of the story as a list of lists is
chosen as a tribute to the original Lisp version .
Programming McSAM in Prolog is a triviality as demonstrated by Program
14.21. The top-level relation is mcsam(Story,Script) which expands a Story into
its "understood " equivalent according to a relevant Script . The script is found
by the predicate find (Story,Script,Defaults). The story is searched for a nonvariable
argument that triggers the name of a script . In our example of John
visiting Leones , the atom leones triggers the restaurant script , indicated by the
fact trigger( leones,restaurant) in Program 14.22.
The matching of the story to the script is done by match(Script, Story) which
234 Nondeterministic Programming 14.4
mcsam(Story,Script) +-
find (Story,Script ,Defaults),
match(Script,Story),
name_defaults(Defaults) .
find (Story,Script ,Defaults) +-
filler (Slot,Story),
trigger (Slot,Name),
script (Name,Script ,Defaults).
match(Script,Story) +-
Story is a subsequence of Script .
match(Script ,[ ]).
match ( [Line IScript],[Line IStory]) +- match (Script ,Story ) .
match([Line IScript],Story) +- match(Script,Story) .
filler (Slot, Story) +-
Slot is a word in the Story .
filler (Slot,Story) +-
member([Action IArgs] ,Story),
member(Slot,Args) .
name_defaults(Defaults) +-
Unifies default pairs in Defaults .
name-defaults(( ]).
name_defaults([-[N,N]IL]) +- name _defaults
(L).
name_defaults([[Nl,N2 ]IL]) +- Nl # N2,name _defaults
(L).
Program 14 .21 : McSAM
associates lines in the story with lines in the script . Remaining slots in the script
are filled in by name_defaults ( Defaults ) . The "output " is
unification .
,..
father ," ELIZA responds with " Does anyone else in your family like teasing
my father ." Modify Program 14 . 20 to " fix " this behavior , changing references
Output : John went to Leones . He was shown from the door to a seat .
The waiter brought John a check , and John left Leones for
another place .
14 . 5 Background
queens problem and coloring maps , using generate - and - test programs . They have
used the examples as evidence of Prolog 's inadequate control . Suggestions for
1979 ) and intelligent backtracking ( Bruynooghe and Pereira , 1984 ) . A good discussion
( 1983 ) .
The zebra puzzle , Exercise 14 . 1 ( iv ) , did the rounds on the Prolog Digest in
the early 1980 's and was used as an unofficial benchmark to test both the speed
code .
The definitive discussion of don ' t - care and don ' t - know non determinism in
The original planning program in Prolog was Warpian ( Warren , 1976 ) , reproduced
in Coelho et al . ( 1980 ) .
mid 1960 ' s . A good description of the program appears in Semantic Information
Processing ( Minsky , 1968 ) . Evans ' program tackled many aspects of the problem
that are made trivial by our choice of representation , for example , identifying that
there are triangles , squares and circles in the figures . Our version , Program 14 . 17 ,
emerged from a discussion group of the first author with a group of Epistemics
nQ
(i ) Given
00
A B
U I
C9
2@3 DO
5 0
(ii ) Given
:
0
~ 0 F
7
00 8 9
6
10
(iii ) Given:
@
G@H [g
I
0
II
~ 12 13 14 15
15 .1 Difference -lists
. . . . . . . . .
.
Xs
. v .
Xs\ Ys
. . . . . y
N
~ cn
. . . . .
. . . . . . . . . .
. V J
Xs \ Z s
Logical expressions are unified , not evaluated , so that the name of the binary
functor used to denote difference -lists can be arbitrary , as long as it is used consistently
. It can even be omitted entirely , the head and tail of the difference -list
becoming two separate arguments in a predicate .
Lists and difference -lists are closely related . Both are used to represent sequences
of elements . Any list L can be trivially represented as a difference -list
L\ [ ]. The empty list is representedby any difference-list whose head and tail are
identical , the most general form being As\ As.
Difference -lists are an established logic programming technique . The use of
difference - lists rather than lists can lead to more concise and efficient programs .
The improvement occurs because of the combining property of difference -lists .
Two incomplete difference -lists can be concatenated to give a third difference -list
in constant time . In contrast , lists are concatenated , using the standard append
program , in time linear in the length of the first list .
Consider the lists in Figure 15.1. The difference-list Xs\ Zs is the result of
appending the difference-list Ys\ Zs to the difference-list Xs\ Ys. This can be
expressedas a single fact. Program 15.1 defines a predicate append_dl(As,Bs,Cs)
which is true if the difference - list Cs is the result of appending the difference -list
Bs to the difference -list As . We use the suffix _dl to denote a variant of a predicate
that uses difference - lists .
15.1 Difference-lists 241
append _ dl ( As , Bs , Cs ) + -
append _ dl ( Xs \ Ys , Ys \ Zs , Xs \ Zs ) .
ftatten ( Xs , Ys ) + -
Difference - lists are the logic programming counterpart of Lisp ' s rplacd , which
is also used to concatenate lists in constant time and save " consing " ( allocating
new list - cells ). There is a difference between the two : the former are side - effect
free , and can be discussed in terms of the abstract computation model , whereas
The doubly recursive clause can be simplified by unfolding the append_dl goal
with respectto its definition in Program15.1. The result is
Hatten_dl([X IXs],As\ Ds) +-
Hatten_dl(X,As\ Bs), Hatten_dl(Xs,Bs\ Ds).
The program for flatten _dl can be used to implement flatten by expressing the
connection between the desired flattened list and the difference - list computed by
flatten _dl as follows :
Let us investigate the program in action . Figure 15.2 is a trace of the query
flatten (((a),(b,(c))),Xs) ? with respect to Program 15.2.
The trace shows that the output , Xs, is built top-down (in the terminology
of Section 7.5). The tail of the difference-list acts like a pointer to the end of
the incomplete structure . The pointer gets set by unification . By using these
"pointers " no intermediate structures are built , in contrast to Program 9.la .
reverse ( Xs , Y s) +-
Y s is the reversal of the list Xs .
reverse _ dl ( [X I Xs ] , Ys \ Zs ) +-
reverse - dl ( Xs , Y s \ [XI Zs ] ) .
reverse _dl ( [ ] , Xs \ Xs ) .
quicksort ( Xs , Ys ) i - quicksort _ dl ( Xs , Ys \ [ ]) .
quicksort _ dl ( [X I Xs ] , Ys \ Zs ) +-
quicksort _ dl ( Bigs , Y sl \ Zs ) .
quicksort _dl ( [ ) , Xs \ Xs ) .
We give another example of the similarity between difference -lists and ac-
cumulators . Program 15 .3 is a translation of "naive " reverse (Program 3 .16a )
where lists have been replaced by difference -lists , and the append operation has
been unfolded away .
When are difference -lists the appropriate data structure for Prolog programs ?
Programs with explicit calls to append can usually gain in efficiency by using
difference -lists rather than lists . A typical example is a doubly recursive program
where the final result is obtained by appending the outputs of the two recursive
calls . More generally a program that independently builds different sections of a
list to be later combined together is a good candidate for using difference -lists .
Program 15.2. The recursive clause is the quicksort algorithm interpreted for
difference - lists where the final result is pieced together implicitly rather than
explicitly . The base clause of quicksort _distates that the result of sorting an empty
list is the empty difference -list . Note the use of unification to place the partitioning
element X after the smaller elements Y s and before the bigger elements Ysl in
the call quicksort_dl(Littles , Ys\ [Xl Ys1]).
Program 15. 4 is derived from Program 3.22 in exactly the same way that
Program 15.2 is derived from Program 9.la . Lists are replaced by difference -lists
and the append_dl goal unfolded away. The initial call of quicksort _dl by quicksort
express es the relationship between the desired sorted list and the computed sorted
difference - list .
distribute ( [ ] ,[ ] ,[ ] ,[ ]) .
(i ) Rewrite Program 15.2 so that the final list of elements is in the reverse order
to how they appear in the list of lists .
(ii ) Rewrite Programs 3.27 for pre_order ( Tree,List ) , in _order ( Tree,List ) and
post_order ( Tree,List ) , which collect the elements occurring in a binary tree ,
to use difference -lists and avoid an explicit call to append .
(iii ) Rewrite Program 12.3 for solving the Towers of Hanoi so that the list of
moves is created as a difference - list rather than a list .
15 .2 Difference - structures
The concept underlying difference -lists is the use of the difference between
incomplete data structures to represent partial results of a computation . This
can be applied to recursive data types other than lists . This section looks at a
specific example , algebraic sums .
This program is similar in structure to Program 15.2 for flattening ~ists using
difference -lists . There is an initialization stage where the difference -structure is
set up , typically calling a predicate with the same name but different arity or
different argument pattern . The base case passes out the tail of the incomplete
structure , while the goals in the body of the recursive clause pass the tail of the
first incomplete structure to be the head of the second.
248 Incomplete Data Structures 15.2
+
/ \
+ +
/ \ / \
a bc d
The program builds the normalized sum top -down . By analogy with the
programs using difference -lists , the program can be easily modified to build the
structure bottom -up , which is an exercise at the end of the section .
The declarative reading of these programs is straightforward . Operationally
the programs can be understood in terms of building a structure incrementally ,
where the "hole " for further results is referred to explicitly . This is entirely
analogous to difference -lists .
15 .3 Dictionaries
What happens if we check Cathy 's number with the query lookup ( cathy,Dict ,
5951) ? where the number is incorrect ? Rather than entering a second entry for
Cathy , the query fails due to the test Key # Key1 .
The lookup procedure given in Program 15.8 completes Program 14.20, the
simplified ELIZA . Note when the program begins , the dictionary is empty , indicated
by being a variable . The dictionary is built up during the matching against
the stimulus half of a stimulus -response pair . The constructed dictionary is used
to produce the correct response . Note that entries are placed in the dictionary
250 Incomplete Data Structures 15.3
lookup(Key,Dictionary , Value) +-
Dictionary contains Value indexed under Key .
Dictionary is represented aBa list of pairs (Key, Value) .
lookup (Key,[(Key,Value) IDictionary ],Value).
lookup (Key,[(Keyl ,Valuel ) IDictionary ],Value) +-
Key :f: Keyl , lookup (Key,Dictionary ,Value).
Program 15 .8 : Dictionary lookup from a list of tupies
lookup(Key,Dictionary , Value) +-
Dictionary contains Value indexed under Key .
Dictionary is represented as an ordered binary tree .
without their values being known : a striking example of the power of logical variables
. Once an integer is detected , it is put in the dictionary , and its value is
determined later .
Searching linear lists is not very efficient for a large number of key-value pairs .
Ordered binary trees allow more efficient retrieval of information than linear lists .
The insight that an incomplete structure can be used to allow entry of new keys
as well as to look up values carries over to binary trees .
The binary trees of Section 3.4 are modified to be a four -place structure
dict(Key, Value,Left,Right), where Left and Right are, respectively, the left and
right sub diction aries , and Key and Value are as before . The functor dict is used
to suggest a dictionary .
At each stage the key is compared with the key of the current node . If it
15.3 Dictionaries 251
freeze(A ,B) +-
Freeze term A into B.
freeze(A )B) +-
copy(A ,B), numbervars(B )O,N).
melt_new(A ,B) +-
Melt the frozen term A into B.
is less, the left branch is recursively checked; if it is greater , the right branch is
taken . H the key is non -numeric , the predicates < and > must be generalized .
The cut is necessary in Program 15.9, in contrast to Program 15.8, due to the
nonlogical nature of comparison operators which will give errors if keys are not
instantiated .
252 Incomplete Data Structures 15.3
Given a number of pairs of keys and values , the dictionary they determine is
not unique . The shape of the dictionary depends on the order in which queries
are posed to the dictionary .
The dictionary can be used to melt a term that has been frozen using Program
13.2 for numbervars . The code is given as Program 15.10. Each melted variable is
entered into the dictionary , so that the correct shared variables will be assigned.
15 .4 Queues
A queue is empty if both its head and tail can be instantiated to the empty list ,
expressed by the fact empty ( [ ]\ [ ]) . Logically , the clause empty (. X\ X) would also
be sufficient , however , due to the lack of occurs check in Prolog , discussed in
Chapter 4, it may succeed erroneously on a nonempty queue , creating a cyclic
data structure .
15.4 Queues 253
queue(S) f0-
B is a sequence of enqueue and dequeue operations ,
representedas a list of terms enqueue(X) and dequeue
(X) .
queue(S) +- queue(S,Q\ Q) .
queue([enqueue(X ) IXs],Q) ~
enqueue(X ,Q,Ql ), queue(Xs,Ql ).
queue([dequeue(X )IXs],Q) ~
dequeue(X ,Q,Ql ), queue(Xs,Ql ).
queue([ ],Q).
enqueue(X ,Qh\ [X IQt),Qh\ Qt ) 1
dequeue(X ,[X IQh)\ Qt ,Qh\ Qt )
Program 15 . 11 : A queue process
When the empty list is being flattened , either the top element is dequeued
flatten ( Xs , Ys ) +-
flatten ( Xs , Ys ) +- flatten - q ( Xs , Qs \ Qs , VB ) .
flatten _ q ( [X I Xs ] , Ps \ [XsIQs ] , Ys ) +-
flatten _q ( X , Ps \ Qs ,Y s ) .
flatten _ q ( X , [ QIPs ] \ Qs , [X I Ys ]) +-
constant ( X ) , X : / : [ ] , flatten _ q ( Q , Ps \ Qs , Ys ) .
flatten - q ( [ ] , [ QIPs ] \ Qs , Ys ) +-
flatten _ q ( Q , Ps \ Qs , Ys ) .
flatten _q ( [ ] , [ ] \ [ ] , [ ] ) .
flatten _q ( [ ] , [ ] \ [ ] , [ ] ) .
reduced to the goal flatten _ q ( X , Asl \ [X I Asl ] , Ys ) using the third clause . The goal
is further reduced to flatten _ q ( Xl , Asl \ AslYs ) by the first clause , which clearly
gives rise to a non - terminating computation . Flattening an empty list with respect
unification with the third clause of flatten _ q can be avoided by changing the order
of clauses and using a cut . The preferred , declarative solution is to add a test to
the third clause of flatten _q so that it only succeeds on non - empty queues .
flatten _ q ( [ ] , Queue ,Y s ) ~
queue , enqueue (X) messages are sent with X determined , and dequeue (X) with X
undetermined . As long as more elements are enqueued than dequeued , the queue
behaves as expected , with the difference between the head of the queue and the
tail of the queue being the elements in the queue . However , if the number of
happens - the content of the queue becomes " negative ." The head runs ahead
It is interest - to observe
in ~ that this behavior is consistent with the associa -
15 .5 Background
Difference-lists have been in the logic programming folklore since its inception
. The first description of them in the literature is given by Clark and Tarnlund
(1977).
The automatic transformation of simple programs without difference-lists to
programs with difference-lists, for example, reverseand flatten , can be found in
Bloch (1984).
The elegant lookupprocedure for ordered binary trees is describedby Warren
(1980), and is used as a central technique for writing compilers in Prolog, as will
be described in Chapter 23. .
Maintaining dictionaries and queues can be given a theoretical basis as a
perpetual process, as described by van Emden (1984) and Lloyd (1984).
Queuesare more important in concurrent logic programming languages, since
their input need not be a list of requests but a stream, which is generated incrementally
by the processes requesting the servicesof the queue.
Chapter 16
Parsing with
Definite Clause Gra Ill Illars
Prolog in fact originated from attempts to use logic to express grammar rules and
Parsing with DCGs is discussed here because of its relevance to the previous
( nonterminal ) - + ( body )
sequence of terminal symbols . The meaning of the rule is that body is a possible
form for a phrase of type nonterminal . Nonterminal symbols are written as Prolog
Consider the simple context - free grammar for a small subset of English given
in Figure 16 . 1 . The grammar is self - explanatory , the reading of the first rule being :
Parsing with Definite Clause Grammars 257
Grammar Rules
sentence(8) +-
append(NP ,VP ,8), noun_phrase(NP), verb_phrase(VP ).
The vocabulary rules involving terminal symbols can be expressed as facts , for
example ,
sentenceS \ SO) +-
noun _phraseS \ Sl ) , verb _phrase (Sl \ SO) .
noun _phraseS \ SO) +-
determiner (S\ Sl ) , noun_phrase2(Sl \ SO).
noun _phraseS ) +-
noun _phrase2 (S) .
noun _phrase2 ( 8 \ 80 ) +-
noun - phrase2 (S ) +-
noun (S ) .
verb _phraseS ) +-
verb (S ) .
verb _phraseS \ SO ) +-
determiner ( [ a I8 ] \ 8 ) .
noun ( [ surprise IS ) \ S ) .
in Figure 16 . 1
words that forms a sentence according to the rules of the grammar . Similarly
and verb ( S) all are true if their argument , a difference - list S of words , is the part
and lists of terminal symbols . Nonterminal symbols are Prolog atoms , and an
Parsing with Definite Clause Grammars 259
translate ( ( A ,B ) , ( Al , Bl ) ,Xs \ Ys ) t -
translate ( A ,Al ,Xs \ Xsl ) ) translate ( B ,Bl ,Xsl \ Ys ) .
translate ( A )AliS ) t -
non _terminal ( AfunctorAl ,Ail ) , arg ( l ,AliS ).
translate ( Xs , true ,S ) i -
terminals ( Xs ) ,
sequence ( Xs , S ) .
terminals ( [X IXs ] ) .
sequence ( [ ] ,Xs \ Xs ) .
Clause ). The procedure for translate / 2 translates the left - hand side and righthand
side of the grammar rule to the head and body of the equivalent Prolog
clause . The basic idea is to add a difference - list to each nonterminal symbol .
A new version of translate , translate ( Symbol J Goal JXs ) is necessary . The relation
argument . The three clauses for translate / 9 cover the possible terms in a grammar
rule . If the term is a conjunction , each conjunct is recursively translated with the
argument is the difference - list . Sequences of terminal symbols are unified into
the rule ' s difference - list using sequence ( Symbol JXs ) . A post - processor can remove
the excessive true goals , or one can use a difference - structure to prevent their
construction in the first place .
The first extension is constructing a parse tree for the sentence as it is being
parsed . Arguments representing (subparts of ) the parse tree must be added to the
predicates in Program 16.1. The extension is similar to adding structured arguments
to logic programs as discussed in Section 2.2. We modify sentence/ l to be a
binary relation sentence( Tree, Words) where Tree is the parse tree of Words parsed
according to the grammar . The other unary predicates representing phrases and
parts of speech must similarly be changed to binary predicates . We assume that
the parse tree is the first argument .
The first clause of Program 16.1 is extended to be
The compound term sentence( NP , VP ) represents the parse tree , with NP and VP
representing the parsed noun phrase and verb phrase .
This extension can also be reflected in augmenting the grammar rules . An
extra argument can be added to the nonterminal symbols , so that they become
structures . The rule above is represented as
Program 16.2, translating context -free grammars into Prolog programs , can
be extended to translateD O Gs into Prolog . The extension is posed as Exercise
16(iii ) . We write DO Gs then in grammar rule notation , being aware they are
essentially Prolog programs .
The DCGs in Program 16.3 are an extension of Program 16.1 which computes
the parse tree at the same time as parsing a sentence. As a logic program , it is
similar to Program 2.3 which computed a structure in addition to identifying the
circuit components . The program builds the parse tree top -down , exploiting the
power of the logical variable .
The new program would parse "The decorated pieplates contain a surprise ," but
unfortunately would also parse "The decorated pieplates contains a surprise ."
There is no insistence that noun and verb must both be singular , or both be
plural .
Number agreement can be enforced by adding an argument to the parts of
speech that must be the same. The argument indicates whether the part of speech
is singular or plural . Consider the grammar rule
The rule insists that both the noun phrase which is the subject of the sentence
and the verb phrase which is the object of the sentence have the same number ,
singular or plural . The agreement is indicated by the sharing of the variable Num .
Expressing subject / object number agreement is context -dependent information ,
which is clearly beyond the scope of context -free grammars .
Program 16.4 is an extension of Program 16.3 that handles number agreement
262 Parsing with Definite Clause Grammars
Vocabulary
correctly . Noun phrases and verb phrases must have the same number , singular
or plural . Similarly the determiners and nouns in a noun phrase must agree in
number . The vocabulary is extended to indicate which words are singular and
which plural . Where number is unimportant , for example , with adjectives , it can
be ignored , and no extra argument is given . The determiner the can be either
The next example of a DOG uses another Prolog feature , the ability to refer
to arbitrary Prolog goals in the body of a rule . Program 16 . 5 is a grammar for
recognizing numbers written in English up to one thousand . In doing so , the value
of the number recognized is calculated using the arithmetic facilities of Prolog .
The basic relation is number ( N) where Nis the numerical value of the number
being recognized . According to the grammar specified by the program , a number
tens(20) - + [twenty].
tens(30) - + [thirty ].
tens(40) - + [forty ].
tens(50) - + [fifty ].
tens(60) - + [sixty].
tens(70) - + [seventy].
tens(80) - + [eighty].
tens(90) - + [ninety ].
Program 16 .5: A DOG for recognizing numbers
denote the rest of a number of 3 and 2 digits , respectively , after the leading digit
has been removed . The predicates digit , teen and tens recognize , respectively ,
single digits , the numbers ten to nineteen inclusive and the multiples of ten from
twenty to ninety inclusive .
A new syntactic construct is necessary in the grammar to allow arbitrary
Prolog goals. This is done by placing the Prolog goal in curly braces . We illustrate
264 Parsing with Definite Clause Grammars
This says that a three digit number N must first be a digit with value D followed
by the word "hundred" followed by the rest of the number which will have value
Nl . The value for the whole number N is obtained by multiplying D by 100 and
adding Nl .
DCGs inherit another feature from logic programming , the ability to be used
backward . Program 16.5 can be used to generate the written representation of a
given number up to a thousand . In technical terms , the grammar generates as
well as accepts . The behavior in so doing is classic generate- and-test . All the
legal numbers of the grammar are generated one by one and tested if they have
the correct value , until the actual number posed is reached . This feature is a
curiosity rather than an efficient means of writing numbers .
16 . 1 Background
Prolog was connected to parsing right from its very beginning . As mentioned
before , the Prolog language grew out of Colmerauer ' s interest in parsing , and his
Prolog - 10 were also keen on natural language processing , and wrote one of the
more detailed accounts of definite - clause grammars ( Pereira and Warren , 1980 ) .
Parsing with Definite Clause Grammars 265
This paper gives a good discussion of the advantages of DCGs as a parsing formalism
in comparison with A T Ns .
Many Prologs , for example , Wisdom Prolog , provide a hook so that grammar
rules are transformed automatically as the file is consulted .
Even though the control structure of Prolog matches directly that of
recursive -descent , top -down parsers , other parsing algorithms can also be implemented
in it quite easily. For example , Matsumoto et al . ( 1986) describes a
bottom -up parser in Prolog .
The grammar in Program 16.1 is taken from Winograd's book on computa-
tionallinguistics (Winograd , 1983) .
Chapter 17
Second-Order Progralllllling
Solving a Prolog query with a program entails finding an instance of the query
that is implied by the program . What is involved in finding all instances of a query
that are implied by a program ? Declaratively , such a query lies outside the logic
programming model presented in the first chapter . It is a second-order question
since it asks for the set of elements with a certain property . Operationally , it is also
outside the pure Prolog computation model . With pure Prolog all information
about a certain branch of the computation is lost on backtracking . This prevents
a simple way of using pure Prolog to find the set of all solutions to a query , or
even to find how many solutions there are to a given query .
This section discuss es nredicates
~ that enable the answerin --
!2: of second-order
.
querIes . We call such predicates set-predicates . They can be regarded as new
17.1 Set expressions 267
primitives . However , they are not true extensions to Prolog , since they can be defined
in Prolog , using some of its extra - logical features , notably assert and . retract .
all solutions and show later how they can be implemented . As with the standard
their logical specification . But this approximation is very useful for many
We demonstrate the use of set - predicates using part of the Biblical database
children ( X , Cs ) + - - children ( X , [ ] , Cs ) .
children ( X , A , Cs ) + - -
children ( X , Cs , Cs ) .
The program success fully answers a query such as children ( terach , Xs ) ' ? with answer
serious drawbacks , however , that prevent it from being the basis of more general
set - predicates . First , each time a solution is added to the accumulator , the whole
Second , there are problems with generality . A query such as children ( X , Cs ) ' ?
backtracking due to the cut . Once " free " variables are instantiated , no alternative
solution is possible . Removing the cut causes incorrect behavior on the query
children ( terach , X ) .
268 Second-Order Programming 17.1
The two primitive set - predicates are as follows . The relation bag _ of ( Term ,
Goal , Instances ) is true if Instances is the bag ( multiset ) of all instances of Term
for which Goal is true . Multiple identical solutions are retained . The relation
whose instances are being collected . The complete search tree for the goal must
be traversed . Hence , an infinite branch appearing in the search tree causes non -
termination .
Set - predicates can have multiple solutions . Consider the query set _ oj ( Y ,
father ( X , Y) , Kids ) ? There are a number of alternative solutions corresponding
Set - predicates may be nested . For example , all father -children relations
can be computed with the query set _oJ( Father - Kids ,set _oJ( X ,parent (Father ,
X) ;Kids ) , Y s) ? The solution is [terach - [ abraham , nachor ,haran ] , abraham - [ isaac ]
, haran - [ lot , milcah , yiscah ]] .
There are two possible ways to define the behavior of set _of( X , Goal ,Instances )
when Goal fails , i .e., has no true instances . We define set _of and bag_of to always
succeed , returning the empty list as the value of Instances when there are no
solutions to Goal . This definition assumes that the knowledge in the program is
all that is true . It is analogous to the approximation of negation by negation as
17.1 Set expressions 269
Set predicates
set_of! (X ,Goal,Instances) +-
set_of(X ,Goal,Instances), Instances = [Ills ].
bag_oil (X , Goal,Instances) +- Instances is the multiset of
instances of X for which Goal is true , if there are such .
The multiplicity of an element is the number of
different ways Goal can be proved with it as an instance of X .
bag_of! (X ,Goal,Instances) +-
bag_of(X ,Goal,Instances), Instances = [Ills ].
size- oj( X , Goal,N) +- N is number of distinct
instances of X such that Goal is true .
failure .
Versions of set- of and bag_of can be defined to fail when there are no solutions .
We call the new relations set_ofl and bag_ofl and give their definitions in Program
17 . 1 .
Many of the recursive procedures shown before can be rewritten using set-
predicates . For example , Program 7.9 for removing duplicates from a list of
elements can be defined simply in terms of testing for membership :
This definition is significantly less efficient , however , than writing the recursive
procedure directly . It is true in general that recursive programs are more
efficient than using set predicates on current Prolog implementations .
Other second-order utility predicates can be defined using these basic set-
predicates . Counting the number of distinct solutions is possible with a program
size_oJ(X , Goal,N) which determines the number Nofsolutions of Xto a goal Goal..
It is given in Program 17.1. If there are no solutions to Goal, N is instantiated
to O. If the desired behavior of size_of was failure on no solutions , set- ofl can be
270 Second
-Order Programming 17.1
for_all (Goal,Condition ) +-
set - of ( Condition , Goal , Cases ) , check ( Cases ) .
check ( [ ] ) .
reap ( X , Xs \ Ys ) + -
reap ( $ mark , Xs \ Xs ) .
used instead of set_of. A version using bag_of instead of set_of would count the
number of solutions with duplicates .
Another set utility predicate checks whether all solutions to a query satisfy
a certain condition . Program 17.2 defines a predicate for ".all ( Goal, Condition )
succeeding when Condition is true for all values of Goal. It uses the metavariable
facility .
The query for _all (father (X , O) ,male ( O) ) ? checks which fathers have only male
children . It produces two answers X = terach and X = abraham.
A simpler , more efficient but less general version of for _all can be written
without using set-predicates . A combination of nondeterminism and negation by
failure produces a similar effect . The definition is
It successfully answers a query such as for _all (father ( terach,X) ,male (X) ) ? but fails
17.2 Applications of set expressions 271
(i) Define the predicate intersect(Xs, Ys,Zs) using a set expression to compute
the intersection Zs of two lists Xs and Y s. What should happen if the two
lists do not intersect ? Compare the code with the recursive definition of
intersect .
Set expressions are a significant addition to Prolog . Clean solutions are obtained
to many problems by using set expressions , especially when other programming
techniques , discussed in previous chapters , are incorporated . This section
presents three example programs : traversing a graph breadth first , using the Lee
algorithm for finding routes in VLSI circuits , and producing a keyword in context
(KWIC ) index.
Section 14.2 presents three programs , 14.8, 14.9, and 14.10, for traversing a
graph depth first . We discuss here the equivalent programs for traversing a graph
breadth first . .
Consider the edge clauses in Program 17.4, representing the left -hand graph
in Figure 14.3. Using them , the query connected( a,X) ? gives the solutions b, c, d,
e, f , g, j , k, h, i , which is a breadth -first traversal of the graph .
Like Program 14.8, Program 17.4 correctly traverses a finite tree or a' directed
acyclic graph (DAG ) . If there are cycles in the graph , the program will not
terminate . Program 17.5 is an improvement over Program 17.4 where a list of
the nodes visited in the graph is kept . Instead of adding all the successor nodes
at the end of the queue, each is checked to see if it has been visited before . This
is performed by the predicate filter in Program 17.5.
Program 17.5 in fact is more powerful than its depth -first equivalent , Program
14.10. Not only will it correctly traverse any finite graph , it will correctly
traverse infinite graphs as well . It is useful to summarize what extensions to
pure Prolog have been necessary to increase the performance in searching graphs .
Pure Prolog correctly search es finite trees , and DAGs , Adding negation allows
correct searching of finite graphs with cycles , while set expressions are necessary
for infinite graphs . This is shown in Figure 17.2.
Calculating the path between two nodes is a little more awkward than for
depth -first search. It is necessary to keep with each node in the queue a list of
the nodes linking it to the original node . The technique is demonstrated in the
next chapter in Program 18.6.
connected(X , Y) +-
N ode X is connected to node Yin the DAG defined by edge
/ 2.
connected (X ,Y ) +- connected _bfs ( [X IXs ]\ Xs ,Y ) .
connected ( X , Y ) + -
connected ( X , Y ) + - connected ( [X I Xs ] \ Xs ,Y , [X ] ) .
connected ( [A I Xs ] \ Y s , A , Visited ) .
set _ of ( N , edge ( A , N ) , N s ) ,
filter ( ( ] ,V , V , Xs , Xs ) .
cost route between two points in a circuit using the Lee algorithm .
The problem is formulated as follows . Given a grid that may have obstacles ,
find a shortest path between two specified points . Figure 17 . 3 shows a grid with
obstacles . The heavy solid line represents a shortest path between the two points
. . . . . . . .
. . . .
. . .
. .
. . .
. . .
. .
Points in the plane are represented by their Cartesian coordinates and denoted
X - Y. In Figure 17.3, A is 1- 1 and B is 5- 5. This representation is chosen for
readability , and utilizes the definition of "- " as an infix binary operator . Paths are
calculated by the program as a list of points from B to A , including both endpoints .
In Figure 17.3 the route calculated is [5- 5,5- ;,,5- 3,5- 2,;,- 2,3- 2,2- 2,1- 2,1- lJ, and
17.2 Applicationsof set expressions 275
member ( Xl , Wave ) ,
neighbor ( Xl , X ) ,
neighbor ( Xl - Y , X2 - Y ) + - next _ to ( Xl , X2 ) .
neighbor ( X - Yl , X - Y2 ) + - next _ to ( Yl , Y2 ) .
next - to ( X , XI ) + - Xl : = X + I .
next _ to ( X , XI ) + - X > 0 , Xl : = X - I .
test Jee(Name,Path) +-
data(Name,A ,BiObstacles), lee..route(A ,BiObstacles,Path).
data(test,1- 1,5- 5,[obstacle(2- 3,4- 5) ,obstacle(6- 6,8- 8)]) .
Program 17.6 (Continued)
Waves are defined inductively . The initial wave is the list [A]. Successive
waves are sets of points that neighbor a point in the previous wave , and do not
already appear in previous waves. They are illustrated by the lighter solid lines
in Figure 17.3.
The predicate find _path(A,B, Waves,Path) finds the path Path back from B
to A through the Waves generated in the process . Path is built downward which
means the order of the points is from B to A . This order can be changed by using
an accumulatorin find_path.
Program 17.6 produces no output while computing the Lee route . In practice
the user may like to see the computation in progress . This can be easily done by
adding appropriate write statements to the procedures next_wave and find _path .
Our final example in this section concerns the keyword in context problem
(KWIC ). Again a simple Prolog program, combining nondeterministic and second-
order programming , suffices to solve a complex task .
Sample input to a program is given in Figure 17. 4 together with the expected
output . The context is described as a rotation of the title with the end of the title
278 Second-Order Programming 17.2
indicated by a " I " . In the example the keywords are algorithmic , debugging , logic ,
problem , program , programming , prolog and solving , all the ' non trivial ' words .
The relation we want to compute is kwic ( Titles J Kwic Titles ) where Titles is
the list of titles whose keywords are to be extracted , and Kwic Titles is the sorted
list of keywords in their contexts . Both the input and output titles are assumed
would convert freer form input into lists of words , and produce prettier output .
append :
Ys is Bs followed by As . .
keywords . This is done by isolating the word in the first call to append . Note that
This definition also improves the previous attempt by removing the duplicate
solution when one of the split lists is empty , and the other is the entire list .
Suppose each keyword Word is identified by a fact of the form keyword ( Word ) .
The solutions to the rotate procedure can be filtered so that only words identified
Operationally rotate _ and - filter considers all keys , filtering out the unwanted alternatives
insert the end of title mark " I " , providing the context information . This is done by
adding the extra symbol in the second append call . Incorporating this discussion
necessary over all the possible titles . Advantage is derived from the behavior of
17.2 Applications of set expressions 279
set-of in sorting the answers. The complete program is given as Program 17.7,
and is an elegant example of the expressivepower of Prolog. The test predicate
is test_kwic/ 2.
(i) Modify Program 17.6 to handle obstacles specified differently than as rect-
angles.
(ii ) Adapt Program 17.7 for KWIC so that it extracts keywords from lines of
text .
280 Second-Order Programming 17.2
has_property ( Xs ,P) +-
Each element in the list Xs has property P.
has_property ([X I Xs],P) +-
apply(P,X ), has_property (Xs,P).
has_property ( [ ],P).
apply(male,X ) +- male(X ).
maplist(Xs,P, Ys) +-
Each element in the list Xs stands in relation
P to its corresponding element in the list Y s.
The two predicates in Figure 17.5 are transformed into standard Prolog in Program
17.8. Sample definitions of apply clauses are given for the examples mentioned
in the text .
by Program 9.3. In order to handle this correctly the definition of apply must be
extended a little as below :
apply(P,Xs) +-
P = .. Ll , append(Ll ,Xs,L2), Goal = .. L2, Goal.
17 .4 Background
State -space graphs are used to represent problems . Nodes of the graph are
states of the problem . An edge exists between nodes if there is a transition rule ,
also called a move, transforming one state into the next . Solving the problem
means finding a. path from a given initial state to a desired solution state by
applying a sequence of transition rules .
Program 18.1 is a framework for solving problems by searching their state -
space graphs , using depth -first search as described in Section 14.2.
No commitment has been made to the representation of states . The moves are
specified by a binary predicate move(State ,Move ) where Move is a move applicable
to State . The predicate update(State ,Move ,Statel ) finds the state Statel reached
by applying the move Move to state State . It is often easier to combine the
move and update procedures . We keep them separate here to make knowledge
more explicit , and retain flexibility and modularity possibly at the expense of
performance .
The validity of possible moves is checked by the predicate legal(State) which
checks if the problem state State satisfies the constraints of the problem . The
program keeps a history of the states visited to prevent looping . Checking that
looping does not occur is done by seeing if the new state appears in the history
18.1 Searching state -space graphs 285
solve-dfs(State,History ,( )) +-
final ..state(State).
solve_dfs(State,History ,(MoveIMoves)) +-
move(State,Move),
update(State,Move,State1),
legal(Statel ),
not member(Statel ,History ),
solve_dfs(Statel ,(Statel IHistory ),Moves).
Testing the framework
test_dfs(Pro blem,Moves) +-
initial -state(Problem,State), solve_dfs(State,[State] ,Moves).
Program 18.1: A depth-first state-transition framework for
problem solving
of states . The sequence of moves leading from the initial state to the final state
is built incrementally in the third argument of solve_dfs/ 9.
To solve a problem using the framework , the programmer must decide how
states are to be represented , and axiomatize the move, update and legal procedures .
A suitable representation has profound effect on the success of this framework .
Let us use the framework to solve the wolf , goat and cabbage problem . We
state the problem informally . A farmer has a wolf , goat , and cabbage on the left
side of a river . The farmer has a boat that can carry at most one of the three ,
and he must transport this trio to the right bank . The problem is that he dare
not leave the wolf with the goat (wolves love to eat goats ) or the goat with the
cabbage (goats love to eat cabbages) . He takes all his jobs very seriously and does
not want to disturb the ecological balance by losing a passenger .
States are represented by a triple wgc(B ,L ,R) where B is the position of the
boat (left or right ) , L is the list of occupants of the left bank , and R the occupants
of the right bank . The initial and final states are wgc(leJt,[wolJ,goat, cabbage],[ ])
and wgc(right ,[ ], [wolJ, goat, cabbage]) , respectively . In fact , it is not strictly necessary
to keep the occupants from both the left and right banks . The occupants
from the left bank can be deduced from the occupants of the right bank , and vice
versa . Having both makes specifying moves clearer .
286 SearchTechniques 18.1
States for the wolf , goat and cabbage problem are a structure
wgc ( Boat , Left , Right ) , where Boat is the bank where the boat
the river , and Right is the list of occupants of the right bank .
precedes ( X ,Y ) .
insert ( X , [ ] , [X ] ) .
precedes(wolf ,X ) .
precedes (X ,cabbage) .
legal ( wgc ( left , L , R ) ) + - not illegal ( R ) .
It is convenient for checking for loops to keep the lists of occupants sorted .
Thus wolf will always be listed before goat , both of whom will be before cabbage
Moves transport an occupant to the opposite bank , and can thus be specified
18.1 Searching state -space graphs 287
u .0
8litres
5litres
41it
Figure 18.1: The water jugs problem
by the particular occupant who is the Cargo. The case when nothing is taken is
specified by the cargo alone. The nondeterministic behavior of member allows a
concise description of all the possible moves in three clauses: for moving something
from the left bank , from the .right bank , or for the farmer rowing in either direction
by himself , as shown in Program 18.2.
For each of these moves, the updating procedure must be specified , namely
changing the position of the boat (by update_boat/ f ), and updating the banks
(by update_banks). Using the predicate select allows a compact description of
the updating process. The update_banks/ 3 procedure is necessary to keep the
occupant list sorted , facilitating the check if a state has been visited before . It
contains all the possible cases of adding an occupant to a bank .
Finally , the test for legality must be specified . The constraints are simple .
The wolf and goat cannot be on the same bank without the farmer , nor can the
goat and cabbage .
Program 18.2 collects together the facts and rules needed to solve the wolf ,
goat cabbage problem in addition to Program 18.1. The clarity of the program
speaks for itself .
We use the state -transition framework for solving another classic search problem
from recreational mathematics - the water jugs problem . There are two jugs
of capacity 8 and 5 liters with no markings , and the problem is to measure out
exactly 4 liters from a vat containing 20 liters (or some other large number). The
possible operations are filling up a jug from the vat , emptying a jug into the vat ,
and transferring the contents of one jug to another until either the pouring jug is
emptied completely , or the other jug is filled to capacity . The problem is depicted
in Figure 18.1.
The problem can be generalized to Njugs of capacity Cl ," ., CN . The problem
is to measure a volume V, different from all the ai 'S but less than the largest .
288 Search Techniques 18.1
capacity ( I , OI ) ,
Liquid : = VI + V2 ,
Excess : = Liquid - 01 ,
capacity ( 2 , 02 ) ,
Liquid : = VI + V2 ,
Excess : = Liquid - 02 ,
capacity ( I , 8 ) .
capacity ( 2 , 5 ) .
The particular problem we solve is for two jugs of arbitrary capacity , but
the approach is immediately generalizable to any number of jugs . The program
assumes two facts in the database , capacity ( I , OJ) , for I equals 1 and 2. The
natural representation of the state is a structure J.ugs( Vl , V2) where Vl and V2
18.1 Searching state -space graphs 289
represent the volumes of liquid currently in the two jugs , The initial state is
juys (O, O) and the desired fInal state either J'uys(O,X) or juys (X ,O) , where Xis the
desired volume , Assuming that the first jug has larger capacity , only one final
state juys (X ,O) need be specified , since it is easy to transfer the required amount
from the second jug to the first , by emptying the first jug , then transferring to it
the contents of the second jug ,
The data for solving the jugs problem in conjunction with Program 18.1 are
given as Program 18.3. There are six moves: filling each jug , emptying each jug ,
and transferring the contents of one jug to another . A sample fact for filling the
first jug is move(jugs ( Vl , V2) ,jill (1)) . The jugs ' state is given explicitly to allow
the data to co-exist with other problem solving data such as in Program 18.2.
The emptying moves are optimized to prevent emptying an already empty jug .
The updating procedure associated with the first four moves is simple , while the
transferring operation has two cases. If the total volume in the jugs is less than
the capacity of the jug being filled , the pouring jug will be emptied and the other
jug will have the entire volume . Otherwise the other jug will be filled to capacity
while the difference between the total liquid volume and the capacity of the filled
jug will be left in the pouring jug . This is achieved by the predicate adjust / 4.
Note that the test for legality is trivial since all reachable states are legal .
Most interesting problems have too large a search space to be searched ex-
haustively by a program such as 18.1. One possibility for improvement is to put
more knowledge into the moves allowed . Solutions to the jug problem can be
found by filling one of the jugs whenever possible , emptying the other whenever
possible , and otherwise transferring the contents of the jug being filled to the jug
being emptied . Thus instead of six moves only three need be specified , and the
search will be more direct , because only one move will be applicable to any given
state . This may not give an optimal solution if the wrong jug to be constantly
filled is chosen.
Developing this point further , the three moves can be coalesced into ahigher -
level move , fill _and_transfer . This tactic ffils one jug and transfers all its contents
to the other jug , emptying the other jug as necessary. The code for transferring
from the bigger to the smaller jug is
Using this program only three fill and transfer operations from one jug to
the other are necessary to solve the problem in Figure 18.1.
We show two search techniques that use an evaluation function explicitly : hill
climbing and best -first search. In the following , the predicate value(State , Value)
is an evaluation function . The techniques are described abstractly .
Hill climbing is a generalization of depth -first search where the successor position
with the highest score is chosen rather than the leftmost one chosen by
Prolog . No change is necessary to the top -level framework of Program 18.1. The
hill -climbing move generates all the states that can be reached from the current
one in a single move using set_of, and then orders them in c;iecreasing order with
respect to the values computed by the evaluation function . The predicate evaluate
_and_order (Moves ,State ,MVs ) determines the relation that MVs is an ordered
list of move-value tupies corresponding to the list of moves Moves from a state
State . The overall program is given as Program 18. 4.
solve.l1ill_climb(State,History ,[ ]) +-
final-8tate(State).
solve.l1ill_climb(State,History ,[MoveI Moves)) +-
hill _climb (State,Move),
update(State,Move,State1),
legal(Statel ),
not member(Statel ,History ),
solve.l1ill_climb(Statel , [Statel IHistory ),Moves).
hill _climb(State,Move) +-
set_of(M ,move(State,M ),Moves),
evaluate_and_order(Moves,State,[ ] ,MV s),
member((Move,Value),MVs).
evaluate_and_order(Moves,State,SoFar, OrderedM Vs) fAll
the Moves from the current State
are evaluated and ordered as Ordered Moves .
SoFar is an accumulator for partial computations .
Hill climbing is a good technique when there is only one hill and the evaluation
function is a good indication of progress . Essentially , it takes a local look at the
state space graph , making the decision on where next to search on the basis of
the current state alone .
At each stage of the search there is a set of moves to consider rather than
a single one . The plural predicate names , for example , updates and legals , indicate
this . Thus legals ( States , Statesl ) filters a set of successor states checking
which ones are allowed by the constraints of the problem . One disadvantage of
breadth - first search ( and hence best -first search ) is that the path to take is not
as conveniently calculated . Each state must store explicitly with it the path used
to reach it . This is reflected in the code .
solve_best(Frontier ,History,Moves) +-
Moves is the sequence of moves to reach a desired final state from
the initial state , where Frontier contains the current states under
consideration , and History contains the states visited previously .
news([(S,P)IStates],History ,Statesl ) +-
memberS ,History ), news(States,History ,Statesl ).
news([(S,P)IStates],HistoryS ,P) IStatesl ]) +-
not memberS ,History ), news(States,History ,Statesl ).
news([ ] ,History ,[ ]).
Program 18 .6 : Best -first problem -solving framework
294 SearchTechniques 18.1
evaluates ( [ ],[ ]) .
Program 18 .6 ( Continued )
(i) Redo the water jugs program based on the two fill - aIld _transfer operations .
Three missionaries and three cannibals are standing at the left bank of a
river . There is a small boat to ferry them across with enough room for only
one or two people . They wish to cross the river . If ever there are more
will convert the cannibals . Find a series of ferryings to transport safely all
the missionaries and cannibals across the river without exposing any of the
cannibals to conversion .
solve - best ( [ state ( State , Path , Value ) I Frontier ] , History , Final Path ) + -
legal ( Statel ) ,
water , and had to escape from their unpleasant position in a boat that would
only hold three persons at a time . Every husband was so jealous that he
would not allow his wife to be in the boat or on either bank with another
man ( or with other men ) unless he was himself present . Find a way of getting
( v ) Express the eight queens puzzle within the framework . Find an evaluation
function .
What happens when we playa game ? Starting the game means setting up
the chess pieces , dealing out the cards , or setting out the matches , for example .
Once it is decided who plays first , the players take it in turns to make a move .
296 SearchTechniques 18.2
The predicate initialize ( Game , Position , Player ) determines the initial game position
a move , the move being executed , and the next player being determined . The
neatest way of specifying this is as a tail recursive procedure , play , with three
arguments : a game position , a player to move and the final result . It is convenient
to separate the choice of the move by choose - move / Sfrom its execution by move / So
The remaining predicates in the clause below display the state of the game , and
Program 18.8 provides a logical framework for game- playing programs . Using
it for writing a program for a particular game focuses attention on the important
issues for game-playing : what data structures should be used to represent the
game position , and how strategies for the game should be expressed . We demonstrate
the process in Chapter 20 by writing programs to play Nim and Kalab .
The problem -solving frameworks of the previous section are readily adapted
to playing games. Given a particular game state , the problem is to find a path of
moves to a winning position .
A game tree is similar to a state -space graph . It is the tree obtained by
identifying states with nodes and edges with player 's moves. We do not , however ,
identify nodes on the tree , obtained by different sequence of moves, even if they
repeat the same state . In a game tree , each layer is called a ply .
Most game trees are far too large to be searched exhaustively . This section
discuss es the techniques that have been developed to cope with the large search
18.2 Searching game trees 297
play( Game) +-
Play game with name Game.
play(Game) ~
initialize (Game,Position,Player),
display_game(Position,Player),
play (Position,Player,Result).
play (Position ,Player ,Result ) +-
game_over (Position ,Player ,Result ) , !, announce (Result ) .
play (Position ,Player ,Result ) +-
chooseJD. ove(Position ,Player ,Move ) ,
move (Move ,Position ,Positionl ) ,
display _game (Positionl ,Player ) ,
next _player (Player ,Playerl ) ,
!, play (Positionl ,Playerl ,Result ) .
Program 18 .8 : Framework for playing games
space for two -person games. In particular we concentrate on the minimax algorithm
augmented by alpha -beta pruning . This strategy is used as the basis of a
program we present for playing Kalah in Chapter 20.
We describe the basic approach of searching game trees using evaluation functions
. Again in this section value(Position , Value) denotes an evaluation function
computing the Value of Position , the current state of the game. Here is a simple
298 Search Techniques 18.2
The predicate move(Position ,Move ) is true if Move is a possible move from the
current position .
The basic relation is evaluate_and_choose(Moves ,Position ,Record,Move )
which chooses the best move Move in the possible Moves from a given Position
. For each of the possible moves, the corresponding position is determined ,
its value calculated and the move with the highest value chosen. Record is a
record of the current best move so far . In Program 18.9 it is represented as a
. tuple (Move , Value) . The structure of Record has been partially abstracted in the
procedure update/ 4. How much data abstraction to use is a matter of style and
tradeoff between readability , conciseness, and performance .
Looking ahead one move , the approach of Program 18.9, would be sufficient
if the evaluation function were perfect , that is if the score would reflect which
positions led to a win and which to a loss. Games become interesting when
a perfect evaluation function is not known . Choosing a move on the basis of
looking al1ead one move is generally not a good strategy . It is better to look
several moves ahead, and infer from what is found the best move to make .
The minimax algorithm is the standard method for determining the value of
a position based on searching the game tree several ply ahead . The idea is as
follows .
The algorithm assumes that , when confronted with several choices, the opponent
would make the best choice for him , i .e., the worst choice for me. My goal
then is to make the move that maximizes for me the value of the position after
the opponent will make his best move , i .e., minimizes the value for him . Hence
the name minimax . This reasoning proceeds several ply ahead , depending on
the resources that can be allocated to the search. At the last ply the evaluation
function is used.
18.2 Searching game trees 299
Player
0 pponent
Player
Program 18.10 encodes the minimax algorithm . The basic relation is minimaxD
,Position ,Max MiniMove , Value) which is true if Move is the move with
the highest Value from Position obtained by searching D ply in the game tree .
Max Min is a flag which indicates if we are maximizing or minimizing . It is 1
for maximizing and - 1 for minimizing . A generalization of Program 18.9 is used
to choose from the set of moves. Two extra arguments must be added to evaluate
_and_chooseD the number of ply and Max Min the flag . The last argument
is generalized to return a record including both a move and a value rather than
just a move . The minimax procedure does the bookkeeping , changing the number
of moves being looked ahead , and also the minimax flag . The initial record is
( nil , - 1000) , where nil represents an arbitrary move and - 1000 is a score less than
any possible score of the evaluation function .
300 Search Techniques 18.2
The observation about efficiency that was made about combining the move
generation and update procedures in the context of searching state -space graphs
has an analogue when searching game trees . Whether it is better to compute the
set of positions rather than the set of moves (with the corresponding change in
algorithm ) will depend on the particular application .
The minimax algorithm can be improved by keeping track of the results of
the search so far , using a technique 'known as alpha -beta pruning . The idea is to
keep for each node the estimated minimum value found so far , the alpha value ,
along with the estimated maximum value , beta . If , on evaluating a node , beta is
exceeded, no more search on that branch is necessary. In good cases more than
half of the positions in the game tree need not be evaluated .
+ -
Chooses the Best Move from the set of Moves from the current
Position using the minimax algorithm with alpha -beta cutoff searching
Depth ply ahead . Alpha and Beta are the parameters of the algorithm .
Movel records the current best move .
Beta,Move, Value) which extends minimax by replacing the minim axing flag with
alpha and beta . The same relationship holds with respect to evaluate_and_choose.
For example , the last node in the game tree in Figure 18.2 does not need to
be searched . Once a move with value - 1 is -found , which is less than the value of
302 Search Techniques 18.2
+ 1 the player is guaranteed , no other nodes can contribute to the final score .
The program can be generalized by replacing the base case of alpha _ beta by
a test whether the position is terminal . This is necessary in chess programs , for
18 . 3 Background
Search techniques for both planning and game playing are discussed in AI
textbooks . For further details of search strategies or the minimax algorithm and
its extension to alpha - beta pruning , see , for example , Nilsson ( 1971 ) or Winston
( 1977 ) .
Meta - programs treat other programs as data . They analyze , transform , and
particularly easy in Prolog due to the equivalence of programs and data : both being
in Program 16 . 2 .
shells are developed in the second section . The third section discuss es program
19 . 1 Simple metainterpreters
relation scheme . The relation solve ( GoaO is true if Goal is true with respect to
the program being interpreted . We use solve / l throughout this section to denote
a meta - interpreter .
304 Meta-Interpreters 19.1
solve( Goal) ~
Goal is deducible from the pure Prolog program
defined by clause/ 2.
solve(true ) .
solve((A ,B)) +- solve(A ), solve(B) .
solve(A ) +- clause(A ,B) , solve(B).
Program 19.1: A meta-interpreter for pure Prolog
The simplest metainterpreter that can be written in Prolog uses the metavariable
facility , namely
solve(A ) +- A .
Its usefulness can be seen in Programs 12.6 and 12.7 where it forms the ba.gis
for a shell process and logging facility written in Prolog .
A more interesting metainterpreter simulates the computational model of
logic programs . Goal reduction for pure Prolog programs can be described by the
three clauses comprising Program 19.1. This meta -interpreter and its extensions
form the basis for this chapter .
Declaratively , the interpreter reads as follows . The constant true is true . The
conjunction (A ,B) is true if A is true and B is true . A goal A is true if there is a
clause A ~ B in the interpreted program such that B is true .
We give also a procedural reading of the three clauses in Program 19.1. The
solve fact states that the empty goal , represented in Prolog by the atom true ,
is solved . The next clause concerns conjunctive goals . It reads : "To solve a
conjunction (A ,B) , solve A and solve B ." The general case of goal reduction is
covered by the final clause. To solve a goal , choose a clause from the program
whose head unifies with the goal , and recursively solve the body of the clause.
The procedural reading of Prolog clauses is necessary to demonstrate that the
metainterpreter of Program 19.1 indeed reflects Prolog 's choices of implementing
the abstract computation model of logic programming . The two choices are the
selection of the leftmost goal as the goal to reduce , and sequential search and
backtracking for the nondeterministic choice of the clause to use to reduce the goal .
The goal order of the body of the solve clause handling conjunctions guarantees
that the leftmost goal in the conjunction is solved first . Sequential search and
backtracking comes from Prolog 's behavior in satisfying the clause goal .
The hard work of the interpreter is borne by the third clause of Program
19.1. The call to clause performs the unification with the heads of the clauses
19.1 Simple meta-interpreters 305
solve ( member ( X , [ a , b , c ) ) )
true Output : X = a
solve ( true )
clause ( true , T ) f
true Output :
X=b
,
solve ( true )
clause ( true , T ) f
true Output :
x =c
,
solve(true )
clause(true ,T ) f
clause(member(X ,[c] ,B2)) {B2= member(X,[ ])}
solve(member(X ,[ ]))
clause(member(X ,[ ]),B3)
No (more) solutions
A simple application constructs a proof tree as it solves the goal . The technique
is similar to constructing a parse tree for a grammar in Program 16 .3 , and
adding structured arguments to logic programs discussed in Section 2 .2 . The
proof tree is useful for expla Ilation facilities for expert systems as is discussed in
the next section .
The basic relation is solve( Goal, Tree) where Tree is a proof tree for solving the
goal Goal. Proof trees are represented by the structure Goalt - Proof where Proof is
a conjunction of the branch es proving Goal. Program 19.2 implementing solve/ 2
is a straightforward extension of Program 19.1. The three clauses correspond
exactly to the three clauses of the metainterpreter for pure Prolog .
The solve fact states that the empty goal is true with a trivial proof tree ,
represented by the atom true . The second clause states that the proof tree of a
conjunctive goal (A ,B) is a conjunction of the proof trees for A and B . The final
solve clause builds a proof tree A +- Prooffor the goal A , where Proof is recursively
built by solving the body of the clause used to reduce A .
We give an example of using Program 19.2 with the pure logic program ,
Program 1.2. The query solve(son(lot ,haran ) ,ProoJ) ? has the solution
-
system A := B). system(A < B).
system read(X)). system(write (X )) .
system integer(X)) . system(functor (T ,F ,N)).
system clause(A,B)). system(system(X )).
The query solve ( son ( X , haran ) , Prooj ) ? has the solution X = lot and the same value
for Proof .
outside pure Prolog . The various system predicates are not defined by
clauses in the program and thus need different treatment . The easiest way to incorporate
A table stating which predicates are system ones is necessary . We assume the table
consists of facts of the form system ( Predicate ) for each system predicate . Figure
19 . 2 gives part of that table . The clause in the metainterpreter handling system
predicates is
solve (A ) +- system (A ) , A .
The extra solve clause makes the behavior of the system predicates invisible
behavior one wants to be invisible . Conversely , there are some system provided
predicates which should be made visible . Examples are predicates for negation
and second - order programming . These are best handled by having a special clause
Simulating the behavior of the cut correctly is a problem with this metainterpreter
. The naive solution is to consider cut as a system predicate . Effectively
this means adding the clause
solve(!) +- !.
This clause does not have the required effect . The cut in the clause guarantees
commitment to the current solve clause rather than affecting the search tree of
which the cut is a part . In other words , the scope of the cut is too local .
A neater solution uses a system predicate known as ancestor cut . Ancestor
cut is provided in Wisdom Prolog and Waterloo Prolog , but not in Edinburgh
Prolog . The general form is !(Ancestor ) , where Ancestor refers to an ancestor of
308 Meta -Interpreters 19.1
solve( Goa~ +-
Goalis deduciblefrom the Prologprogram
definedby clause
/ i!.
solve(true ) .
solve( (A ,B )) +- solve (A ) , solve (B ) .
solve(l) +- !(reduce (A ) ) .
solve(not A ) +- not solve (A ) .
solve(.set_of(X,
. Goal ,Xs))
. . i - set _of(X,solve
(Goal
),Xs).
solve(A ) +- system (A ) , A .
solve(A ) +- reduce (A ) .
reduce (A ) +- clause(A ,B ) , solve (B ) .
Program 19 .3 : A meta -interpreter for full Prolog
the current goal . If Ancestor is a positive integer , n say, the nth ancestor of the
current goal is considered . Counting is done upward from the current goal , so
the first ancestor is the parent goal , the second ancestor is the grandparent goal ,
etc . If Ancestor is a noninteger term , the first ancestor unifying with Ancestor is
considered . In either case all siblings of the specified ancestor are pruned from
the search tree in the same manner as if a cut had been applied directly to the
ancestor goal .
To handle cut correctly using ancestor cut , a separate predicate from solve
is necessary ; reduce( GoaQ is used here . The correct scope for cut is obtained by
allowing the metainterpreter to cut the previous reduce goal with the ancestor
cut .
All the observations and improvements of the above discussion are incorporated
into a metainterpreter for Prolog in Program 19.3.
The meta -interpreter in Program 19.3 can be made more efficient by adding
cuts . The choice of the appropriate clause in the collection of solve clauses is
deterministic . Once the correct cIa.use has been identified it can be committed to .
Clauses can be added for the extensions to pure Prolog in Program 19.2, the
meta -interpreter building proof trees . System goals are handled with the clause
version , Program 19.4, handles only success branch es of the computations in pure
Prolog , and does not display failure nodes in the search tree . It is capable of
generating the trace in Figure 6.2 of the query append(Xs, Ys,[a,b,c]). The second
version , Program 19.5, handles system predicates , and more importantly failure
nodesin the searchtree. It is capableof generatingthe tracesof Figures6.1 and
6.3 of the queries son(X ,haran) and quicksort([2,1,3],Xs), respectively.
The basic predicate is trace(Goal,Depth) where Goal is solved at some Depth.
The starting depth is assumed to be O. The three clauses in Program 19.4 correspond
to the three clauses of Program 19.1. The first two clauses state that
the empty goal is 'solved at any depth , and the depth of solving each conjunct in
a conjunction is the same. The final clause matches the goal with the head of a
program clause, displays the goal , increments the depth and solves the body of
the program clause at the new depth .
The predicate displafj( Goal,Depth) , which displays a Goal at a given Depth, is
an interface for printing the traced goal . The depth in the proof tree is indicated
by depth of indentation . The definition of display uses an indentation which is
some multiple of the depth in the tree .
There is some subtlety in the goal order of the clause
trace(Goal,Depth) ~
clause(A ,B),
display(A ,Depth),
Depthl := Depth + l ,
trace(B,Depthl ) .
The display goal is between the clause goal and the trace goal . This ensures
that the goal is displayed each time Prolog backtracks to clause. If the order of
the clause and display goals are swapped , only the initial call of the goal A is
displayed ~
Using Program 19.4 for the query trace( append(Xs, Ys,[a,b,c])) with Program
3.15 for append generates a trace as presented in Section 6.1. The output messages
and semicolons for alternative solutions are provided by the underlying Prolog .
There is only one difference from the trace in Figure 6.2. The unifications are
already performed .
Program 19. 4 can be extended in order to trace the failure of goals. In
order to print failed goals, we must rely on the operational behavior of Prolog , in
particular the use of clause order . Cuts are added to the first two clauses, and an
extra clause is added at the end of the program :
310 Meta-Interpreters 19.1
trace ( GoaQ
trace ( Goal ) + -
trace ( Goal , O ) .
trace ( ( A , B ) , Depth ) + -
trace ( A , Depth ) + -
clause ( A , B ) ,
display ( A , Depth ) ,
Depth1 : = Depth + 1 ,
trace ( B , Depth1 ) .
display ( A , Depth ) + -
trace( Goa~ +-
Goal is deducible from the Prolog program defined by
clause / 2 . The program traces the proof by side effects .
clause ( A ,B ) ,
display ( A , Depth ) , nI ,
Depth ! := Depth + 1,
trace ( B ,Depthl ).
trace ( A ,Depth ) +-
not clause(A ,B), display(A ,Depth), tab (8), write (f ), ill , fail .
display(A ,Depth)
Spacing := 3* Depth , tab (Spacing ) , write (A ) .
Program 19 .5 : A tracer for Prolog
19.2 Enhancedmeta-interpretersfor expert systems 311
trace(A,Depth
)
notclause
(A,B), display
(A,Depth
), tab(lO), write(f), nl, fail.
Note that display is modified not to start on a new line , so that the failure message
is consistent with the traces in Chapter 6. An extra new line command is necessary
to the clauses that call display .
System goals are handled by the clause
The call to display is after the goal has succeeded, to correctly use the last rule if
the goal should fail . The cut is necessary to distinguish from the last clause . The
new program is given as Program 19.5. It can give the trace of Figure 6.3, and
also that of Figure 19.1.
program .
( iii ) Write an interactive tracer which prompts the user for a response before each
goal reduction .
inference engine is not entirely appropriate for expert systems written in Prolog .
executable . However Prolog does not provide important features expected of expert
three features of expert systems : interaction between the user and the program ,
We demonstrate the explanation facility and interactive shell using the toy
expert system in Program 19 . 6 . The program can decide where to place a dish in
312 Meta -Interpreters 19.2
solve ( Goa ~
solve ( true ) .
solve ( ( A ,B ) ) + -
solve ( A ) , solve ( B ) .
solve ( A ) + -
clause ( A ,B ) , solve ( B ) .
solve ( A ) + -
ask(A ,Answer ) +-
display -query (A ) , read (Answer ) .
respond (yes,A ) -+-
assert (A ) .
respond (no ,A ) -+-
assert (untrue (A ) ) , fajI .
known (A ) +- A .
known (A ) +- untrue (A ) .
display _query (A ) ~ write (A ) , write ('? ').
Program 19 .7 : An interactive shell
19.2 Enhanced meta -interpreters for expert systems 313
the oven for baking . Comments on the suitability of Prolog for building expert
Program 19 . 7 is an interactive shell which can query the user for missing
when goals that the interpreter fails to prove by itself can be delegated to the
user . To implement this facility , the following clause is added to the end of the
metainterpreter of Program 19 . 1 :
solve ( A ) + -
for asking from the user . For example , the fact askable ( type ( Dish , Type ) ) indicates
To avoid asking the same question repeatedly , the program records the answer
to the query . This is handled by the predicate respond / ! : ! . If the answer to the
query A is yes , a fact A is asserted into the program . If the answer is no , the
An improved version of the shell allows the interaction to go the other way
as well . When asked a question , the user can respond with a question of her own .
We consider how the shell should respond to the user ' s question " why . "
The obvious reply of the shell is the rule whose conclusion the program is
trying to establish .
This can be easily incorporated into the shell by extending all the relationships
with an extra argument , the current rule being used . The rule must be
state of the computation in Prolog programs . The solve goal must be extended
appropriately as discussed below . The interface to the explanation of " why "
queries is then
display . . xule ( Rule ) , ask ( Goal , Answer ) , respond ( Answer , Goal , Rule ) .
This version of respond writes out the current parent rule , then prompts the
user to answer the query once more . The format in which the rule will appear is
determined by display _rule , a modular extension of the shell to allow the user to
represent rules in her favorite way. .
Repeated responses of why using the above clause for respond result in repeated
restatement of the parent rule . A better solution is to give the "grandpar -
314 Meta-Interpreters 19.2
ent " rule in response to the second why, the "great grandparent " rule in response
to the next why, and so on all the way up the search tree . To achieve this behavior
, the code is modified so that the argument containing the rule contains
instead the list of ancestor rules . The new version of respond is
Repeated requests of why then give the ancestor rules in turn . An extra clause is
needed to cover the case when there are no more rules to explain .
The complete interactive shell incorporating why explanations is given as
Program 19.8. A trace using the program is given in Figure 19.3. User responses
are in italics . We explain the remaining predicates in that program .
The second argument of solve( Goal,Rules) , used in Program 19.8, is a list
of the rules used to reduce the ancestor nodes of Goal in the current proof tree .
The list of rules is updated by the solve clause performing goal reduction . The
19.2 Enhanced meta -interpreters for expert systems 315
solve( Goa~ +-
Goal is deducible from the pure Prolog program
defined by clause/ 2.
The user is prompted for missing information ,
and can ask for a "why " explanation .
solve(Goal) t - solve(Goal,[ ]).
solve(true ,Rules).
solve((A ,B),Rules) -+-
solve(A ,Rules), solve(B ,Rules).
solve(A ,Rules) +- .
clause(A ,B), solve(B ,[rule (A ,B) IRules]).
solve(A ,Rules) +-
askable(A ), not known(A ), ask(A ,Answer), respond(Answer,A ,Rules).
ask(A ,Answer) +-
display_query(A ), read(Answer).
respond (yes,A ,Rules ) i --
assert (A ) .
respond ( no ,A ,Rules ) i --
assert (untrue (A ) ) , fail .
respond (why ,A , [Rule IRules ]) i --
display ..rule(Rule ) ,
ask ( A ,Answer ) ,
respond (Answer ,A ,Rules ) .
respond (why ,A , [ ]) i --
writeln ( ('No more explanation possible ']) ,
ask ( A , Answer ) ,
known ( A ) + - A .
known ( A ) + - untrue ( A ) .
display _ query ( A ) + -
write _ conjunction ( ( A , B ) ) + -
write _ conjunction ( A ) + -
write ( A ) , ill .
how( Goa~ +-
Explains how the goal Goal was proved .
how(Goal) +-
solve ( Goal , Proof ) , interpret ( Proof ) .
interpret ( Proof ) + -
interpret ( Proof ) + - ~
interpret ( Proofl ) ~
representation chosen for rules is a structure rule (A ,B) . The only other predicate
which is affected by this choice of representation is display _rule .
Answering a "why " query is a simple explanation facility where a single , local
chain of reasoning is reported . Our next example is a more interesting explanation
facility that explains the complete proof of a solved query .
The basic idea is interpreting a proof of a goal , where a proof has been collected
in a metainterpreter as shown in Program 19.2. A query how( GoaQ'I,
asking how a goal is proved , is handled by executing the metainterpreter on the
goal and interpreting the resulting proof , A simple program for "how " explanations
is given as Program 19,9.
Figure 19,4 gives a trace of using Program 19,9 to explain the goal
19.2 Enhanced meta - interpreters for expert systems 317
how ( place _in _oven ( dishi , top ) ) ?, using the facts type ( dishi , bread ) and size ( dishi ,
smal ~ .
The explanation in Figure 19 .4 is very clear , but there are hidden shortcomings
. One is the exhaustive nature of the explanation . For any but the smallest
knowledge base , too much output is produced . The screenfuls of text produced
for an expert system with hundreds of rules are not intelligible . A practical mod -
ification to Program 19 .9 is to restrict the explanation to one level at a time and
allow the user to ask for more if necessary . A modified explanation appears as
Figure 19 .5 .
Suppose the explanation is geared toward an expert baker who knows the
classification of dishes : that is , what is a pastry , etc . Although the program must
still do the reasoning establishing that a dish is a pastry by being a cake , etc . , it
is of no interest or relevance to the baker . This can be handled by a . special clause
for interpret , which assumes a predicate classification ( Goa ~ for goals which have
to do with classification :
solve ( true , l ) .
solve ( ( A , B ) , C ) + -
solve ( A , C ) + -
clause _ cf ( A , B , Cl ) , solve ( B , C2 ) , C : = Cl * C2 .
solve ( true , 1 , T ) .
solve ( ( A , B ) , O , T ) + -
solve ( A , O , T ) + -
claus ~ _ cf ( A , B , 01 ) ,
01 > T ,
T1 : = T / 01 ,
solve ( B , 02 , T1 ) ,
0 : = 01 * 02 .
viewpiont of the user of the expert system . More generally whole Prolog routines
that implement algorithms need not be explained , and a more concise report of
The final example of using a metainterpreter for an expert system is the incorporation
and for computing the uncertainty of conclusions . The main requirement is that
in the limiting case , when all rules are certain , the behavior of the system will
Improve display _rule in Program 19.7 to generate English text , rather than
Prolog terms .
Extend the known predicate in Programs 19.7 and 19.8 to handle functional
concepts .
(iii ) Extend Program 19.8 to handle responsesother than yes, no, and why.
(iv ) Modify Program 19.9 so that it produces the trace in Figure 19.5.
(v) Combine the interactive and uncertainty reasoning shells to prompt the user
for the uncertainty of facts and rules . Record the values so they can be used
.
agaIn .
320 Meta -Interpreters 19.2
To debug a program , we must assume that the programmer has some intended
behavior of the program in mind , and an intended domain of application on which
the program should exhibit this behavior . Given those , debugging consists of finding
discrepancies between the program 's actual behavior and the programmer 's
intended behavior . Recall the definitions of an intended meaning and a domain
from Section 5.2. An intended meaning M of a pure Prolog program is the set of
ground goals on which the program should succeed. The intended domainD of
a program is a domain on which the program should terminate . We require the
intended meaning of a program to be a subset of the intended domain .
We say that Al is a solution to a goal A if the program returns on a goal A
its instance AI . We say that a solution A is true in an intended meaning M if
every instance of A is in M . Otherwise it is false in M .
A pure Prolog program can exhibit only three types of bugs , given an intended
meaning and an intended domain . When invoked on a goal A in the intended
domain , the program may do one of three things :
a. Fail to terminate .
b . Return some false solution A ().
c. Fail to return some true solution A ().
solve ( A , O , overflow ( [ ] ) ) .
solve ( ( A , B ) , D , Overflow ) + -
D > 0 ,
solve ( A , D , Overflow A ) ,
D > 0 ,
clause ( A , B ) ,
Dl : = D - l ,
solve ( B , D 1 , Overflow B ) ,
solve(AiD ,no_overflow) +-
D > 0,
system (A ) , A .
solve_conjunction ( overflow (S) ,BiD ,overflow (S) ) .
solve_conjunction (no _overflow ,BiD ,Overflow ) +-
solve (BiD ,Overflow ) .
return _overflow (no _overflow ,A ,no _overflow ) .
return _overflow ( overflow (S) ,A ,overflow ([AIS ]) ) .
Program 19 .12 : A meta -interpreter detecting a stack overflow
meta -interpreter shown in Program 19.12 achieves this . It is invoked with a call
solve( Goal,D , Overflow ) , where Goal is an initial goal , and D an upper bound on
the depth of recursion . The call succeeds if a solution is found without exceeding
the predefined depth of recursion , with Overflow instantiated to no- overflow .
The call also succeeds if the depth of recursion is exceeded, but in this case Overflow
contains the stack of goals , i .e., the branch of the computation tree , which
exceeded the depth -bound D .
isort(Xs, Ys) +-
Ys is an ordered permutation of Xs . Nontermination program .
When called with the goal solve ( isort ( [ . 2, . 2] , Xs ) , 6 , Overjlow ) , the solution returned
is
) Cs == [2 , 2 ,2 ,2 , 2 , 2 ] ,
Overflow = = overflow ( [
isort ( [2 , 2 ] , [2 , 2 ,2 , 2 , 2 , 2 ] ) ,
insert ( 2 , [2 ] , [2 ,2 ,2 , 2 , 2 ,2 ] ) ,
insert ( 2 , [2 ] , [2 ,2 , 2 , 2 , 2 ] ) ,
insert ( 2 , [2 ] , [2 ,2 , 2 , 2 ] ) ,
insert ( 2 , [2 ] , [2 , 2 , 2 ] ) ,
insert ( 2 , [2 ] , [2 ,2 ] ) ] )
The overflown stack can be further analyzed , upon return , to diagnose the
the same input , or by a sequence of goals that calls each goal with increasingly
large inputs . The first situation occurs in the example above . It is clearly a bug ,
that should be fixed in the program . The second situation is not necessarily a bug ,
and knowing whether the program should be fixed , or a larger machine should be
The second type of bug is returning a false solution . A program can return
a false solution only if it has a false clause . A clause G is false with respect to an
The correctness of this algorithm follows from a simple inductive proof . The
algorithm is embedded in an enhanced metainterpreter , shown .as Program 19.15.
The algorithm , and its implementation , assume an oracle that can answer
queries concerning the intended meaning of the program . The oracle is some entity
external to the diagnosis algorithm . It can be the programmer , who can respond
to queries concerning the intended meaning of his program , or another program ,
which has been shown to have the same meaning as the intended meaning of
the program under debugging . The second situation may occur in developing a
new version of a program , and using the older version as an oracle . It can also
occur when developing an efficient program (e.g., quicksort ) ; given an inefficient
executable specification of it (i .e., permutation sort ) , and using the specification
as an oracle .
324 Meta -Interpreters 19.3
false-Bolution (A ,Clause ) +-
solve (A ,Proof ) ,
false_clause (Proof ,Clause ) .
false_clause (true ,ok) .
false_clause ( (A ,B ) ,Clause) +-
false-clause (A ,Clause A ) ,
check-conjunction (Clause A ,B ,Clause ) .
false_clause ((A +- B ) ,Clause) +-
false_clause(B ,Clause B ) ,
check_clause ( Clause B ,A ,B ,Clause ) .
check_conjunction ( ok ,B ,Clause ) +-
false_clause (BiClause ) .
check_conjunction ( (A +- Bl ) ,B ,(A +- Bl ) ) .
check_clause(ok ,A ,BiClause ) +-
query -goal (A ,Answer ) ,
check- answer (Answer ,A ,B ,Clause ) .
check_clause ( (AI +- BI ) ,A ,B ,(A ~+- BI ) ) .
check-answer (true ,A ,B ,ok ) .
check- answer(false ,A ,B , (A +- Bl )) +-
extract _body (B ,Bl ) .
extract -body (true ,true ) .
extract _body ( (A i - B ) ,A ) .
extract _body (( (Ai - B ) ,Bs) ,(A ,As ) ) i -
extract _body (Bs ,As ) .
query -goal (A ,true ) +-
system (A ) .
query -goal ( Goal ,Answer ) +-
not system ( Goal ) ,
writeln (['Is the goal ' ,Goal ,' true ?']) ,
read (Answer ) .
Program 19 .15 : Bottom -up diagnosis of a false solution
19.3 Enhanced meta -interpreters for debugging 325
x = [3 ,2 , 1] ,
C = insert ( 2 , [ 1] , [2 , 1] ) +- 2 ~ 1
There is a diagnosis algorithm for false solutions with an even better query
complexity , called divide - and- query. The algorithm progress es by splitting the
proof tree into two approximately equal parts , and querying the node at the
splitting point . If the node is false , the algorithm is applied recursively to the
subtree rooted by this node . If the node is true , its subtree is removed from the
19.3 Enhanced meta -interpreters for debugging 327
clause ( A , B ) ,
query - clause ( ( A + - B ) ) , ! ,
missing - solution ( A , A ) + -
not system ( A ) .
read ( Answer ) ,
tree and replaced by true , and a new middle point is computed . The algorithm can
be shown to require a number of queries logarithmic in the size of the proof tree .
over both the top - down and the bottom - up diagnosis algorithms .
solution is more difficult then the previous bugs . We say that a clause covers a
missing-solution(isort ([2,1,3],[1,2,3]),C)?
Enter a true ground instance of
(isort ([2,1,3] ,[1,2,3]) +- isort ([1,3] ,Xs) ,insert(2,Xs,[1,2,3]))
if there is such , or ' no ' otherwise
no .
G = insert (1,[3],[1,3])
The reader can verify that the goal insert(1,[3],[1,3]) is not covered by Program
19.4 Background 329
19.14.
The three algorithms shown can form the basis of a high -quality interactive
program development environment for Prolog .
19 .4 Background
There are two agpects to the above rule . First , the heuristic identification of a
bacteria based on its gram stain , morphology , and aerobicity . Second, there is
a certainty factor attached to the rule . We argue that these should be separate .
The uncertainty is best handled by an enhanced metainterpreter such ag Program
19.10. The heuristic knowledge is expressed in the Prolog rule :
Note that the rule above , and those in Program 19.6, are essentially ground . The
only variable appearing in the "context " of object under study . For MYCIN , the
context in the above rule is the organism , for the oven management system it is
the dish .
Rules using more of the power of Prolog are just as easy to write . Here is a
clause from a toy medical expert system , which gives a flavor of what is possible .
The credit evaluation expert system in Chapter 21 provides further examples .
330 Meta-Interpreters 19.4
The first to argue explicitly for the use of Prolog to build expert systems
were Clark and McCabe ( 1982 ) . That paper discuss es how explanation facilities
and uncertainty reasoning can be added to simple expert systems such as Program
predicates . An explanation facility and a query the user facility were incorporated
1984 ) . Using meta - interpreters as a basis for explanation facilities in expert systems
Takeuchi and Furukawa ( 1985 ) have shown that partial evaluation can eliminate
object program that inherits the functionality of the metainterpreter but not its
overhead . Sterling and Beer ( 1986 ) particularizes the work for expert systems .
Meta - interpreters , and more generally metaprograms , have also been composed
to affect the control How of Prolog programs . References are Dincbas and
Prolog has been used for a wide range of applications : expert systems , natural
language understanding , symbolic algebra , compiler writing , building embedded
languages , and architectural design to name a few . In this chapter we give a flavor
of writing application programs in Prolog .
The first chapter looks at programs for playing three games: mastermind ,
Nim , and Kalab . The next chapter presents an expert system for evaluating
requests for credit . The third chapter presents a program for solving symbolic
equations , while the final chapter looks at a compiler for a Pascal-like language .
The empha.sis in presentation in these chapters is on writing clear programs .
Knowledge embedded in the programs is made explicit . Minor efficiency gains are
ignored if they obscure the declarative reading of the program .
Chapter 20
Game - Playing Programs
20.1 Mastermind
Our first program guesses the secret code in the game of mastermind . It is
a good example of what can be programmed in Prolog easily with just a little
thought .
There is a very simple algorithm for playing the game : impose some order
on the set of legal guesses ; then iterate , making the next guess that is consistent
with all the information you have so far until you find the secret code .
guess ( Code ) , which acts as a generator , uses the procedure selects ( Xs , Ys ) ( Program
guess ( Code ) + -
The procedure check ( Guess ) tests the proposed code Guess . It first verifies
that Guess is consistent with all ( i . e . , not inconsistent with any ) of the answers
to queries already made ; then it asks the user for the number of bulls and cows
in Guess . The ask ( Guess ) procedure also controls the generate - and - test loop ,
succeeding only when the number of bulls is four , indicating the correct code is
found :
check ( Guess) +-
not inconsistent (Guess) , ask(Guess) .
inconsistent ( Guess) +-
query (Old ,Bulls ,Cows) ,
not bulls - and_cows- match ( Old ,Guess,Bulls ,Cows) .
The bulls match between a previous guess Old Guess and a conjectured guess
Guess if the number of digits in the same position in the two guesses equals
the number of Bulls in Old Guess. It is computed by the predicate exact
_matches ( Old Guess, Guess,Bulls ) . The cows match if the number of common
336 Game-Playing Programs 20.1
mastermind ( Code ) +-
cleanup , guess ( Code ) , check ( Code ) , announce .
guess ( Code ) ~
Code = [Xl ,X2 ,X3 ,X4 ] , selects ( Code , [ 1 )2 , 3 , 4 , 5 ,6 , 7 , 8 ,9 ,0 ] ) .
check ( Guess ) +-
inconsistent ( Guess ) +-
query ( Old Guess ,Bulls , Cows ),
not bulls _and _cows - I Datch ( Old Guess , Guess , Bulls , Cows ).
exact - matches ( X ,Y ,N ) + -
size _of ( A ,same _place ( A ,X ,Y ) ,N ) .
commoll J Ilembers ( X , Y ,N ) + -
size _of ( A , ( member ( A ,X ) ,member ( A , Y ) ) ,N ) .
Asking a guess
ask ( Guess ) +-
repeat ,
writeln ( [ ' How many bulls and cows in ' , Guess ,' ? '] ) ,
read ( ( Bulls , Cows )) ,
Bookkeeping
announce +-
digits without respect to order corresponds to the sum of Bulls and Cows, and is
computed by the procedure bulls_and_cows_match . It is easy to count the number
of matching digits and common digits in two queries , using the set-predicate
size_of/ So
The ask( Guess) procedure is a memo-function which records the answer to
the query . It performs some limited consistency checks on the input with the
procedure sensible(Response ) and succeedsonly if the answer is 4 bulls. The
expected syntax for the user's reply is a tuple (Bulls, Cows).
The remaining (top-level) predicates are for bookkeeping. The first , cleanup,
removes unwanted information from previous games. The predicate announce
tells how many guesses were needed using the set-predicate utility size_of.
A more efficient implementation of the exact_matches and common _members
procedures can be obtained by writing iterative versions :
I I I
IIIII
IIIIIII
20.2 Nim
We turn our attention now from mastermind to Nim , also a game for two
players . There are N piles of matches , and the players take turns to remove some
of the matches (up to all) in a pile. The winner is the player who takes the last
match . Figure 20.1 gives a common starting position , with four piles of 1, 9, 5
and 7 matches respectively . .
To implement the Nim playing program , we use the game-playing framework
given as Program 18.8.
The first decision is the representation of the game position and the moves.
A natural choice for positions is a list of integers where elements of the list correspond
to piles of matches. A move is a tuple (NiM) for taking M matches
from pile N. Writing the procedure move(Move,Position,Position1),- where Position
is updated to Positionl by Move, is straightforward . The recursive rule
counts down match piles until the desired pile is reached . The remaining piles of
matches representing the new game position is computed routinely :
move((K ,M ),[NINs],[N I Nsl]) +-
K > 1, Kl := K- l , move((Kl ,M ),Ns,Nsl ).
There are two possibilities for updating the specified pile of matches , the base
case of the procedure . H all the matches are taken , the pile is removed from the
list . Otherwise the new number of matches in the pile is computed , and checked
to be legal :
move((I ,N),[NINs],Ns).
move((I ,M ),[NINs],[N I INs]) +- N > M , Nl := N- M .
The mechanics of turns for two person games is specified by two facts .
The initial piles of matches and who moves first must be decided by the
two players . Assuming the computer moves second, the game of Figure 20.1 is
specified as
1
11
101
111
000
The game is over when the last match is taken . This corresponds to the game
position being the empty list . The person having to move next is the loser , and
the output messages of announce are formulated accordingly . The details are in
Program 20.2.
It remains to specify how to choose the moves. The opponent 's moves are
accepted from the keyboard ; how much flexibility is allowed in input is the responsibility
of the programmer . Here , because we concentrate on the logic of the
game, we assume the player will enter legal moves:
choose...move(Position ,opponent ,Move ) +-
writeln ( ['please make move ']) , read (Move ) .
nim -sum([NINs],Bs,Sum) +-
binary (N ,Ds), nim _add(Ds,Bs,Bsl ) , nim-sum(Ns,Bs1,Sum).
nim -sum([ ],Sum,Sum).
nim _add(Bs,[ ],Bs).
nim _add([ ],Bs,Bs).
nim _add([B IBs],[CICs],[DIDs]) +-
D := (B+ C) mod 2, nim _add(Bs,Cs,Ds).
Program 20 .2 : A program for playing a winning game of Nim
20.2 Nim 341
binary ( 1,[1]).
binary (N ,[DIDs]) iN
> 1, D := N mod 2, ni := N/ 2, binary (ni ,Ds).
decimal(Ds,N) +- decimal(Ds,Oil ,N).
decimal([ ],N ,T ,N).
decimalD IDs],A ,T ,N) +- Al := A + D*T , Tl := T *2, decimal(Ds,Al ,Tl ,N).
zero([ ]).
zero([OIZs]) -to- zero(Zs).
safe_move(Position,Nim Sum,Move) ~
Move is a move from the current Position with
the value Nim Sum which leavesa safe position .
safe. . move (Piles,Nim Sum,Move) +--
safe. . move(Piles,Nim Sum,l ,Move) .
safeJI1ove([Pile IPiles] ,Nim Sum,K ,(KiM )) f -
binary (Pile,Bs), can-zero(Bs,Nim Sum,Ds,O), decimal(Ds,M).
safeJI1ove([Pile IPiles] ,Nim Sum,K ,Move) f -
Kl := K + l , safeJI1ove(Piles,Nim Sum,Kl ,Move) .
can-zero([],Nim Sum,[ ],0) +-
zero(Nim Sum) .
can-zero([B I Bs],[O INim Sum],[CIDs],C) +-
can-zero(Bs,Nim Sum,Ds,C) .
can-zero([B IBs],[1INim Sum],[DIDs],C) +-
D := 1- B*C, C1 := 1- B, can-zero(Bs,Nim Sum,Ds,C1).
Program 20.2 (Continued)
a safe position creates an unsafe one. The best strategy is to make an arbitrary
move when confronted with a safe position hoping the opponent will blunder, and
convert unsafe positions to safe ones.
To implement this strategy, we need two algorithms . One to compute the
nim-sum of a given position , and the other to determine a move to convert an
unsafe position to a safe one. The move to make is determined by the safety of
the position . If the position is unsafe, use the algorithm to find a move to make
the position safe and win the game. If the position is safe, make an arbitrary
move (one match from the first pile) and hope:
342 Game-Playing Programs 20.2
choose J D . ove ( Ns , computer , ( l , l ) ) + - % The computer ' s ' arbitrary move '
safe ( Ns ) .
unsafe . It is defined by calculating the rum - sum Sum of the piles of matches ( by
In a prior version of the program unsafe did not return Sum . When writing
safe _ move it transpired that the Dim - sum was helpful , and it was sensible to pass
the already computed value , rather than recomputing it . The predicate safe is
The Dim - sum is computed by nim _ sum ( Ns , So Far , Sum ) . The relation computed
is that Sum is the Dim - sum of the numbers Ns added to what has been
accumulated in So Far . To perform the additions , the numbers must first be converted
overcome the difficulty of adding lists of unequal length , the least significant digits
represented as [ 0 , 1 , 1 ] . The two numbers can then be added from least significant
digit to most significant digit , as is usual for addition . This is done by nim _ add / 9
and is slightly simpler than regular addition since no carry needs to be propagated .
The code for both binary and nim _ add appears in Program 20 . 2 .
The mm - sum Sum is used by the predicate safe _ move ( Ns , Sum , Move ) to find
a winning move Move from the position described by Ns . The piles of matches
are checked in turn by safe _ move / 4 to see if there is a number of matches which
can be taken from the pile to leave a safe position . The interesting clause is
The heart of the program is can _ zero ( Bs , Nim Sum , Ds , Carry ) . This relation is true
if replacing the binary number Bs by the binary number Ds would make Nim Sum
zero . The number Ds is computed digit by digit . Each digit is determined by the
corresponding digit of Bs , Nim Sum and a carry digit Carry initially set to O . The
20.3 Kalah 343
20.3 Kalah
We now present a program for playing the game of Kalah that uses alpha -
beta pruning . Kalah is a game which fits well into the paradigm of game trees for
two reasons. First , the game has a simple , reason ably reliable evaluation function ,
and , second, its game tree is tractable , which is not true for games such as chess
and go. It has been claimed that Kalah programs that have been written are
unbeatable by human players . Certainly , the one presented here beats us.
Kalah is played on a board with two rows of six holes facing each other . Each
player owns a row of six holes , plus a kalah to the right of the holes . In the initial
state there are six stones in each hole and the two kalahs are empty . This is
pictured in the top half of Figure 20.3.
A player begins his move by picking up the stones of one of his holes . Proceeding
counterclockwise around the board , he puts one of the picked -up stones
in each hole and in his own kalah skipping the opponent 's kalab , until no stones
remain to be distributed . There are three possible outcomes . If the last stone
lands on the kalah , the player has another move . If the last stone lands on an
empty hole owned by the player , and the opponent 's hole directly across the board
contains at least one stone , the player takes all the stones in the hole plus his last
landed stone and puts them all in his kalah . Otherwise the player 's turn ends,
and his opponent moves.
The bottom kalah board in Figure 20.3 represents the following move from
the top board by the owner of the top holes . He took the six stones in the
rightmost hole and distributed them , the last one ending in the kalah , allowing
another move . The stones in the fourth hole from the right were then distributed .
If all of the holes of a player become empty (even if it is not his turn to play ) ,
the stones remaining in the holes of the opponent are put in the opponent 's kalah
and the game ends. The winner of the game is the first player to get more than
half of the stones in his kalah .
( 0. 0. Om
(
(
6 6 6 6 6 6
... .. . . .. . .. . .. .. .
0.ON
0 .. 0 .. 0 .. 0 ,. 0 .. 0 ..
70
oO
0-
00 0
:
Om
0.
60
6
() 0
Figure 20.3: Board positions for Kalah
Play framework
play (Game ) +- See Program 18.8.
Choosing a move by minimax with alpha-beta cut -off
choose- move(Position ,computer ,Move ) +-
lookahead (Depth ) ,
alpha -beta (Depth ,Position ,- 40,40,Move ,Value ) ,
nl , write (Move ) , nl .
choose- move(Position ,opponent ,Move ) +-
nl , writeln (['please make move']) , read (Move ) , legal (Move ) .
alpha _beta (O,Position ,Alpha ,Beta ,Move ,Value ) +-
value (Position ,Value ) .
alpha _beta (DiPosition ,Alpha ,Beta ,Move ,Value ) +-
D > 0,
set_of (M ,move (Position ,M ) ,Moves) ,
Alphal := - Beta ,
Betal := - Alpha ,
Dl := D - l ,
evaluate _and _choose(Moves ,PositionD 1,Alphal ,Betal ,nil , (Move ,Value )) .
evaluate _and _choose( [Move IMoves] ,PositionD ,Alpha ,Beta ,Record ,Best Move ) +-
move (Move ,Position ,Positionl ) ,
alpha _beta (D ,Positionl ,Alpha ,Beta ,Move X ,Value ) ,
Valuel := - Value ,
cutoff (Move ,V aluel ~D ,Alpha ,Beta ,Moves ,Position ,Record ,Best Move ) , !.
evaluate _and _choose( [ ] ,PositionD ,Alpha ,Beta ,Move , (Move ,Alpha ) ) .
cutoff (Move ,Value ,D ,Alpha ,Beta ,Moves ,Position ,Movel , (Move ,Value ) ) +-
Value ~ Beta , !.
cutoff (Move ,Value ,D ,Alpha ,Beta ,Moves ,Position ,Movel ,Best Move ) +-
Alpha < Value , Value < Beta , I,
evaluate _and _choose(Moves ,PositionD ,Value ,Beta ,Move ,Best Move ) .
cutoff (Move ,Value ,D ,Alpha ,Beta ,Moves ,Position ,Movel ,Best Move ) - -
Value ~ Alpha , !,
evaluate _and -choose(Moves ,PositionD ,Alpha ,Beta ,Move 1,Best Move ) .
move(Board,[MIMs]) +-
member(M ,[1,2,3,4,5,6]),
stonesin -l1ole(M ,Board,N) ,
extend-Inove(N ,M ,Board,Ms).
move(board([O,O,O,O,O,O],K ,Ys,L),[ ]).
Program 20 .3 : A complete program for playing Kalah .
346 Game-Playing Programs 20.3
Evaluation function
pieces ( K ) , N = : = 6 * K , ! .
pieces ( N ) , K > 6 * N , ! .
nth . . . member ( N , [ H I Hs ] , K ) + -
nth . . . member ( 1 , [H I Hs ] , H ) .
ll - aubstitute ( l , [X I Xs ] , Y , [ YIXs ] ) + - I .
ll - aubstitute ( N , [X I Xs ] ,Y , [X I Xsl ] ) + -
legal ( [ ] ) .
show ( Position ) .
show ( board ( H , K , Y , L ) ) + -
write -stones(H) +- ,-
nl , tab (5), display-holes(H) .
display holes([H IHs]) +-
write _pile (H), display-1Ioles(Hs).
display-1Ioles([ ]) +- nl .
Program 20.3 (Continued)
20.3 Kalah 349
lookahead(2).
initialize (kalah,board([N,N,N,N ,NN ],O,[N,N ,N,N,NN ] ,O),opponent) +-
pieces(N).
pieces(6).
Program 20.3 (Continued)
The code gives all moves on backtracking . The predicate stones( M ,Board ,N)
returns the number of stones N in hole M of the Board if N is greater than 0, failing
if there are no stones in the hole . The predicate extend_move(M ,Board ,NiMs )
returns the continuation of the move Ms . The second clause for move handles the
special case when all the player 's holes become empty during a move .
Testing whether the move continues is nontrivial , since it may involve all the
procedures for making a move . If the last stone is not placed in the kalah , which
can be determined by simple arithmetic , the move will end , and there is no need
to distribute all the stones . Otherwise the stones are distributed , and the move
continues recursively .
The basic predicate for making a move is distribute _stones( Stones,N ,Board ,
Boardl ) which computes the relation that Boardl is obtained from Board by
distributing the number of stones in Stones starting from hole number N .
There are two stages to the distribution , putting the stones in the player 's
holes , distribute _my _holes, and putting the stones in the opponent 's holes , distribute
_your _holes.
The simpler case is distributing the stones in the opponent 's holes . The holes
are updated by distribute , and the distribution of stones continues recursively if
there is an excess of stones . A check is made to see if the player 's board has
become empty during the course of the move , and , if so, the opponent 's stones
are added to his kalah .
Distributing the player 's stones must take into account two possibilities , distributing
from any particular hole , and continuing the distribution for a large
350 Game-Playing Programs 20.3
The central predicates have been described . A running program is now obtained
by filling in the details for I / O , for initializing and terminating the game,
etc . Simple suggestions can be found in the complete program for the game, given
as Program 20.3.
In order to optimize the performance of the program , cuts can be added .
Another tip is to rewrite the main loop of the program as a failure - driven loop
rather than a tail recursive program . This is sometimes necessary in implementations
which do not incorporate tail recursion optimization and a good garbage
collector .
20 .4 Background
At the time of writing this book , there has been a surge of activity in the
application of artificial intelligence to industry . Of particular interest are expert
systems - programs designed to perform tasks previously allocated to highly
paid human experts . One important feature of expert systems is the explicit
representation of knowledge .
This entire book is relevant for programming expert systems . The example
programs typify code that might be written . For instance , the equation -solving
program of the next chapter can be , and has been , viewed as an expert system .
The knowledge of expert systems is usually expressed in a rulelike form . Prolog
whose basic statements are rules is thus a natural language for implementing
expert systems . We briefly discuss the relationship of Prolog rules to classical
expert systems such as MYCIN in the background to Chapter 19.
The chapter presents an account of developing a prototype expert system .
The example comes from the world of banking : to evaluate requests for credit
from small business ventures .
The most important factor is the collateral that can be offered by the client in
case the venture folds . The various types of collateral are divided into categories .
Currency deposits , whether local or foreign , are first - class collateral . Stocks are
examples of second-class collateral , while the collateral provided by mortgages
and the like is regarded as illiquid .
Also very important is the client 's financial record . Experience in the bank
has shown that the two most important factors are the client 's net worth per assets
, and the current gross profits on sales. The client 's short -term debt per annual
sales should be considered in evaluating the record , and slightly less significant
is last year 's sales growth . For knowledge engineers with some understanding of
banking no further explanation of such concepts is necessary. In general a knowledge
engineer must understand the domain sufficiently to be able to communicate
with the domain expert .
ChaoSuses qualitative terms in speaking about these three factors : "The client
had an excellent financial rating , or a good form of collateral . His venture would
provide a reasonable yield , etc ." Even concepts that could be determined quantitatively
are discussed in qualitative terms . The financial world is too complicated
to be expressed only with the numbers and ratios constantly being calculated . In
order to make judgments , experts in the financial domain tend to think in qualitative
terms with which they are more comfortable . To echo expert reasoning and
to be able to interact with ChaoSfurther , qualitative reasoning must be modeled .
On talking to Chas , it became clear that a significant amount of the expert
knowledge he described could be naturally expressed as a mixture of procedures
and rules . On being pressed a little on the second and third interviews Chas gave
rules for determining ratings for collateral , and financial rating . These involved
considerable calculations , and in fact Chas admitted that to save himself work in
the long term , he did a quick initial screen to see if the client was at all suitable .
This information is sufficient to build a prototype . We show how these comments
and observations are translated into a system . The top level basic relation is
credit(Client,Answer) where Answer is the reply given to the request by Client for
credit . The code has three modules - collateral , financial _rating and bank_yield
- corresponding to the three factors the expert said were important . The initial
screen that the client is worth considering in the first place is performed by
the predicate ok_profile(Client). The answer Answer is then determined with the
A Credit Evaluation Expert System 353
predicate evaluate(Profile ,Answer ) which evaluates the Profile built by the three
modules .
Being proud knowledge engineers , we stress the features of the top level
formulation in credit/ 2. The modularity is apparent . Each of the modules can be
developed independently without affecting the rest of the system . Further there is
no commitment to any particular data structure , i .e., data abstraction is used . For
this example a structure profile ( C,FY ) represents the profile of collateral rating
C, the financial rating F and the yield Y of a client . However , nothing central
depends on this decision and it would be easy to change it . Let us consider some
of the modular pieces.
Let us look at the essential features of the collateral evaluation module . The
relation collateral _rating / 2 determines a rating for a particular client 's collateral
. The first step is to determine an appropriate profile . This is done with
the predicate collateral _profile which classifies the client 's collateral as first _class,
second_class or illiquid , and gives the percentage each covers of the amount of
credit the client requested . The relation uses facts in the database concerning
both the bank and the client . In practice there may be separate databases for the
bank and the client . Sample facts shown in Program 21.1 indicate , for example ,
that local currency deposits are a first class collateral .
The profile is evaluated to give a rating by collateral _evaluation . It uses
"rules of thumb " to give a qualitative rating of the collateral : as excellent , good ,
etc . The first collateral _evaluation rule , for example , reads that : "The rating is
excellent if the coverage of the requested credit amount by first class collateral is
greater than or equal to 100 percent ."
Two features of the code bear comment . First , the terminology used in
the program is the terminology of ChaB. This makes the program (almost ) selfdocumenting
to the experts , and means they can modify it with little help from
the knowledge engineer . Allowing people to think in domain concepts also facilitates
debugging , and aBsists in using a domain independent explanation facility
aB discussed in Section 19.2. Second, the apparent naivete of the evaluation rules
is deceptive . There is a lot of knowledge and experience hidden behind these simple
numbers . Choosing poor values for these numbers may mean suffering severe
losses.
The financial evaluation module evaluates the financial stability of the client .
It uses items taken mainlY from the balance and profit / loss sheets. The financial
rating is also qualitative . A weighted sum of financial factors is calculated (by
score) and used by calibrate to determine the qualitative class.
354 A Credit Eval1
lation Expert System
Credit Evauation
ok _ profile ( Client ) ,
sumlist ( Xs , Sum ) ,
Evaluation rules
f -
FirstClass ~ 100 .
evaluate(Profile, Outcome) +-
Outcomeis the reply to the client'8 Profile.
evaluate(Profile,Answer) ~
rule (Conditions,Answer), verify (Conditions,Profile).
Program 21.1 (Continued)
356 A Credit Evaluation Expert System
Client Data
ok _profile ( client 1) .
It should be noted that both the modules giving the collateral rating and
the finarlcial rating reflect the point of view and style of a particular expert ,
Chas Manhattan , rather than the universal truth . Within the bank there is no
consensus about the subject . Some people tend to be conservative and some are
prepared to take considered risks .
Programming the code for determining the collateral and financial ratings
proceeded easily . The knowledge provided by the expert was more - or - less directly
translated into the program . The module for the overall evaluation of the client ,
however , was more challenging .
The major difficulty was formulating the relevant expert knowledge . Our
expert was less forthcoming with general rules for overall evaluation than for
rating the financial record , for example . He happily discussed the profiles of
particular clients , and the outcome of their credit requests and loans , but was
reluctant to generalize . He preferred to react to suggestions rather than volunteer
rules .
This forced a close re-evaluation of the exact problem we were solving . There
were three possible answers the system could give : approve the request for credit ,
refuse the request , or ask for advice . There were three factors to be considered .
Each factor had a qualitative value that was one of a small set of possibilities . For
358 A Credit EvaluationExpert System
example , the financial rating could be bad , medium , good or excellent . Further
the possible values were ranked on an ordinal scale .
several rules . Here is a typical one : " If the client ' s collateral rating is excellent ( or
better ) , her financial rating good ( or better ) , and her yield at least reasonable ,
then grant the credit request ."
But this misses many cases covered by the rule , for example , when the client 's
profile is ( excellent , good , excellent ). All the cases for a given rule can be listed .
It seemed more sensible , however , to build a more general tool to evaluate rules
expressed in terms of qualitative values from ordinal scales .
There is potentially a problem with using ordinal scales due to the large
number of individual cases that may need to be specified . If each of the N
modules have M possible outcomes , there are NM cases to be considered . In
general , it is infeasible to have a separate rule for each possibility . Not only is
space a problem for so many rules , but the search involved in finding the correct
rule may be prohibitive . So instead we defined a small ad hoc set of rules . We
hoped the rules defined , which covered many possibilities at once , would be sufficient
to cover the clients the bank usually deal with . We chose the structure
rule ( Conditions , Conclusion ) for our rules , where Conditions is a list of conditions
under which the rule applies and Conclusion is the rule ' s conclusion . A condition
has the form condition ( Factor , Relation , Rating ) , insisting that the rating from the
factor named by Factor bears the relation named by Relation to the rating given
by Rating .
rule ( [condition ( collateral , ' ~ ' , excellent ) , condition ( finances , ' ~ ' , good ),
condition ( yield , ' ~ ' ,reasonable ) ) , give _credit ).
Anot 'her rule given by Ohas reads : " If both the collateral rating and financial
rating are good , and the yield is at least reasonable , then consult your superior ."
This is translated to
A Credit Evaluation Expert System 359
Factors can be mentioned twice to indicate they lie in a certain range , or might
not be mentioned at all . For example , the rule
rule ( [condition (collateral ,' $ ' ,moderate ) ,condition (finances ,' $ ' ,medium )],
refuse_credit).
states that a client should be refused credit if the collateral rating is no better than
moderate and the financial rating is at best medium . The yield is not relevant ,
and so is not mentioned .
The interpreter for the rules is written nondeterministic ally . The procedure
is : "Find a rule and verify that its conditions apply ," as defined by evaluate . The
predicate verify ( Conditions ,Profile ) checks that the relation between the corresponding
symbols in the rule and the ones that are associated with the Profile
of the client is as specified by Conditions . For each Type that can appear , a
scale is necessary to give the order of values the scale can take . Examples of
scale facts in the bank database are scale( collateral , [excellent , good, moderate])
and scale(finances , [excellent , good, medium , bad] ) . The predicate select_value returns
the appropriate symbol of the factor under the ordinality test which is performed
by compare. It is an access predicate , and consequently the only predicate
dependent on the choice of data structure for the profile .
At this stage the prototype program is tested . Some data from real clients is
necessal' Y, and the answer the system gives on these individuals is tested against
what the corresponding bank official would say. The data for clientl is given in
Program 21.2. The reply to the query credit ( clientl ,X) is give_credit .
Our prototype expert system is a composite of styles and methods - not
just a backward chaining system . Heuristic rules of thumb are used to determine
the collateral rating ; an algorithm , albeit a simple one, is used to determine the
financial rating ; and there is a rule language , with an interpreter , for expressing
outcomes in terms of values from discrete ordinal scales. The rule interpreter proceeds
forwards from conditions to conclusion , rather than backward as in Prolog .
Expert systems must become such composites in order to exploit the different
forms of knowledge already extant .
The development of the prototype was not the only activity of the knowledge
engineers . Various other features of the expert system were developed in parallel .
An explanation facility was built as an extension of Program 19.9. A simulator for
rules based on ordinal scales was built to settle the argument among the knowledge
engineers as to whether a reasonable collection of rules would be sufficient to cover
360 A Credit Evaluation Expert System
21.1 Background
More details on the credit evaluation system can be found in Ben-David and
Sterling (1985).
Chapter 22
An Equation Solver
methods can and do take widely different forms . They can be a collection of rules
for solving the class of equations to which the method is applicable , or algorithms
implementing a decision procedure .
Abstractly a method has two parts : a condition testing whether the method
is applicable , and the application of the method itself .
The type of equations our program can handle are indicated by the three
examples in Figure 22.1. They consist of algebraic functions of the unknown , that
is + , - , * , / , and exponentiation to an integer power , and also trigonometric and
exponential functions . The unknown is x in all three equations .
We briefly show how each equation is solved .
The first step in solving equation (i ) in Figure 22.1 is factorization . The
problem to be solved is reduced to solving cos(x) = 0 and 1- f2.sin (x) = o. A solution
to either of these equations is a solution to the original equation .
Both the equations cos(x) = O and 1- 2.sin (x) = O are solved by making x the
subject of the equation . This is possible since x occurs once in each equation .
The solution to COS
( x) = 0 is x= arccos( 0) . The solution of 1- 2.sin ( x) = 0 takes
the following steps:
1- 2.sin (x ) = 0,
2.sin (x ) = 1,
sin (x ) = 1/ 2,
x = arcsin (1/ 2) .
The key to solving equation (iii ) in Figure 22.1 is to realize that the equation
is really a quadratic equation in ~ . The equation ~ .x_5.f! + 1 + 16= 0 can be
rewritten as (~ )2- 5'2'~ + 16= O. This can be solved for fJXgiving two solutions
of the form ~ = Rhs, where Rhs is free of x. Each of these equations are solved
for x to give solutions to equation (iii ).
PRESS was tested on equations taken from British A -level examinations in
mathematics . It seems that examiners liked posing questions such as equation
(iii ) which involved the student manipulating logarithmic , exponential or other
transcendental functions into forms where they could be solved as polynomials .
A method called homogenization evolved to solve equations of these type .
The aim of homogenization is to transform the equation into a polynomial in
some term containing the unknown. (We simplify the more general homogenization
of PRESS for didactic purposes.) The method consists of four steps which
we illustrate for equation (iii ). The equation is first parsed and all maximal nonpolynomial
terms containing the unknown are collected with duplicates removed.
This set is called the offenders set. In the example it is { & :I:,~ +l } . The second
step is finding a term , known as the reduced term . The result of homogenization
is a polynomial equation in the reduced term . The reduced term in our example
is ~ . The third step of homogenization is finding rewrite rules that express
each of the elements of the offenders set as a polynomial in the reduced term .
Finding such a set guarantees that homogenization will succeed. In our example
the rewrite rules are & :1:= (~ )2 and 2(:1:+1) = 2.~ . Finally , the rewrite rules are
applied to produce the polynomial equation .
We complete this section with a brief overview of the equation solver . The
basic predicate is solve_equation(Equation,X ,Solution) . The relation is true if
Solution is a solution to Equation in the unknown X . The complete code appears
as Program 22.1.
Program 22.1 has four clauses for solve_equation , one for each of the four
methods needed to solve the equations in Figure 22.1. More generally , there is
a clause for each equation solving method . The full PRESS system had several
more methods .
Our equation solver ignores several features that might be expected . There
is no simplification of expressions , no rational arithmetic , no record of the last
equation solved , no help facility , and so forth . PRESS did contain many of these
facilities as discussed briefly in the background section at the end of this chapter .
364 An Equation Solver 22.1
22.2 Factorization
The top -level clause in Program 22.1 has a cut as the first goal in the body . This
is a green cut : none of the other methods depend on the success or failure of
factorization . In general we omit green cuts from clauses we describe in the text .
22 .3 Isolation
A useful concept to locate and manipulate the single occurrence of the unknown
is its position . The position of a subterm in a term is a list of argument
numbers specifying where it appears . Consider the equation cos(x) = O. The term
cos(x) containing x is the first argument of the equation , and x is the first (and
only ) argument of cos(x) . The position of x in cos(x) = 0 is therefore [1,1]. This is
indicated in the diagram in Figure 22.2. The figure also shows the position of x
in 1- 2.sin (x) = 0 which is [1,2,2,1] .
The clause defining the method of isolation is
% Addition
isolax ( 1 , Terml + Term2 = Rhs , Terml = Rhs - Term2 ) .
% Addition
isolax ( 2 , Terml + Term2 = Rhs , Term2 = Rhs - Terml ) .
% Subtraction
isolax ( 1 , Terml - Term2 = Rhs , Terml = Rhs + Term2 ) .
% Subtraction
isolax ( 2 , Terml - Term2 = Rhs , Term2 = Terml - Rhs ) .
Term2 # O .
Terml # O .
isola . x ( 2 , Termlj Term2 = Rhs , Term2 = log ( base ( Terml ) , Rhs ) ) . % Exponentiation
Program 22 . 1 ( Continued )
22..'? Isolation 367
solve_polynomial _equation(PolyEquation ,X ,X = - Bj A ) +-
linear (PolyEquation ), I,
pad (Poly Equation ,[(A ,l ) ,(B,O)]).
solve_polynomial_equation(PolyEquation ,X ,Solution) +-
quadratic(Poly Equation), I,
pad (Poly Equation ,[(A ,2),(B ,l ),(C,O)]),
discriminant (A ,B,C,Discriminant ),
root (X ,A ,B,C,Discriminant ,Solution) .
discriminant (A ,B,C,D) +- D := B *B - 4*A * C.
Program 22.1 (Continued)
22 . 3
Isolation 369
root ( X , A , B , C , O , X = - B / ( 2 * A ) ) .
pad ( [ ] , [ ] ) .
Its effect is to ensure that the unknown appears on the left -hand side of Equationl .
The first argument N , the head of the position list , indicates the side of the
equation in which the unknown appears . A 1 means the left -hand side, and the
equation is left intact . A 2 means the right -hand side, and so the sides of the
equation are swapped .
The transformation of the equation is done by isolate / 3. It repeatedly applies
rewrite rules until the position list is exhausted :
22.3 Isolation 373
The rewrite rules , or isolation axioms , are specified by the predicate iso-
lax(N ,Equation ,Equation1 ) . Let us consider an example used in solving 1-
2,sin (x) = 0. An equivalence transformation on equations is adding the same quantity
to both sides of an equation . We show its translation into an isolax axiom for
manipulating equations of the form u- v= w. Note that rules need only simplify
the left -hand side of equations , since the unknown is guaranteed to be on that
side.
Two rules are necessary to cover the two cases whether the first or second
argument of u- v contains the unknown . The term u- v= w can be rewritten to
either u= w+ v or v= u- w. The first argument of isolax specifies which argument
of the sum contains the unknown . The Prolog equivalent of the two rewrite rules
is then
If Term !! equals zero , however , the rewriting is invalid . A test is therefore added
which prevents the axioms for multiplication being applied , if the term by which
it divides is o. For example ,
In fact the equation has a more general solution . Integers of the form 2.n'7r
can be added to either solution for arbitrary values of n. The decision whether a
particular or general solution is desired depends on context , and semantic infor
mation , independent of the equation solver .
374 An Equation Solver 22.3
22.4 Polynomial
The polynomial normal form is a list of tupies of the form (Ai ,Ni ) , where
Ai is the coefficient of XNi , which is necessarily nonzero . The tupies are sorted
into strictly decreasing order of Ni ; for each degree there is at most one tuple .
For example , the list [(lift ) ,( - 3,1) ,( ft,0)] is the normal form for -;;2- 3.x+ ft. The
leading term of the polynomial , is the head of the list . The classical algorithms
for handling polynomials are applicable to equations in normal form . Reduction
to polynomial normal form occurs in two stages:
It is convenient for many of the polynomial methods to assume that all the
terms in the polynomial form have nonzero coefficients . Therefore the final step
of polynomial _normal _form is removing those terms whose coefficients are zero.
This is achieved by a simple recursive procedure remove_zero_terms .
The code for polynomial _form directly echoes the code for is_polynomial . For
each clause used in the parsing process, there is a corresponding clause giving the
resultant polynomial . For example, the polynomial form of a term xrl. is [(l ,n)]
which is expressed in the clause
The recursive clauses for polynomial _form manipulate the polynomials in order
to preserve the polynomial form . Consider the clause
add_polynomials ([ ] ,Poly,Poly) .
add_polynomials ( [P IPoly],[ ],(P IPoly]).
add_polynomials ([(Ai ,Ni ) IPolyl ] ,[(Aj ,Nj ) IPoly2] ,[(Ai ,Ni ) IPoly]) ~
Ni > Nj , add_polynomials(Polyl ,[(Aj ,Nj ) IPoly2],Poly).
add_polynomials ( [(Ai ,N) IPoly 1),[(Aj ,N) IPoly2] , [(A ,N) IPoly]) ~
Ni = := Nj , A := Ai + Aj , add_polynomials(Polyl ,Poly2,Poly) .
add_polynomials([(Ai ,Ni) IPolyl ],[(Aj ,Nj) IPoly2],[(Aj ,Nj )IPoly]) ~ '
Ni < Nj , add_polynomials([(Ai ,Ni )IPolyl ],Poly2,Poly).
in order to see which is applicable and can be used to solve the equation . The
the standard formula . The equation solver mirrors the human method . The
( with quadratic ) if the leading ! erm in the polynomial is of second degree . Since
zero terms have been removed - in putting the polynomial into its normal form ,
pad puts them back if necessary . The next two steps are familiar : calculating the
discriminant , and returning the roots according to the value of the discriminant .
quadratic ( Poly ) ,
pad ( Poly , [ ( A , 2 ) , ( B , l ) , ( 0 , 0 ) ] ) ,
discriminant ( A , B , 0 , Discriminant ) ,
discriminant ( A , B , C , D ) + - D : = ( B * B - 4 * A * C ) .
root ( X , A , B , C , O ,X = - B / ( 2 * A ) ) .
Other clauses for solve _ polynomial _ equation constitute separate methods for
solving different polynomial equations . Linear equations are solved with a simple
formula . In PRESS , cubic equations are handled by guessing a root and then
factors , or that quartic equations missing a cubic and a linear term are really
disguised quadratics .
22 . 5 Homogenization
The top - level clause for homogenization reflects the transformation of the
checks that there are multiple offenders . If there is only a single offender , homogenization
The predicate reduced _ term / 1, finds a reduced term , that is a candidate for the
new unknown . In order to structure the search for the reduced term , the equation
is classified into a type . This type is used in the next stage to find rewrite rules
reduced term . The type of the example equation is exponential . PRESS encodes
a lot of heuristic knowledge about finding a suitable reduced term . The heuristics
are dependent on the type of the terms appearing in the offenders set . To aid the
stages - classifying the type of the offenders set and finding a reduced term of
that type :
The heuristic used to select the reduced term in this example is that if all
the bases are the same, A , and each exponent is a polynomial in the unknown , X ,
then a suitable reduced term is A x :
The straightforward code for base and polynomial _exponents is in the complete
program . The heuristics in PRESS are better developed than the ones shown
here . For example , the greatest common divisor of all the leading terms of the
polynomials is calculated and used to choose the reduced term .
The next step is checking whether each member of the offenders set can
be rewritten in terms of the reduced term candidate . This involves finding an
378 An Equation Solver 22.5
Substituting the term in the equation echoes the parsing process used by
offenders as each part of the equation is checked whether it is the appropriate
term to rewrite .
(i ) Add isolation axioms to Program 22.1 to handle quotients on the left -hand
side of the equation . Solve the equation x/ 2= 5.
(ii ) Add to the polynomial equation solver the ability to solve disguised linear
and disguised quadratic equations . Solve the equations 2:r:3- 8= x3, and x4-
5x2 + 6= 0.
(iii ) The equation cos(f2.x) - sin (x) = D can be solved as a quadratic equation in
sin (x) by applying the rewrite rule cos(f2.x) = 1- f2.sin2 (x) . Add clauses to
Program 22.1 to solve this equation . You will need to add rules foridentifying
terms of type trigonometric , heuristics for finding trigonometric reduced
terms , and appropriate homogenization axioms .
(iv ) Rewrite the predicate free_of( Term ,X) so that it fails as soon as it finds an
occurrence of X in Term .
22 .6 Background
PRESS includes various modules , not discussed in the chapter , that are interesting
in their own right : for example , a package for interval arithmetic (Bundy ,
1984) , an infinite precision rational arithmetic package developed by Richard
O ' Keefe , and an expression simplifier based on difference - structures as described
in Section 15.2, developed by Lawrence Byrd . The successful integration of all
these modules is strong evidence for the practicality of Prolog for large programming
projects .
The development of PRESS showed up classic.points of software engineering .
For example , at one stage the program was being tuned prior to publishing some
statistics . Profiling was done on the program , which showed that the predicate
most commonly called was free_of. Rewriting it as suggested in Exercise (iv )
above resulted in a speedup of 35 percent in the performance of PRESS .
Program 22.1 is a consider ably cleaned-up version of PRESS . Tidying the
code enabled further research . Program 22.1 was easily translated to other logic
programming languages , Concurrent Prolog and FCP (Sterling and Codish , 1986) .
Making the conditions when methods were used more explicit , enabled the writing
of a program to learn new equation solving methods from examples (Silver , 1986) .
Chapter 23
A COInpiler
Our final application is a compiler . The program is presented top- down . The
first section outlines the scope of the compiler and gives its definition . The next
three sections describe the three components : the parser , the code generator and
the assembler .
program factorialj
begin
read value ;
count := 1 ;
result := 1 ;
begin
count := count + 1j
end .,
write result
end
ARITHMETIC CONTROL
I/ O, etc.
Literals Memory
jumpeq read
tokens is paxsed in the second stage , syntax analysis , to give a source structure .
The third and fourth stages, respectively , transform the source structure into
relocatable code, and assemble this into absolute object code. The final stage
outputs the object program .
Our compiler implements the middle three stages. Both the first stage of
lexical analysis and the final output stage are relatively uninteresting and are
not considered here . The top level of the code handles syntax analysis , code
generation and assembly.
The basic predicate compile ( Tokens, ObJ'ect Code) relates a list of tokens Tokens
to the ObJ'ect Code of the program the tokens represent . The compiler compiles
correctly any legal PL program , but does not handle errors ; that is outside
the scope of this chapter . The list of tokens is assumed to be input from some
rMC
382 A Compiler 23.1
~~
symbol address instruction operand
~tijtij
READ 21
LOADC
STORE
LOADC
STORE
LABELl LOAD
SUB
JUMPGE
LOAD
ADDC
STORE COUNT
LOAD RESULT
MUL COUNT
STORE RESULT
JUMP LABELl
LABEL2 LOAD RESULT
WRITE
HALT
COUNT BLOCK
RESULT
VALUE
Figure 23.3 Assembly code version of a factorial program
previous stage of lexical analysis . The parser performing the syntax analysis ,
implemented by the predicate parse, produces from the Tokens an internal parse
tree Structure . The structure is used by the code generator encode to produce
relocatable code Code. A dictionary associating variable locations to memory
address es and keeping track of labels is needed to generate the code. This is the
23.1 Overview of the compiler 383
identifier (X ) - + [X ], { atom(X )} .
pI Jnteger(X ) - + [X ], {integer(X )} .
test(compare(Op,X ,Y )) - + expression(X ), comparison_op(Op), expression(Y ).
-
comparison _op ('= ') - +
~'= ']
' .
encode
-expression (expr(Op,El ,E2),DiCode) +-
not singleinstruct ion(Op,E2,D,Instruction),
single-operation(Op,E1,D,E2Code,Code) ,
encode _expression (E2,D,E2Code).
singleinstruct ion(Op,number(C) ,D,instr(OpCode,C)) +-
literal_operation(Op,OpCode).
singleinstruct ion(Op,name(X),D,instr(OpCode,A)) +-
memory_operation(Op,OpCode), lookup(X,D,A).
single_operation(Op,E,D,Code,(Code;Instruction)) +-
commutative(Op), singleJ.nstruction(Op,E,D,Instruction).
single_operation(Op,E,DiCode,
(Code;instr(store,Address);Load;instr(OpCode,Address))) +-
not commutative(Op),
lookup('temp',D,Address),
encode -expression
(E,D ,Load) ,
op_code(Op,E,OpCode).
op_code(Op,number(C) ,OpCode) +- literal-operation(Op,OpCode).
op_code(Op,name(X),OpCode) +- memory_operation(Op,OpCode).
literal_operation('+ ',addc). memory_operation('+ ',add).
literal-operation('- ' ,subc). memory_operation('- ' ,sub).
literal_operation('* ',mulc). memory_operation('*',mul).
literal_operation(' f ',divc). memory_operation(' f ',div).
commutative(' + '). commutative(' *').
encode
_test(compare(Op,El ,E2),Label,D,(Code; instr(OpCode,Label) )) +-
comparison _opcode(Op,OpCode),
encode _expression
(expr('- ',El ,E2) ,DiCode).
comparison
-opcode('= ' jumpne). comparisoll-opcode(' ' jumpeq).
comparison
_opcode('> ' jumple). comparison
_opcode('2::' jumplt ).
comparison
_opcode('< ' jumpge). comparison
_opcode('~ ' jumpgt).
lookup(Name,Dictionary,Address) +- SeeProgram15.9
The assembler
assemble
( Code,Dictionary, TidyCode) +-
TidyCodeis the result of assemblingCoderemoving
no_opsand labeL ." and filling in the Dictionary.
Program 23.1 (Continued)
386 A Compiler 23.1
tidy _ and _ count ( Code , liN , Tidy Code \ ( instr ( halt , O ) ; block ( L ) ) ) ,
Nl := N + l ,
allocate ( Dictionary ,N 1 , N2 ) ,
L := N2 - Nl , !.
Nl := N + I .
allocate ( void , NN ) .
allocate ( Before , NO , Nl ) ,
N2 := Nl + l ,
allocate ( After , N2 ,N ) .
Program 23 . 1 ( Continued )
second argument of encode . Finally , the relocatable code is assembled into object
The testing data and instructions for the program are given as Program
list of tokens . The two small programs consist of a single statement each , and
test features of the language not covered by the factorial example . The program
23 . 2 The parser
to the DCG , whose top - level predicate is pI _ program . The DCG has a single
variant of Program 16 .2 is assumed to translate the DCG into Prolog clauses . The
convention of that program is that the last argument of the predicates defined by
test_compiler(X ,Y ) +-
program(X ,P), compile(P,Y ).
program (testl , [program,testl ,';',begin,write ,x ,'+ ',y ,'- ',z,' / ' ,2,end]).
program (test2, [program,test2,';',
begin,if ,a,' > ',b,then,max,':= ' ,a,else,max,':= ',biend]).
program(factorial )
[program,factorial ,';,
,begin
,read ,value ,' ; '
,
count , ' .-
, -
' , 1" ' .'
,
result ,
' ,.-- ' , 1 " ' . '
,while ,count , ' < ' ,value ,do
,begin
, count , ' ..= ' , count " ' + ' 1 " ' .'
,
result ,
' .-
. -
' , result "
' * ' count
,
end ,
' ., '
,write ,result
,end]).
Program 23.2: Testing and data
parse (Source,Structure ) +-
pl _program (Structure ,Source\ [ }) .
The structure returned as the output of the parsing is the statement constituting
the body of the program . For the purpose of code generation the top level
program statement has no significance , and is ignored in the structure built .
388 A Compiler 23.2
rest -statements ((SiSs)) - + [' ;'], statement (S) , rest -statements (Ss) .
expression (X ) ~ pl _constant (X ) .
expression
- (,expr
- (.Op~ ,X ,Y .)). +-
pl _constant (X ) , arithmetic _op (Op ) , expression (Y ) .
This subclass of expressions does not respect the standard precedence of arithmetic
operators . The expression x.2+ y is parsed as x.( 2+ y) . On the other hand ,
the expression x + y- z/ 2 is interpreted unambiguously as x+ ( y- (z/ 2)) . We restrict
ourselves to the subclass to simplify both the code and its explanation in this
chapter .
For this example , we restrict ourselves to two types of constants in PL : iden -
tifiers and integers . The specification of pI_constant duly consists of two rules .
Which of the two is found is reflected in the structure returned . For identifiers X ,
the structure name (X) is returned , while number (X) is returned for the integer
X:
integers and atoms , respectively . This allows the use of Prolog system predicates
to identify the PL identifiers and integers . Recall that the curly braces notation
identifier ( X ) - + [X ] , { atom (X ) } .
plinteger ( X ) - + [X ] , { integer (X ) } .
In fact all grammar rules that use PL identifiers and constants could be modified
to call the Prolog predicates directly if greater efficiency is needed .
A list of arithmetic operators is necessary to complete the definition of arithmetic
expressions . The form of the statement for addition , represented by " + " , is
given below . The grammar rules for subtraction , multiplication and division are
analogous , and appear in the full parser in Program 23.2:
statement(if (T ,81,82)) -+
[if ], test(T), [then], statement(Sl), [else], statement(S2).
Tests are defined to be an expression followed by a comparison operator and
another expression . The structure returned has the form compare ( Op , X , Y) , where
Op is the comparison operator , and X and Yare the left - hand and right - hand
expressions in the test , respectively :
test ( compare ( Op ,X ,Y ) ) - +-
expression ( X ) , comparison _op ( Op ) , expression (Y ) .
While statements consist of a test and the action to take if the test is true .
The structure returned is while ( T , S ) where T is the test and S is the action . The
syntax is defined by the following rule :
PL identifier , and returns the structure read ( X ) , where X is the identifier . Write
statements are similar :
Collecting together the various pieces of the DCG described above gives a
parser for the language . Note that ignoring the arguments in the DCG gives a
formal BNF grammar for PL .
Let us consider the behavior of the parser on the test data in Program 23 . 2 .
The parsed structures produced for the two single statement programs have the
form ( structure ) ; void where ( structure ) represents the parsed statement . The write
statement is translated to
if ( compare( > , name ( a) , name ( b) ) , assign( max , name ( a) ) , assign( max, name ( b)) ) .
Program testl :
write ( expr ( + ,name (x ) ,expr (- ,name (y ) ,expr (j ,name (z) ,number (2) )) ) ) ;void
if (compare (> ,name (a) ,name (b )) ,assign(max ,name (a) ) ,assign (max ,name (b))
) ;void
Program test S:
The basic relation of the code generator is encode( Structure , Dictionary , Code) ,
which generates Code from the Structure produced by the parser . This section
echoes the previous one. The generated code is described for each of the structures
produced by the parser representing the various PL statements :
The structure produced by the parser for the general PL assignment statement
has the form assign(Name ,Expression ) where Expression is the expression to
be evaluated and assigned to the PL variable Name . The corresponding compiled
392 A Compiler 23.3
in the compiled code is the structure instr ( X , Y ) where X is the instruction and
( Code ; instr ( store , Address ) ) , where Code is the compiled form of the expression ,
which , after execution , leaves the value of the expression in the accumulator . It
have been specified between Name and Address , and between Expression
and Code . From the programmer ' s point of view it is irrelevant when the final
structure is constructed , and in fact the order of the two goals in the body of
this clause can be swapped without changing the behavior of the overall program .
Furthermore , the lookup goal , in relating Name with Address , could be making
a new entry , or retrieving a previous one , where the final instantiation of the
address happens in the assembly stage . None of this bookkeeping needs explicit
There are several cases to be considered for compiling the expression . Constants
C is the constant . Similarly identifiers are compiled into the instruction load
lookup ( X , D , Address ) .
not matter in which order the two instructions are determined . The clause of
encode _ expression is
The nature of the single instruction depends on the operator and whether
the PL constant is a number or an identifier . Numbers refer to literal operations
while identifiers refer to memory operations:
single-instruction (Op,number(C),D ,instr (Opcode,C)) +-
literal _operation (Op,Opcode) .
single-instruction (Op,name(X ),D ,instr (Opcode,A )) +-
memory-operation (Op,Opcode), lookup (X ,D ,A ) .
code will be determined from the compiled code for calculating E2 , and the single
location , called " $ temp " in the code below . The sequence of instructions is then
the code for E2 , a store instruction , a load instruction for El and the appro -
( Code ;
Load ;
) + -
not commutative ( Op ) ,
op _ code ( Op ,E , OpCode ) .
multiplication , which circumvents the need for a temporary variable . In this case
394 A Compiler 23.3
the memory or literal operation can be performed on el , assuming that the result
of computing E2 is in the accumulator:
single_operation(Op,E,D ,Code,(Code;Instruction)) +-
commutative(Op), single~nstruction(Op,E,DiInstruction).
The next statement is the conditional if -then -else parsed into the structure
if( Test, Then,Else) . To compile the structure , we have to introduce labels where
instructions can jump to . For the conditional we need two labels marking the
beginning and end of the elsepart respectively. The labels have the form label(N) ,
where N is the address of the instruction . The value of N is filled in during the
assembling stage , when the label statement itself is removed . The schematic of
the cod~ is given by the third argument of the following encode clause:
encode(if (Test,Then,EIse),D,
(TestCode;
Then Code ;
instr (jump ,L2);
label(Ll );
E IseCodej
label (L2 ) )
)+-
encode_test (Test,Ll ,D ,TestCode) ,
encode(Then,D,ThenCode),
encode(Else,D,ElseCode) .
parsed into the structure while( Test, Statements). A label is necessarybefore the
test , then the test code is given as for the if -then -else statement , then the body
of code corresponding to Statements and a jump to re-perform the test . A label
is necessary after the jump instruction for when the test fails .
23.3 The code generator 395
Program testl :
Program test2 :
instr ( store , Max ) ) jinstr ( jump , L2 ) ; label ( Ll ) j ( instr ( load , B ) ; instr ( store , Max ) ) ;
label ( L2 ) ) ; no _ op
Program factorial :
instr ( read , Value ) ; ( instr ( loadc , l ) ; instr ( store , Count ) ) ; ( instr ( loadc ,l ) ;
instr ( store , Result ) ) ; ( label ( Ll ) ; ( ( instr ( load , Count ) ; instr ( sub , Value )) ;
;
( ( instr ( load , Result ) ; instr ( mul , Count ) ) ; instr ( store , Result ) ) ; no _op ) ;
( label ( Ll ) j
Test Codej
Do Codej
instr ( jump , Ll ) ;
label ( L2 ) )
) +-
encode ( Do , D , Do Cod e) .
read ( . X) , is compiled into a single read instruction where the table is used to get
the correct address :
lookup ( X , D , Address ) .
The output statement is translated into encoding an expression , and then a write
instruction :
Figure 23 .6 contains the relocatable code after code generation and before
assembly for each of the three examples of Program 23 . 2 . Mnemonic variable
names have been used for easy reading .
23 .4 The assembler
The final stage performed by the compiler is assembling the relocatable code
into absolute object code . The predicate assemble ( Code , Dictionary , Object Code )
takes the Code and Dictionary generated in the previous stage , and produces the
object code . There are two stages in the assembly . During the first stage , the
instructions in the code are counted , at the same time computing the address es
of any labels created during code generation and removing unnecessary null operations
. This tidied code is further augmented by a halt instruction , denoted
by instr ( halt , 0 ) , and a block of L memory locations for the L PL variables and
temporary locations in the code . The space for memory locations is denoted by
block ( L ) . In the second stage address es are created for the PL and temporary
variables used in the program : .
tidy _and _count ( Code , liN , Tidy Code \ ( instr ( halt ,O ) ;block (L ) ) ) ,
Nl := N + l ,
The predicate tidy _ and _ count ( Code , M , N , Tidy Code ) tidies the Code into Tidy -
Code where the correct address es of labels have been filled in , and the null operations
have been removed . Procedurally , executing tidy _ ana _count constitutes
a second pass over the code . M is the address of the beginning of the code ,
while N is one more than the address of the end of the original code . Thus the
number of actual instructions in Code is N + 1- M . Tidy Code is represented as a
difference - structure based on " ;, ' .
The recursive clause of tidy _ and _ count demonstrates both standard difference -
structure technique , and updating of numeric values :
Three types of primitives occur in the code : instructions , labels and no _ops .
23.4 The assembler 397
Program testl :
instr (load,11) jinstr (divc,2) jinstr (store,12)jinstr (load,10) j
instr (sub,12) jinstr (add,9) jinstr (write ,O) jinstr (halt ,O) jblock(4)
Program test2:
instr (load,10) ;instr (subill ) ;instr (jumple ,7) ;instr (load,10) ;
instr (store,12) ;instr (jump ,9) jinstr (load,ll ) jinstr (store,12) j
instr (halt ,O) ;block (3)
Program factorial :
instr ( read , 21 ) ; instr ( loadc , l ) ; instr ( store , 19 ) ; instr ( loadc ,l ) ;
Nl := N + l .
Both labels and no -ops are removed without updating the current address or
adding an instruction to the tidied code:
tidy _and _count (label (N ) ,NN ,Code \ Code ) .
tidy _and _count (no _op ,NN ,Code \ Code) .
Declaratively the clauses are identical . Procedurally , the unification of the label
number with the current address causes a major effect in the program . Every
reference to the label address is filled in . This program is another illustration of
the power of the logical variable .
The predicate allocate (Dictionary ,M ,N) has primarily a procedural interpretation
. During the code generation as the dictionary is constructed , storage locations
are associated with each of the PL variables in the program , plus any
temporary variables needed for computing expressions . The effect of allocate is
to assign actual memory locations for the variables , and fill in the references to
them in the program .
398 ACorn piler 23.4
The variables are found by traversing the Dictionary . M is the address of the
memory location for the first variable , while N is one more than the address of
the last . The order of variables is alphabetic corresponding to their order in the
allocate ( void , NN ) .
allocate ( Before , NO , Nl ) ,
N2 : = Nl + l ,
( i ) Extend the compiler so that it handles repeat loops . The syntax is repeat
( statement ) until ( test ) . Extensions to both the parser and the compiler need
program repeat ;
begin
1. , - I ,
, - ,
repeat
begin
write ( i ) ;
i : = i + l
end
until i = 11
end .
encoder , you will have to cater for the possibility of needing several temporary
variables .
23 . 5 Background
Prolog implementations vary in the details of how the user interacts with
the Prolog system , and how it develops Prolog programs . Here we give a general
overview of how one might interact with a standard Prolog system .
Prolog systems are usually file -oriented . That is, the source of the program
under development resides in one or more files . The standard cycle of program
development is :
While the program is not complete do
Compose (part of ) the program using a text editor ,
and place it in a file ;
Enter Prolog , and consult the file ;
Run the program , usually under the Prolog debugger .
Consult is the standard Edinburgh Prolog system predicate for loading a set of
procedures residing in some file . In an operating system which can keep suspended
process es, usually the Prolog system and the text editor process es are
both kept simultaneously . If so, only the file that has been changed need to be
consulted . Otherwise , the entire program needs to be consulted afresh every time .
Alternatively , in a Prolog system that can save its state on a file , used under an
operating system which cannot keep process es, it may be advised to checkpoint
portions of a program which have been debugged into a saved Prolog state , and
start Prolog with that saved state .
Some Prolog systems , e.g. Quintus Prolog , allow an even better interaction
between Prolog and the text editor .
Considering debugging , each Prolog system has its own conventions . However
, most modem debuggers are based on Byrd 's box model of debugging (Warren
, 1981) . In this model , one can inspect a goal when it is called , when it
400 Appendix
B . System Predicates
This section describes all the evaluable system predicates available in Wisdom
Prolog . These predicates are provided in advance by the system and they
cannot be redefined by the user . Any attempt to add clauses or delete clauses
to an evaluable predicate will fail with an error message, and leave the evaluable
predicate unchanged . Evaluable predicates are available for the following tasks :
Input / Output
Reading -in programs
Opening and closing files
Reading and writing Prolog terms
Getting and putting characters
Arithmetic
Affecting the flow of the execution
Classifying and operating on Prolog terms
(metalogical facilities )
Term Comparison
Debugging facilities
Environment facilities
NAME MEANING
Types:
atom ( Atom )
Atom is an atom .
integer ( I ) I is an integer .
atomic ( A ) A is an atom or an integer .
constant ( X )
Same as atomic .
functor ( St , F , A ) F is the principal functor of St and A is its arity .
arg ( N , S , Sn ) Sn is the Nth argument of S.
var ( V )
V is a variable
nonvar ( C )
C is not a variable .
Program manipulation :
Same as assert .
a. ssertz ( Clause )
Retract a clause unified with Clause .
retract ( Clause )
abolish ( F ,A ) Retract ( erase ) all the clauses ,
I/ O :
Input me .
402 Appendix
output file .
stream .
type Type .
Debugging :
trace Prompt for a goal to trace .
General :
! Cut
call(G) Call G .
, conjunction .
, disjunction .
- .. umv .
x = y X unifies with Y .
\= Negation of = .
Tl \ = = T2 Negation of = = .
Special:
iterate (G) Do G until it fails (efficiently).
fork _exec(me,Comm) a -like command.
ancestor(G ,N) The goal G is the Nth ancestor of the current
goal (used by the debugger).
cutg(G) ancestor cut .
retry (G) Retry goal G.
Arithmetics :
[ + file , + file2 , . . . , . . .] .
To assert procedures from the terminal you should use the user file :
[ user ] or [ + user ]
Hooks :
The user can define his own shell by defining the shellj O predicate .
$ shell :-
expand _ goal :
Using this predicate the user can define his own conversions
expand _ clause :
Shell:
s-expand -goal System expand goal .
user _call Execute the call of the user .
display . xesult (Vs ) Display the value of the variables .
get . xeply Get the user reply from the terminal .
cons Jist For consulting a number of files using lists .
Appendix 405
C . Predefined Operators
For prefix :
fx means that the precedence of the argument must be lower
than the precedence of the operator .
fy means that the precedence of the argument may be equal
to the precedence of the operator .
For postfix:
xf and yf - analogously the same conventions of the prefix .
For infix :
xIx xfy yfx - mean that both sub expressions which are the arguments of the
operator must be of lower precedence than the operator itself ;
only the left -hand argument should be of lower precedence ;
only the right -hand argument should be of lower precedence ; respectively .
To definean operatortype:
I 7- op(Precedence
, Type, Op).
WherePrecedence is from 1 to 1200, Typeis oneof the mentioned
above
, Opis
the operator
's nameor a list of names.
Remember that the precedence of the arguments must be lower then 1000
which is the precedence of the ' ,' Thusyoushould
write
assert( (A :- B) )
and not
assert(AB )
406 Appendix
Operator definition :
:- (op(12.00,fx ,[ :- , ?- ])).
:- op(1200, xrx,[ (:- ) , < - , -+- ]).
:- op(llOO,xfy,';').
:- op(lOOO,xfy,',') .
:- op(900,fx ,[not]).
:- op(700,xfx ,[ = , \ = , is, := , = .. , - - , \ = = , = := , = \ = , < , > , ~ , ~ ]) .
:- op(500,yfx ,[+ ,- ,\ ]).
:- op(500,fx ,[(+ ),(- )]).
:- op(400,yfx ,[* ,j ,j I ]).
:- op(300,xfx ,[mod]).
References
Bundy , A . and Welham , R ., Using Meta -level Inference for Selective Application
of Multiple Rewrite Rules in Algebraic Manipulation , Artificial Intelligence
16 , pp . 189 - 212 , 1981 .
Eggert , P.R . and Chow , K .P., Logic Programming Graphics with Infinite Terms ,
Tech . Report University of California , Santa Barbara 83-02, 1983.
van Emden , M ., Warren 's Doctrine on the Slash, Logic Programming Newsletter ,
December , 1982 .
Hill , R ., LUSH -Resolution and its Completeness , DCL Memo 78, Department of
Artificial Intelligence , University of Edinburgh , 1974.
Knuth , Do, The Art Of Computer Programming , Volume 3, Sorting and Searching ,
Addison - Wesley, Reading , Massachussets , 1973.
Mellish , C .S., Some Global Optimizations for a Prolog Compiler , J . Logic Programming
2 , pp . 43- 66, 1985.
Naish, L ., Negation and Control in Prolog, Tech. Report 85/ 12, University of
Melbourne , Australia , 1985 .
O ' Keefe , R .A ., On the Treatment of Cuts in Prolog Source- Level Tools , Proc . 1985
Symposium on Logic Programming , IEEE Computer Society Press, Boston ,
1985 .
Pereira , L .M ., Logic Control with Logic , Proc . First International Logic Programming
Conference , pp . 9- 18, Marseille , 1982.
8SL61
"
"
-
'
"
"
' '
sa
.
'
~
414 References
Intelligence 10, pp. 441- 454, Hayes, Michie and Pao (eds.), Ellis Horwood,
1982.
Weizenbaum
-- ., J ., Computer Power and Human Reason, W .R . Freeman & Co .,
1976 .
O0
Program 3.21: Insertion sort
Program 3.22: Quicksort
Program 3.23: Defining binary trees
Program 3.24: Testing tree membership
Program 3.25: Determining when trees are isomorphic
Program 3.26: Substituting for a term in a tree
Program 3.27: Travers als of a binary tree
Program 3.28: Recognizing polynomials
Program 3.29: Derivative rules
Program 3.30: Towers of Hanoi
Program 3.31: Satisfiability of Boolean formulae
Program 5.1: A simple program using not 89
Program 7.1: Yet another family example 104
Program 7.2: Merging ordered lists 111
Program 7.3: checking for list membership 111
Program 7.4: Selecting the first occurrence of an element from a list 113
Program 7.5: Non -membership of a list 114
Program 7.6: Testing for a subset 114
Program 7.7: Testing for a subset 115
Program 7.8: Translating word for word 116
Program 7.9: Removing duplicates from a list 117
Program 7.10: Reversing with no duplicates 118
Program 8.1: Computing the greatest common divisor of two integers 124
Program 8.2: Computing the factorial of a number 125
Program 8.3: An iterative factorial 127
Program 8.4: Another iterative factorial 127
Program 8.5: Generating a range of integers 128
Program 8.6a: Summing a list of integers 129
Program 8.6b : Iterative version of summing a list of integers using an
accumulator 129
Program 8.7a: Computing inner products of vectors 130
Program 8.7b : Computing inner products of vectors iteratively 130
Program 8.8: Computing the area of polygons 130
Program 8.9: Finding the maximum of a list of integers 131
Program 8.10: Checking the length of a list 131
Program 8.11: Finding the length of a list 131
Program 8.12: Generating a list of integers in a given range 131
Program 9.la : Flattening a list with double recursion 136
Program 9.lb : Flattening a list using a stack 136
List of Programs 417
Figure 16 . 1 258
Program 16.2: Translating grammar rules to Prolog clauses 259
Program 16.3: A DCG computing a parse tree 261
Program 16.4: A DCG with subject / object agreement 262
List of Programs 419
Edinburgh Prolog , 95, 121- 122, 148, explanation , for expert system ,
191 , 199 , 307 313 - 318
logging facility , 186- 188 enhanced , 308 , 311 , 319 , 320 - 323
College Department
55 Hayward Street
Cambridge , MA 02142
Note : The diskette contains all programs shown in the book . They are written
for standard Edinburgh Prolog . You may have to adapt them to other varieties
of Prolog .
Signature
Ship to : Name
Address
City /State/Zip
Special Instruction
Order form for
WISDOM Prolo2
developed by Shmuel Sarra
at the Weizmann Institute of Science
. screen management
. exit to your O .S. w /o leaving the interpreter - so you can use your favorite
text -editor , invoke a system command (e.g., list the directory ) , or even
mount a new shell -interpreter atop the Prolog interpreter
For more information contact Motti Goldberg at : max @wisdom (CSNET or
BITNET )
Pleasesendthe following:
Quantity Version Code Unit price Total