From Logic Programming To Prolog
From Logic Programming To Prolog
Krzysztof R. Apt
Contents
List of Figures
xi
List of Programs
xiii
Preface
xv
Acknowledgements
xvii
1 Introduction
1.1
Background
1.2
Declarative Programming
1.3
Logic Programming Paradigm
1.4
Shortcomings of Prolog
1.5
Theoretical Foundations of Logic Programming
1.6
Program Verification
1.7
Prolog and Program Verification
1.8
Structure of the Book
1.9
Further Reading
1.10 References
1
1
2
3
8
11
12
13
14
16
17
2 Unification
2.1
Terms
2.2
Substitutions
2.3
Unifiers
2.4
* The Nondeterministic Robinson Algorithm
2.5
* Robinsons Algorithm
2.6
The MartelliMontanari Algorithm
2.7
Properties of Mgus
18
18
20
24
26
30
31
37
vii
viii
2.8
2.9
2.10
2.11
Concluding Remarks
Bibliographic Remarks
Summary
References
40
41
41
42
3 Logic
3.1
3.2
3.3
3.4
3.5
3.6
3.7
3.8
3.9
3.10
3.11
3.12
44
45
46
51
52
56
60
63
67
71
72
73
73
4 Logic
4.1
4.2
4.3
4.4
4.5
4.6
4.7
4.8
4.9
4.10
4.11
4.12
4.13
4.14
4.15
75
76
77
79
81
84
87
89
91
92
95
99
100
102
102
102
104
105
114
115
118
123
136
138
ix
5.8
5.9
5.10
5.11
Concluding Remarks
Bibliographic Remarks
Summary
References
141
144
144
144
6 Termination
6.1
Multiset Ordering
6.2
Terminating Programs
6.3
Applications
6.4
* Characterizing Terminating Programs
6.5
Left Terminating Programs
6.6
Applications
6.7
* Characterizing Left Terminating Programs
6.8
* An Improvement
6.9
Concluding Remarks
6.10 Bibliographic Remarks
6.11 Summary
6.12 References
146
147
149
152
155
157
163
166
169
173
174
175
176
178
179
180
183
189
193
195
198
200
202
203
204
204
8 Partial Correctness
8.1
Introduction
8.2
Well-asserted Queries and Programs
8.3
Applications
8.4
Computing Strongest Postconditions
8.5
Applications
8.6
Absence of Failures
8.7
Concluding Remarks
8.8
Bibliographic Remarks
8.9
Summary
8.10 References
207
207
209
213
219
224
226
227
229
230
230
x
9 Programming in Pure Prolog with Arithmetic
9.1
Operators
9.2
Arithmetic Comparison Relations
9.3
Complex Domains
9.4
Lists of Integers
9.5
Difference Lists
9.6
Binary Search Trees
9.7
Evaluation of Arithmetic Expressions
9.8
Concluding Remarks
9.9
Bibliographic Remarks
9.10 Summary
9.11 References
232
233
236
238
240
243
246
250
253
254
255
255
257
257
258
259
263
264
270
272
278
279
280
280
282
283
288
291
293
295
298
302
305
309
312
314
316
317
318
Index
322
List of Figures
27
28
3.1 An SLD-tree
3.2 Another SLD-tree
69
70
108
109
110
111
111
112
115
123
131
132
132
138
148
249
11.1
11.2
11.3
11.4
11.5
284
284
286
301
304
List of Programs
NUMERAL, 118
ORDERED, 240
PALINDROME, 135
PATH, 300
PERMUTATION, 130
PERMUTATION1, 131
PREFIX, 131
QUEUE, 245
QUICKSORT, 241
QUICKSORT ACC, 242
QUICKSORT DL, 245
REMOVE, 297
REVERSE, 134
REVERSE DL, 244
SEARCH TREE, 247
SELECT, 129
SEQUENCE, 136
SET, 288
SET/1, 297
SLOWSORT, 240
SUBLIST, 132
SUBSET, 127
SUFFIX, 132
SUM, 120
SUMMER, 114
TRANS, 299
TRANS DAG, 299
TREE, 139
TREE ISOMORPHISM, 294
TREE MEMBER, 139
ADD, 289
APPEND, 127
APPEND3, 158
APPEND DL, 243
BETWEEN, 252
BIRDS, 303
CENTRAL AMERICA, 115
DELETE, 250
DISJOINT, 298
FACTORIAL, 252
FRONTIER, 141
IN ORDER, 140
INSERT, 249
INTERSECTION, 290
IN TREE, 248
LENGTH, 125
LENGTH1, 253
LESS, 122
LIST, 124
LIST1, 306
MAP COLOUR, 137
MAP OF CENTRAL AMERICA, 137
MAXIMUM, 238
MEMBER, 126
MEMBER1, 287
MERGESORT, 242
META INTERPRETER, 310
MULT, 122
NAIVE REVERSE, 133
NOT EQUAL, 296
xiii
xiv
TREE MINIMUM, 248
UNIFICATION, 309
UNION, 290
WIN, 302
YSP, 305
Preface
Prolog is some twenty years old and so is logic programming. However, they were
developed separately and these two developments never really merged. In particular, the books on Prolog place an emphasis on the explanation of various language
features and concentrate on teaching the programming style and techniques. In
contrast, the books on logic programming deal with the theoretical foundations of
the subject and place an emphasis on the mathematical theory of the underlying
concepts. As a result of these separate developments, verification of Prolog programs fell somewhere in the middle and did not receive the attention it deserved.
Many Prolog programs are much simpler and shorter than their counterparts
written in imperative programming languages. But for practically every Prolog
program there exists a fine line separating the queries which yield correct results
from the queries which go wrong. So program verification is as important for
Prolog as for other programming languages.
The aim of this book is to introduce the foundations of logic programming and
elements of Prolog, and show how the former can be applied to reason about the
latter. To make the book also appropriate for teaching Prolog, a separate chapter
on advanced features of Prolog is included.
Due to its structure the book makes it possible to teach in the same course, in
an integrated way, both logic programming and Prolog. It is appropriate for the
senior undergraduate and for the graduate courses. In fact, we used it for both of
them.
Throughout the theoretical chapters some basic mathematical ability is needed
to follow the arguments. We assume from the reader familiarity with mathematical
induction, but not much more. The presentation is self-contained and in particular,
all the notions used are precisely explained and often illustrated by examples.
It is useful to mention that very few results about the theory of logic programming are needed to reason about the considered subset of Prolog. On the other
hand, these results have to be augmented by additional ones that are established
while dealing with specific program properties, like the termination or absence of
xv
xvi
run-time errors.
Each of these program properties can be dealt with by applying one or two specific results tailored to this property. The usefulness of these methods is demonstrated by several examples.
We confined our presentation to a basic core of the theory of logic programming
and only a simple subset of Prolog is treated in a formal way. This allowed us
to keep the book limited in size and consequently appropriate for a one semester
course. Such a course naturally breaks down into three components:
theory of logic programming,
programming in Prolog,
verification of Prolog programs.
Each of these topics is of importance in its own right and in fact the book is
organized so that the first two can be studied independently. But studying these
three subjects together shows that they form a meaningful whole.
Acknowledgements
Various chapters of this book are based on a joint work I carried out during the
last five years with a number of colleagues. I would like to take this opportunity
and thank them for this fruitful cooperation.
In particular, Chapter 6 is based on a joint work with Dino Pedreschi, Chapter
7 on a joint work with Alessandro Pellegrini and Chapter 10 profited from papers
written jointly with Sandro Etalle and Elena Marchiori. Further, in Chapters 5
and 11 I used some concepts from a joint work with Frank Teusink, in Chapters
6, 8 and 10 I incorporated some results from a joint paper with Ingrid Luitjes
and in Chapters 8 and 10 I drew on some results from a joint work with Maurizio
Gabbrielli and Dino Pedreschi.
Speaking about joint work this book was started as a joint project with
Kees Doets in 1991, the aim of which was to write a comprehensive book on the
resolution method, logic programming and formal aspects of Prolog. However,
soon it turned out that, in contrast to me, Kees was able to shut off from all other
tasks and concentrate solely on the book writing.
Noticing the growing disproportion (to my disadvantage) between my part and
that of Kees I finally took up the courage and suggested to Kees that he publishes his part as a separate book. And so it happened see Doets [Doe94] on
page 17 for the outcome. I heartily recommend this book to those who would
like to study various aspects of logic programming theory omitted from this text,
like computability and foundations of logic programming with negation. I would
like to thank Kees here for numerous discussions we had on the subject of logic
programming from which I greatly profited.
Further, several people took the trouble to read and comment on this book
in different stages of its writing and provided me with valuable criticism. These
are anonymous referees, Annalisa Bossi, Livio Colussi, Wlodek Drabent, Nissim
Francez, Zoe Goey, Andrew Jones, Witek Marek and Dino Pedreschi. I also benefited from useful discussions with Marianne Kalsbeek, Katuscia Palamidessi and
Andrea Schaerf. Frank Teusink kindly designed most of the figures for me and
xvii
xviii
students of my courses helped me to polish the material.
Finally, I would like to thank the staff of Prentice Hall, in particular Jacqueline
Harbor, for the efficient and professional handling of all the stages of the production
of this book.
Chapter 1
Introduction
1.1
Background
2 Introduction
design of VLSI systems, representation of legislation and option trading. These
applications exploit the fact that knowledge about certain domains can be conveniently written down as facts and rules which can be directly translated into Prolog
programs.
These three aspects of logic programming theory, programming and applications grew together and often influenced each other. This versatility of logic
programming makes it an attractive subject to study and an interesting field to
work in.
1.2
Declarative Programming
Logic programming allows us to write programs and compute using them. There
are two natural interpretations of a logic program. The first one, called a declarative
interpretation, is concerned with the question what is being computed, whereas the
second one, called a procedural interpretation, explains how the computation takes
place. Informally, we can say that declarative interpretation is concerned with the
meaning, whereas procedural interpretation is concerned with the method .
These two interpretations are closely related to each other. The first interpretation helps us to better understand the second and explains why logic programming
supports declarative programming. Loosely speaking, declarative programming can
be described as follows. Specifications, when written in an appropriate format, can
be used as a program. Then the desired conclusions follow logically from the program. To compute these conclusions some computation mechanism is available.
Now thinking declaratively is in general much easier than thinking procedurally. So declarative programs are often simpler to understand and to develop.
In fact, in some situations the specification of a problem in the appropriate format
already forms the algorithmic solution to the problem. In other words, declarative programming makes it possible to write executable specifications. It should
be added however, that in practise the programs obtained in this way are often
inefficient, so this approach to programming has to be coupled with appropriate
use of program transformations and various optimization techniques.
Moreover, declarative programming reduces the issue of correctness of programs
to an analysis of the program from the logical point of view. In this analysis
the computation mechanism can be completely disregarded. This is an important
reduction which significantly simplifies the task of program verification.
This dual interpretation of logic programs also accounts for the double use of
logic programming as a formalism for programming and for knowledge representation, and explains the importance of logic programming in the field of artificial
intelligence.
1.3
4 Introduction
| ?- connection(amsterdam, fairbanks).
yes
where can one fly to from Seattle?
| ?- connection(seattle, X).
X = anchorage ;
X = fairbanks ;
no
(Here ; is a request typed by the user to get more solutions.)
can one fly somewhere from Fairbanks?
| ?-
connection(fairbanks, X).
no
etc.
This example shows two aspects of Prolog. First, the same program can be used
to compute answers to different problems (or queries). Second, a program can
be used much like a database. However, in a usual database all the facts about
connections would be stored, whereas here they are computed from more basic
facts by means of rules. The databases in which the knowledge is stored in the
form of both facts and rules is called a deductive database. Thus, Prolog allows us
to model query processing in deductive databases.
Example 2 Consider the problem of finding all elements which appear in two given
lists. In what follows we denote a list of elements a1 , . . ., an by [a1 , . . ., an ]. Such a
list can be alternatively written as [a1 |[a2 . . ., an ]]; a1 is called the head of [a1 , . . ., an ]
and [a2 , . . ., an ] the tail of [a1 , . . ., an ]. So for example, 1 is the head of [1, 2, 3, 4, 5],
whereas [2, 3, 4, 5] is the tail of [1, 2, 3, 4, 5], and we have [1, 2, 3, 4, 5] = [1|[2, 3, 4, 5]].
First we need to define when an element X is a member of the list. Two cases
arise. If X is the head, then the answer is positive. Otherwise we need to check if
X is a member of the tail. This translates into the following rules that define the
member relation:
member(X, [X | List]).
member(X, [Y | List]) member(X, List).
The solution to our problem is now straightforward: we just introduce the rule
which defines the desired relation:
6 Introduction
...
end.
Here the original lists are stored in the arrays a[1..m] and b[1..n] and the outcome
is computed in the segment [1..l] of the array c[1..n]. In addition to the above three
arrays two constants and four integer variables are also used.
This example shows that the searching mechanism does not need to be explicitly
specified in Prolog it is implicitly given. In the last rule the variable X implicitly
generates all elements of the first list, which are then tested for membership in the
second list. But this is the procedural interpretation of the program and it was not
needed to design it. In fact we obtained the program by referring to its declarative
interpretation.
In contrast to the Pascal solution, the Prolog solution can be used in a number
of ways, for example for testing
| ?- member_both(2, [1,2,3], [2,3,4,5]).
yes
or even for instantiating an element of a list:
| ?- member_both(2, [1,2,3], [X,3,4,5]).
X = 2
Finally, in Pascal the length of the arrays has to be known in advance. So
the above piece of Pascal program had to be preceded by an initialization of the
constants m, n to some values fixed in advance, here 100 and 200. So in principle
for every pair of lengths a different program needs to be written. To be able to
deal with lists of arbitrary lengths arrays cannot be used and one has to construct
the lists dynamically by means of pointers.
Example 3 Consider the following puzzle from Brandreth [Bra85, page 22]:
Strange Squares. The square of 45 is 2025. If we split this in two,
we get 20 and 25. 20 plus 25 is 45 the number we started with.
Find two other numbers with four-digit squares that exhibit the same
peculiarity.
To solve it using Prolog we just write down the definition of the number the
square of which is strange according to the above description. For clarity, we
also include the second argument which stands for the square of the number in
question.
sol(N, Z)
between(10, 99, N),
Z is N*N,
Z 1000,
Z // 100 + Z mod 100 =:= N.
Here * stands for multiplication, // for integer division, mod for the reminder
of the integer division and =:= for Prologs equality. In turn is is Prologs
counterpart of the assignment statement which evaluates the right-hand side and
assigns the value of it to the variable on the left-hand side. So for example
| ?- X is 2*3.
X = 6
Finally, between(X, Y, Z) is a relation which holds if Z is an integer between
the integers X and Y, i.e. if X Z Y holds. It is used here to successively generate
all integers between 10 and 99. For certain reasons we postpone the definition of
between to Section 1.4.
Now, to solve the puzzle, we just need to run the query sol(N, Z):
| ?- sol(N, Z).
N = 45,
Z = 2025 ;
N = 55,
Z = 3025 ;
N = 99,
Z = 9801 ;
no
The above program is very simple. Note that all what was needed was to describe the problem. This declarative interpretation turned out to be sufficient for
obtaining the desired program. Procedurally, this program performs quite a laborious computation: for every natural number between 10 and 99 it tests whether
it is a possible solution to the problem. As in the case of the other two programs
we can use it not only for computing but also for testing or for a mixture of both:
| ?- sol(55, 3025).
yes
| ?- sol(55, Z).
Z = 3025
yes
8 Introduction
| ?- sol(44, Z).
no
We have seen so far three examples of Prolog programs. In general, a Prolog
program is a sequence of facts, like direct(amsterdam, seattle) and rules, like
member(X, [Y | List]) member(X, List). These facts and rules define relations. Both the order of the facts and rules in the program and the order of
elements in rules bodies matter from the computation point of view.
The computation starts by posing a query, like sol(N, Z). A query can be seen
as a request to find values for the query variables for which the query holds. As
we have seen, each program can be used with various queries.
So programming in Prolog boils down to writing definitions of relations in a
specific, restricted syntax.
1.4
Shortcomings of Prolog
The programs exhibited in the previous section and many other Prolog programs
are concise, versatile and elegant. So naturally, Prolog was advertised by some as
the programming language in which programming is reduced to the writing of selfexplanatory specifications. This view is in our opinion incorrect and misleading.
It is undoubtedly true that many Prolog programs are strikingly elegant and
straightforward to write. This explains its widespread use and remarkable popularity. However, one should also bear in mind that a naive use of Prolog can easily
lead to unexpected difficulties and undesired behaviour. More specifically
(i) almost every Prolog program can be used in a wrong way,
(ii) arithmetic in Prolog is quite clumsy and its use can easily lead to problems,
(iii) some Prolog programs can be hopelessly inefficient.
Let us discuss these statements one by one.
Re: (i) (a) Termination. Suppose that to the program from Example 1 we add
the seemingly innocent fact
direct(seattle, seattle).
(Think about some scenic flights above the town.) Then if this fact is added at
the end of the program, the query connection(seattle, X) produces repeatedly
all the answers:
| ?- connection(seattle, X).
X = anchorage ;
Shortcomings of Prolog
X = seattle ;
X = fairbanks ;
X = anchorage ;
etc.
If on the other hand this fact is added at the beginning of the program, then the
following puzzling behaviour results:
| ?- connection(seattle, X).
X = seattle ;
X = anchorage ;
X = seattle ;
X = anchorage ;
etc.
So the answer X = fairbanks is never produced.
Further, consider now the situation when the order of the rules defining the
connection relation is reversed. Then when the fact direct(seattle, seattle)
is added at the beginning of the program, the execution of the above query continues forever, and when this fact is added at the end of the program, repeatedly
only one answer is produced:
| ?- connection(seattle, X).
X = fairbanks ;
X = fairbanks ;
etc.
This shows that it is easy to end up with non-terminating computations.
(b) The Occur-check Problem. Consider now the program from Example 2. If
we run it with the query member(X, [f(X),X]) which checks whether the variable
X occurs in the list consisting of two terms, f(X) and X, then instead of yielding
a positive answer, the execution unexpectedly continues forever and produces the
following interrupted listing:
10 Introduction
| ?- member(X, [f(X), X]).
X = f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(
...
In Prolog variables are assigned values by means of the unification algorithm.
The reason for the above abnormal behaviour is an omission of the so-called occurcheck test from this algorithm.
Re: (ii) Arithmetic. In the program from Example 3 we omitted the definition
of the between relation. Conforming to the declarative character of logic programming we would expect that this relation is defined by the rule
between(X, Y, Z) X Z, Z Y.
Unfortunately, this definition is appropriate only for a very limited use of the
between relation, namely for the calls of the form between(s, t, u) where s,t,u
are integers. With this use of the between relation in Example 3 only queries of
the form sol(n, z), where n is an integer, can be computed and the queries of
the form sol(N, Z) lead to an error. The reason is that the relation can be
used only when both of its arguments evaluate to numbers, and when one of its
arguments is a variable an error arises.
It turns out that instead of something remotely resembling the above rule we
actually need the following program:
between(X, Y, Z) X Y, Z is X.
between(X, Y, Z) X < Y, X1 is X+1, between(X1, Y, Z).
This small program is disappointingly complicated. Without going into the
details let us just explain that if X Y, then the value of X is assigned to Z, and
if additionally X < Y then the procedure is called recursively with X incremented
by 1. This increment of X necessitates an introduction of a fresh variable, here X1.
The name X1 is chosen to indicate that it is related to the variable X.
This program is not easy to understand, among others because of the use of
another variable, X1, and due to the elaborated procedure used to generate the
appropriate values. Moreover, it is still easy to end up with an error if one uses
the relation between with wrong arguments:
| ?- between(X, X+10, Z).
{INSTANTIATION ERROR: in expression}
This error is again caused by the fact that the arithmetic comparison relations
require arguments that evaluate to numbers. This shows that arithmetic in Prolog
is not simple to use.
In contrast, in Pascal the integers between the integers x and y are generated
by the for loop:
11
for i := x to y do . . .
which looks like a simpler solution.
Re: (iii) Inefficiency. Declarative programming allows us to write programs
which are easy to understand and develop. In fact, they are executable specifications. But this way of programming does not take into account the efficiency.
And indeed it is very easy to end up with Prolog programs which are hopelessly
inefficient. A good example is the following definition of sorting of a list of numbers:
sorted(List, Out) permutation(List, Out), ordered(Out).
Here permutation is a relation that generates all permutations of a list and
ordered is a relation that tests whether a list of numbers is ordered. We omit
here their definitions. Declaratively, this rule has an obvious meaning: to sort a
list List it suffices to find a permutation Out of it which is ordered. Procedurally,
this rule successively generates all permutations of List until one is found which
is ordered. This results in a hopelessly inefficient behaviour. In the worst case one
can end up generating all the permutations of a list before terminating with the
desired result.
The inefficiency of the above program has of course been well recognized in
the logic programming community and in fact this program is routinely used as
a computing intensive benchmark program aimed at comparing the efficiency of
various implementations.
It is easy to generate other examples of inefficient Prolog programs by simply
translating some definitions into programs. These examples show that in order to
write efficient programs in Prolog it is not in general sufficient to think declaratively. Some understanding of the underlying procedural interpretation is needed.
1.5
This understanding can be acquired by a systematic study of the underlying computational mechanism of Prolog. This mechanism for a pure subset of Prolog
can be precisely explained and clarified by means of the basic notions of logic
programming. These concepts are unification, logic programs and queries, the
SLD-resolution and semantics. Their study is of independent interest.
Unification is a basic mechanism by means of which values are assigned in logic
programming to variables. Logic programs and queries are counterparts of the
Prolog programs and Prolog queries that were informally introduced in Section 1.3.
The SLD-resolution is a proof method built upon the unification that allows us to
prove queries from a program in a constructive way. Constructive means here
that the values for the variables are in effect computed. For example, if we pose the
query (see Example 1 in Section 1.3) connection(seattle, X) which formalizes
the question is there any city to which there is a connection from Seattle?, then
the answer is not yes but a constructive one, like X = fairbanks.
12 Introduction
Finally, to properly understand the meaning of the SLD-resolution, semantics for
logic programs and queries is introduced. The relationships between the proof theory and semantics are clarified by means of so-called soundness and completeness
results.
This is, in a nutshell, the background on logic programming which will be needed
to understand the procedural and declarative interpretation of Prolog programs
considered here.
1.6
Program Verification
The usual way of explaining that a program is correct is that it meets its specifications. This statement has a clear intention but is somewhat imprecise so we shall
be more specific in the sequel.
Correctness of programs is important, both from the point of view of software
reliability and from the point of view of software development. Program verification
is the formal activity whose aim is to ensure correctness of programs. It has a
history spanning a quarter of a century. To quote from our previous book:
The origins of [. . .] program verification can be traced back to Turing
[Tur49], but the first constructive effort should be attributed to Floyd
[Flo67], where proving correctness of flowchart programs by means of
assertions was proposed. This method was subsequently presented in
a syntax-directed manner in Hoare [Hoa69], where an axiomatic proof
method was proposed to verify simple while-programs. Hoares approach received a great deal of attention, and many Hoare-style proof
systems dealing with various programming constructs have been proposed since then.
(Apt and Olderog [AO91, page 11])
13
Declarative programming is usually considered to be a remedy for program verification. The reason is that it allows us to narrow the gap between the specifications
and the final program. This makes the task of proving program correctness simpler
and less error prone.
However, programming languages supporting declarative programming usually
do not score well on efficiency and expressiveness, and consequently they have to be
fine-tuned to cope with these additional needs. This often creates a mismatch which
is either ignored or deemed as irrelevant. In particular Prolog, or even its subset
usually called pure Prolog, differs from logic programming in many small, but
important aspects. They have to do with efficiency, need for better expressiveness
and ease of programming.
We conclude that the need for program verification does not disappear in the
case of declarative programs. In the case of Prolog the problems mentioned in
Section 1.4 have to be taken care of very much in the same way as in the case of
imperative programs.
1.7
Because of the differences between Prolog and logic programming the theory of
logic programs cannot be directly applied to reason about Prolog programs. As a
result, to properly understand Prolog programs this theory has to be appropriately
modified and revised.
We mentioned at the end of Section 1.4 that in order to write efficient Prolog programs some understanding of its procedural interpretation is necessary. However,
for simplicity, while reasoning about Prolog programs it is much more preferable
to rely on a declarative interpretation.
Unfortunately, due to several non-declarative features of Prolog, such a declarative interpretation is, to say the least, problematic. To cope with this problem we
determine a small, but expressive subset of Prolog and show that for programs written in this subset it is possible to reason about their correctness by a combination
of syntactic analysis and declarative interpretation.
In our approach we deal with all program properties mentioned in the previous
section. Thus we study:
Termination.
Now it means that the program under consideration should terminate for the
appropriate queries.
Partial correctness.
Now it means that the program under consideration should deliver correct
answers for the appropriate queries.
Absence of run-time errors.
In the case of Prolog these are:
14 Introduction
absence of the occur-check problem,
absence of errors in presence of arithmetic expressions.
The resulting framework is simple to use and readily applicable to many of the
well-known Prolog programs. Moreover, several aspects of the proposed methods
can be automated.
We believe that this possibility of rigorously verifying a substantial collection
of Prolog programs is of interest both from the practical and from the theoretical
point of view.
1.8
Let us discuss now briefly the contents of this book. You are at this stage almost
done with Chapter 1.
The theoretical framework of logic programming is explained in Chapters 2
4. In logic programming values are assigned to variables by means of certain
substitutions, called most general unifiers. The process of computing most general
unifiers is called unification. In Chapter 2 we discuss in detail the concepts needed
to understand unification and study two well-known algorithms that compute them.
Then in Chapter 3 we define the syntax of logic programs and explain how one
can compute using them. To this end we introduce the resolution method, called
SLD-resolution, define the basic concepts of SLD-derivations and SLD-trees and
provide a study of their fundamental properties.
This computational interpretation of logic programs is called procedural interpretation. It explains how logic programs compute. This interpretation is needed
both to understand properly the foundations of logic programming and to explain
the computation mechanism of Prolog.
To understand the meaning of logic programs, we define in Chapter 4 their
semantics. This interpretation of logic programs is called declarative interpretation. It explains what logic programs compute. The declarative interpretation
abstracts from the details of the computation process and focuses on the semantic
relationship between the studied notions.
The claim that logic programming supports declarative programming refers to
the ability of using the declarative interpretation instead of the procedural interpretation when developing logic programs and analyzing their behaviour. In Chapter
4 we prove the fundamental results which link these two interpretations of logic
programs, namely the Soundness Theorem and Completeness Theorem.
Thanks to its procedural interpretation logic programming can be used as a
programming language. However, to make it a viable tool for programming, the
problems of efficiency and of ease of programming have to be adequately addressed.
Prolog is a programming language based on logic programming in which these two
objectives were adequately met. Prolog goes far beyond logic programming in that
15
it offers several built-in facilities most of which cannot be explained within the
framework of logic programming.
The aim of Chapter 5 is to provide an introduction to programming in a subset
of Prolog which corresponds with logic programming. We call this subset pure
Prolog. We found it convenient to explain the essence of programming in pure
Prolog by dividing the presentation according to the domains over which computing
takes place. So, we successfully deal with finite domains and then numerals, lists,
complex domains, by which we mean domains built from some constants by means
of arbitrary function symbols and, finally, binary trees. We also summarize the
relevant aspects of programming in pure Prolog.
Chapters 68 are devoted to a study of formal properties of pure Prolog programs. To this end we use the theoretical results concerning logic programming
established in Chapters 24. These results have now to be coupled with additional
theoretical results that deal with specific program properties. In Chapter 6 we
study termination of pure Prolog programs. More specifically, by termination we
mean here finiteness of all possible derivations starting in the initial query. This
notion of termination does not depend on the ordering of the clauses in the program. We show the usefulness of the proposed method by applying it successfully
to programs studied in Chapter 5.
In Chapter 7 we study another aspect of correctness of pure Prolog programs
absence of the occur-check problem. To this end we analyze the unification process
more closely. The program analysis is based on various syntactic properties that
involve so-called modes. Informally, modes indicate which argument positions of a
relation symbol should be viewed as an input and which as an output.
We introduce here two syntactic program classes which involve modes and for
each of them establish the absence of the occur-check problem. For some programs
and queries the occur-check problem cannot be avoided. To deal with this difficulty
we propose here program transformations which allow us to insert so-called occurchecks in the program and the query under consideration.
Next, in Chapter 8 we deal with partial correctness, that is the property that a
program under consideration delivers correct answers for the queries of relevance.
As pure Prolog programs can yield several answers, partial correctness can be interpreted either as the task of determining the form of the answers to a query (such
as that all possible answers to the query member both(X, [1,2,3], [2,3,4,5]).
are contained in the set {X = 2, X = 3}), or as the task of computing all of these
answers (such as that X = 2 and X = 3 are precisely all the possible answers to
the query member both(X, [1,2,3], [2,3,4,5]).
We consider in detail both of these interpretations and provide methods that
allow us to deal with them. To this end we introduce assertions and use them to
establish the desired program properties. In addition, we identify here yet another
program property, absence of failures and propose a method allowing us to deal
with it.
After this study of verification of pure Prolog programs, in Chapter 9 we consider a larger subset of Prolog which allows us to deal with arithmetic. This is
16 Introduction
achieved by adding to the programming language of Chapter 5 Prologs arithmetic
comparison relations and the arithmetic evaluator is.
We follow here the style of presentation of Chapter 5 and present programs
written in this subset that deal successively with complex domains which now
involve integers, lists of integers and binary search trees, a subclass of the binary
trees studied in Chapter 5 which remain properly balanced.
In Chapter 10 we return to program verification by considering now programs
written in Prologs subset, which is considered in Chapter 9. We show here that
after a simple modification the methods introduced in Chapters 68 can be also
applied to pure Prolog programs with arithmetic. One new aspect of program
correctness is the possibility of run-time errors due to the presence of arithmetic
relations. To deal with it we introduce the notion of types.
Finally, in Chapter 11 we discuss various more advanced features of Prolog
and explain their meaning. These are cut, the facilities that allow us to collect all
solutions to a query, that is findall, bagof and setof, subsequently meta-variables,
negation, several built-ins that allow us to inspect, compare and decompose terms,
like functor and arg, the built-ins that allow us to inspect and modify the programs,
like clause, assert and retract, and the input/output facilities. We also illustrate
their use by presenting various Prolog programs that deal with sets, directed graphs,
non-monotonic reasoning, unification and interpreters for the subsets of Prolog
considered in Chapters 5 and 9.
Sections marked with an asterisk (*) can be omitted at the first reading.
1.9
Further Reading
For the reader interested in pursuing matters further we would like to suggest the
following books.
For a further study of Prolog: Bratko [Bra86] and Sterling and Shapiro
[SS86].
For a further study of the foundations of logic programming: Doets [Doe94]
and various survey articles in Bruynooghe et al. [BDHM94].
For an alternative approach to the foundations of logic programming and
Prologs verification, based on the use of the attribute grammars: Deransart
and Maluszy
nski [DM93].
For a different approach to the subject in which stress is put on development
of Prolog programs from specification instead of on programs verification:
Deville [Dev90].
For a more informal and more comprehensive treatment of the logic programming paradigm: Nilsson and Maluszy
nski [NM95].
Finally, the presentation of the subject is self-contained and as precise as possible.
More than 170 exercises and more than sixty Prolog programs should help the
reader to test her/his understanding of the text.
References
1.10
[AO91]
17
References
K.R. Apt and E.-R. Olderog. Verification of Sequential and Concurrent
Programs. Texts and Monographs in Computer Science, Springer-Verlag,
New York, 1991.
G. Brandreth. Everymans Classic Puzzles. J.M. Dent & Sons Ltd, Melbourne, 1985.
[Bra86]
[Dev90]
Y. Deville. Logic Programming. Systematic Program Development. International Series in Logic Programming. Addison-Wesley, Reading, MA, 1990.
[Doe94]
[DM93]
[Flo67]
R. Floyd. Assigning meaning to programs. In J.T. Schwartz, editor, Proceedings of Symposium on Applied Mathematics 19, Mathematical Aspects of
Computer Science, pages 1932. American Mathematical Society, New York,
1967.
[Hoa69]
C.A.R. Hoare. An axiomatic basis for computer programming. Communications of the ACM, 12:576580, 583, 1969.
[NM95]
[SS86]
L. Sterling and E. Shapiro. The Art of Prolog. MIT Press, Cambridge, MA,
1986.
[Tur49]
Chapter 2
Unification
In logic programming variables represent unknown values, very much like in mathematics. The values assigned to variables are terms (expressions). These values are
assigned by means of certain substitutions, called most general unifiers. The process of computing most general unifiers is called unification. So unification forms a
basic mechanism by means of which logic programs compute. This chapter provides
an introduction to its study.
The use of unification to assign values to variables forms a distinguishing feature
of logic programming and is one of the main differences between logic programming
and other programming styles. Unification was defined in Robinson [Rob65] in the
context of automated theorem proving. Its use for computing is due to Kowalski
[Kow74]. Since the seminal paper of Robinson several unification algorithms were
presented. To provide some insight into their nature we present here two of the
most known unification algorithms.
We begin by defining in the next section a language of terms. Then in Section 2.2 we introduce the substitutions and prove some basic properties of them.
Next, in Section 2.3 we define the unifiers and most general unifiers (mgus), and
in Section 2.4 prove correctness of a non-deterministic version of Robinsons unification algorithm. By specializing this algorithm we obtain in Section 2.5 the
classical Robinsons unification algorithm. In Section 2.6 we study another wellknown unification algorithm, due to Martelli and Montanari. Finally, in Section
2.7, we analyze various useful properties of mgus. We end this and every other
chapter by concluding remarks, bibliographic remarks and a list of references.
2.1
Terms
There exists a regrettable notational discrepancy between Prolog and logic programming. In Prolog variable names start with an upper case letter whereas logic
programming follows in this respect mathematical logic tradition, so variables are
18
Terms
19
variables,
function symbols,
parentheses, which are: ( and ),
comma, that is: , .
We assume that the set of variables is infinite and fixed. In contrast, the set
of function symbols may vary and in particular may be empty. Each language of
terms is thus determined by its function symbols.
Each function symbol has a fixed arity, that is the number of arguments associated with it. 0-ary function symbols are called
constants, and are denoted by a, b, c, d, . . ..
Throughout the book we denote function symbols of positive arity by f, g, h, k, l, . . ..
Sometimes, one assumes that function symbols have positive arity and constants
are introduced as a separate class of symbols. The approach we take here is slightly
more convenient.
Terms are defined inductively as follows:
a variable is a term,
if f is an n-ary function symbol and t1 , . . . , tn are terms, then f (t1 , . . . , tn ) is
a term.
In particular every constant is a term. Terms are denoted by s, t, u, w, . . .. A
term with no variables is called ground . By Var (t) we denote the set of variables
occurring in t. By a subterm of a term s we mean a substring of s which is again
a term. If w is a subterm of s, then we say that w occurs in s. In general, there
can be several occurrences of a given subterm in a term take for example f (x)
and g(f (x), f (x)).
By definition, every term is a subterm of itself. Not surprisingly, a subterm s of
a term t is called proper if s 6= t.
20 Unification
2.2
Substitutions
Substitutions bind variables to terms. They are the only means of assigning values
to variables within the logic programming framework. The relevant substitutions
are automatically generated during the computation process, so in contrast to
the imperative programming the assignment of values to variables takes place
implicitly.
More precisely, consider now a fixed language of terms. A substitution is a finite
mapping from variables to terms which assigns to each variable x in its domain a
term t different from x. We write it as
{x1 /t1 , . . . , xn /tn }
where
x1 , . . . , xn are different variables,
t1 , . . . , tn are terms,
for i [1, n], xi 6= ti .
Informally, it is to be read the variables x1 , . . . , xn are bound to t1 , . . . , tn , respectively. A pair xi /ti is called a binding. When n = 0, the mapping becomes the
empty mapping. The resulting substitution is then called empty substitution and
is denoted by .
Consider a substitution = {x1 /t1 , . . . , xn /tn }. If all t1 , . . . , tn are ground, then
is called ground, and if all t1 , . . ., tn are variables, then is called a pure variable
substitution. If is a 1-1 and onto mapping from its domain to itself, then is called
a renaming. In other words, a substitution is a renaming if it is a permutation
of the variables from its domain. For example {x/y, y/z, z/x} is a renaming. In
addition, the empty substitution is a renaming.
Further, we denote by Dom() the set of variables {x1 , . . . , xn }, by Range() the
set of terms {t1 , . . . , tn }, and by Ran() the set of variables appearing in t1 , . . . , tn .
Then we define Var () = Dom() Ran(). Given a set of variables V we denote
by | V the substitution obtained from by restricting its domain to V .
We now define the result of applying a substitution to a term s, written as s,
as the result of the simultaneous replacement of each occurrence in s of a variable
from Dom() by the corresponding term in Range().
Example 2.1 Consider a language allowing us to build arithmetic expressions in
prefix form. It contains two binary function symbols, + and and infinitely
many constants: 0, 1, . . .. Then s = +((x, 7), (4, y)) is a term and for the
substitution = {x/0, y/ + (z, 2)} we have
s = +((0, 7), (4, +(z, 2))).
2
Substitutions
21
Exercise 1
(i) Prove that s can be equivalently defined by structural induction as follows:
for a variable x, if x Dom(), then x := (x),
for a variable x, if x 6 Dom(), then x := x,
f (t1 , . . ., tn ) := f (t1 , . . ., tn ).
In particular, if c is a constant, then c := c.
(ii) Give an example showing that without the restriction to the simultaneous replacement these two definitions of s do not need to coincide.
(iii) Prove that = iff x = x for all variables x.
The following lemma provides an alternative definition of composition of substitutions which makes it easier to compute it.
Lemma 2.3 (Composition) Consider two substitutions, := {x1 /t1 , . . . , xn /tn }
and := {y1 /s1 , . . . , ym /sm }. The composition equals the result of the following
procedure:
22 Unification
remove from the sequence
x1 /t1 , . . . , xn /tn , y1 /s1 , . . . , ym /sm
the bindings xi /ti for which xi = ti and the bindings yj /sj for which
yj {x1 , . . . , xn },
form from the resulting sequence of bindings a substitution.
2
For example, we can now easily check that for = {u/z, x/3, y/f (x, 1)} and
= {x/4, z/u} we have = {x/3, y/f (4, 1), z/u}.
Exercise 3
(i) Prove the Composition Lemma 2.3.
(ii) Prove that for every renaming there exists exactly one substitution 1 such that
1 = 1 = . Prove that 1 is also a renaming.
(iii) Prove that if = , then and are renamings.
The next lemma shows that when writing a sequence of substitutions the parentheses can be omitted.
Lemma 2.4 (Associativity)
(i) (s) = s().
(ii) () = ().
Proof.
(i) By a straightforward induction on the structure of s, using Exercise 1(i).
(ii) For a variable x we have
x()
=
{definition of composition of substitutions}
(x)
=
{(i) with s := x}
((x))
=
{(i) with s := x, := , = }
x(),
which proves the claim.
Substitutions
23
(2.1)
24 Unification
Finally, let us remark that often a different definition of renaming is used
see the exercise below. The definition used here seems to be more convenient
because it allows us to talk about renamings independently of the term to be
renamed. Consequently, one can use it in other contexts, for example when relating
substitutions in the next section.
Exercise 4 A substitution := {x1 /t1 , ..., xn /tn } is called a renaming for a term s if
t1 , . . ., tn are different variables ( is a pure variable 1-1 substitution),
{x1 , . . ., xn } Var (s) ( only affects variables of s),
(Var (s) {x1 , . . ., xn }) {t1 , . . ., tn } = ( does not introduce variables which
occur in s but are not in the domain of ).
Prove that terms s and t are variants iff there exists a renaming for s such that t = s.
2
2.3
Unifiers
We already mentioned at the beginning of this chapter that unification is a fundamental concept that is crucial for logic programming.
Informally, unification is the process of making terms identical by means of
certain substitutions. For example, the terms f (a, y, z) and f (x, b, z), with a, b
constants and x, y, z variables, can be made identical by applying to them the
substitution {x/a, y/b}: both sides then become f (a, b, z). But the substitution
{x/a, y/b, z/a} also makes these two terms identical. Such substitutions are called
unifiers. The first unifier is preferable because it is more general the second
one is a special case of the first one. More precisely, the first unifier is a most
general unifier of f (a, y, z) and f (x, b, z) while {x/a, y/b, z/a} is not.
We begin the formal presentation with the following definition needed to define
the notion of a most general unifier.
Definition 2.6 Let and be substitutions. We say that is more general than
if for some substitution we have = .
2
Thus is more general than if can be obtained from by applying to it some
substitution . Since can be chosen to be the empty substitution , we conclude
that every substitution is more general than itself.
This definition is more subtle than it seems.
Example 2.7
(i) As expected, the substitution {x/y} is more general than {x/a, y/a}, because
{x/y}{y/a} = {x/a, y/a}.
(ii) However, unexpectedly, {x/y} is not more general than {x/a}, because if for
Unifiers
25
26 Unification
of their unifiers and so is {y/g(x, a), z/b} which is more general than the first one,
since {x/c, y/g(c, a), z/b} = {y/g(x, a), z/b}{x/c}.
Actually, one can show that {y/g(x, a), z/b} is an mgu of f (g(x, a), z) and f (y, b),
in fact a strong one. In particular, we have
{x/c, y/g(c, a), z/b} = {y/g(x, a), z/b}{x/c, y/g(c, a), z/b}.
(ii) Consider the terms f (g(x, a), z) and f (g(x, b), b). They are not unifiable, since
for no substitution we have a = b.
(iii) Finally consider the terms g(x, a) and g(f (x), a). They are not unifiable either
because for any substitution the term x is a proper subterm of f (x).
2
The problem of deciding whether two terms are unifiable is called the unification
problem. This problem is solved by providing an algorithm that terminates with
failure if the terms are not unifiable and that otherwise produces one of their most
general unifiers, in fact a strong one.
In general, two terms may be not unifiable for two reasons. The first one is
exemplified by Example 2.10(ii) above which shows that two constants (or, more
generally, two terms starting with a different function symbol) cannot unify. The
second one is exemplified by (iii) above which shows that x and f (x) (or more
generally, x and a term different from x but in which x occurs) cannot unify. Each
possibility can occur at some inner level of the considered two terms.
These two possibilities for failure are present in all unification algorithms. The
second of them is called the occur-check failure. We shall discuss it in more detail
at the end of Section 2.6, after presenting unification algorithms. We present here
two such algorithms. To prove their correctness the following simple lemma will
be needed.
Lemma 2.11 (Binding) For a variable x and a term t, x = t iff = {x/t}.
Proof. First, note that x{x/t} = t, so x = t iff x = x{x/t}. Moreover, for
y 6= x we have y{x/t} = y, so y = y{x/t}. Thus x = x{x/t} iff = {x/t}. 2
Exercise 5
(i) Find two terms s and t such that s is an instance of t but s and t are not unifiable.
(ii) Prove that if s is an instance of t then s unifies with a variant of t.
2.4
g
===
==
==
=
x
z k DDD
DD
zz
z
DD
zz
D
z
z
a, 2
g, 1
DD
DD
zz
z
DD
z
DD
zz
zz
>>
>>
>>
>>
a
27
x, 1
y, 2
28 Unification
y k EEE
EE
yy
y
EE
yy
EE
y
yy
g, 1
a, 2
AA
AA
}}
}
AA
}}
AA
}}
}
}
x, 1
y ,2
y k EEE
EE
yy
y
EE
yy
EE
y
yy
g, 1
h, 2
AA
AA
}}
}
A
}
AA
}}
A
}
}}
x, 1
h ,2
b, 1
u, 1
29
30 Unification
2.5
* Robinsons Algorithm
The above algorithm can be made more efficient by searching for the disagreement
pairs in a particular order. The most natural order is the one which coincides with
the simple textual scanning of a term from left to right. When this order is used
the customary representation of a term as a string of symbols, instead of the tree
representation used before, is sufficient for the formulation of the algorithm.
The textual scanning of two terms for the first disagreement pair now locates the
first from left disagreement pair. An important observation is that after applying
a substitution determined by the so located disagreement pair to the considered
pair of terms, the obtained pair of terms coincides on all symbols to the left of the
located disagreement pair. Thus the first disagreement pair of the new pair of terms
lies textually to the right of the one previously considered. These observations lead
to the following algorithm which employs a variable ranging over substitutions
and two pointers to perform the textual scanning of two terms.
Robinsons Algorithm
set to ;
set the pointers to the first symbols of s and t;
while s 6= t do
advance simultaneously the pointers to the right until a pair of different symbols
in s and t is found;
determine the pair of terms w and u whose leading symbols are the ones identified
and perform the associated actions.
(1) w, u is a simple pair
(2) w, u is not a simple pair
od
As before, the algorithm terminates when s = t or when failure arises. The
correctness of this algorithm is a direct consequence of the correctness of the nondeterministic Robinson algorithm and of the above discussion. To illustrate the
operation of this algorithm consider the following example.
Example 2.14 To enhance readability, in all considered pairs of terms we underline the first from left pair of different symbols.
(i) Take the following pair of terms:
k(z, f (x, b, z)) and k(h(x), f (g(a), y, z)).
(2.2)
Scanning them we locate the first disagreement pair, which is z, h(x). This pair
determines the substitution {z/h(x)}. Applying it to each of the original terms we
obtain a new pair of terms, namely
k(h(x), f (x, b, h(x))) and k(h(x), f (g(a), y, h(x))).
31
The next located disagreement pair is x, g(a) which determines the substitution
{x/g(a)}. Applying this substitution to the last pair of terms we obtain a new pair
of terms:
k(h(g(a)), f (g(a), b, h(g(a)))) and k(h(g(a)), f (g(a), y, h(g(a)))).
The next located disagreement pair is b, y which determines the substitution
{y/b}. Applying it to the last pair of terms we obtain the pair
k(h(g(a)), f (g(a), b, h(g(a)))) and k(h(g(a)), f (g(a), b, h(g(a))))
of identical terms.
Now, composing the successively found substitutions we obtain
{z/h(x)}{x/g(a)}{y/b} = {z/h(g(a)), x/g(a), y/b}
which by Theorem 2.13 is an mgu of the pair (2.2).
(ii) Take the following pair of terms:
k(z, f (x, b, z)) and k(h(x), f (g(z), y, z)).
(2.3)
The only difference between this pair and the pair (2.2) is in the argument of the
function symbol g. The first disagreement pair is as before, namely z, h(x). This
pair determines the substitution {z/h(x)}. Applying it to the terms we obtain a
new pair, namely
k(h(x), f (x, b, h(x))) and k(h(x), f (g(h(x)), y, h(x))).
The next located disagreement pair is x, g(h(x)). This pair is not simple, so by
Theorem 2.13 we conclude that the pair (2.3) has no unifier.
2
2.6
32 Unification
Thus the singleton set of equations {s = t} has the same unifiers as the terms s
and t. A unifier of a set of equations E is called a most general unifier (in short
mgu) of E if it is more general than all unifiers of E and is called a strong mgu of
E if for all unifiers of E we have = .
Two sets of equations are called equivalent if they have the same set of unifiers.
A set of equations is called solved if it is of the form {x1 = t1 , ..., xn = tn } where
the xi s are distinct variables and none of them occurs in a term tj . The interest in
solved sets of equations is revealed by the following lemma.
Lemma 2.15 (Solved Form) If E := {x1 = t1 , ..., xn = tn } is solved, then the
substitution := {x1 /t1 , ..., xn /tn } is a strong mgu of E.
Proof. First note that is a unifier of E. Indeed, for i [1, n] we have xi = ti
and moreover ti = ti , since by assumption no xj occurs in ti .
Next, suppose is a unifier of E. Then for i [1, n] we have xi = ti = xi
because ti = xi and for x 6 {x1 , . . ., xn } we have x = x because x = x. Thus
= .
2
We call the unifier determined by E. To find an mgu of a set of equations it
thus suffices to transform it into an equivalent one which is solved. The following
algorithm does it if this is possible and otherwise halts with failure.
MartelliMontanari Algorithm
Nondeterministically choose from the set of equations an equation of a form below
and perform the associated action.
(1) f (s1 , ..., sn ) = f (t1 , ..., tn )
f (s1 , ..., sn ) = g(t1 , ..., tm ) where f 6= g
x=x
t = x where t is not a variable
x = t where x 6 Var (t)
and x occurs elsewhere
(6) x = t where x Var (t) and x 6= t
(2)
(3)
(4)
(5)
33
34 Unification
Proof. We establish four claims.
Claim 1 The algorithm always terminates.
Proof. In contrast to Robinsons algorithm, the proof of termination is a bit more
involved.
Given a set of equations E, we call a variable x solved in E if for some term t
we have x = t E and this is the only occurrence of x in E. We call a variable
unsolved if it is not solved.
With each set of equations E we now associate the following three functions:
uns(E)
lf un(E)
card(E)
We claim that each successful action of the algorithm reduces the triple of natural
numbers
(uns(E), lf un(E), card(E))
in the lexicographic ordering 3 .
Indeed, no action turns a solved variable into an unsolved one, so uns(E) never
increases. Further, action (1) decreases lf un(E) by 1, action (3) does not change
lf un(E) and decreases card(E) by 1, action (4) decreases lf un(E) by at least 1
and action (5) reduces uns(E) by 1.
2
The termination is now the consequence of the well-foundedness of 3 .
Claim 2 Each action replaces the set of equations by an equivalent one.
Proof. The claim holds for action (1) because for all we have f (s1 , ..., sn ) =
f (t1 , ..., tn ) iff for i [1, n] it holds that si = ti . For actions (3) and (4) the
claim is obvious.
For action (5) consider two sets of equations E {x = t} and E{x/t} {x = t},
where E{x/t} denotes the set obtained from E by applying to each of its equations
the substitution {x/t}.
Then
iff
iff
iff
iff
is a unifier of E {x = t}
is a unifier of E and x = t
{Binding Lemma 2.11}
{x/t} is a unifier of E and x = t
is a unifier of E{x/t} and x = t
is a unifier of E{x/t} {x = t}.
2
35
Claim 3 If the algorithm successfully terminates, then the final set of equations
is solved.
Proof. If the algorithm successfully terminates, then the actions (1), (2) and (4)
do not apply, so the left-hand side of every final equation is a variable. Moreover,
actions (3), (5) and (6) do not apply, so these variables are distinct and none of
them occurs on the right-hand side of an equation.
2
Claim 4 If the algorithm terminates with failure, then the set of equations at the
moment of failure does not have a unifier.
Proof. If the failure results by action (2), then the selected equation f (s1 , ..., sn ) =
g(t1 , ..., tm ) is an element of the current set of equations and for no we have
f (s1 , ..., sn ) = g(t1 , ..., tm ).
If the failure results by action (6), then the equation x = t is an element of the
current set of equations and for no we have x = t, because x is a proper
subterm of t.
2
These four claims and the Solved Form Lemma 2.15 imply the desired conclusion.
2
To illustrate the operation of this algorithm reconsider the pairs of terms analyzed in Example 2.14.
Example 2.17
(i) Consider the set
{k(z, f (x, b, z)) = k(h(x), f (g(a), y, z))}
(2.4)
associated with the pair of terms k(z, f (x, b, z)) and k(h(x), f (g(a), y, z)). Action
(1) applies and yields
{z = h(x), f (x, b, z) = f (g(a), y, z)}.
Choosing the second equation again action (1) applies and yields
{z = h(x), x = g(a), b = y, z = z}.
Choosing the third equation action (4) applies and yields
{z = h(x), x = g(a), y = b, z = z}.
Now, choosing the last equation action (3) applies and yields
{z = h(x), x = g(a), y = b}.
Finally, choosing the second equation action (5) applies and yields
{z = h(g(a)), x = g(a), y = b}.
36 Unification
At this stage no action applies, so by the Unification 2 Theorem 2.16 the substitution {z/h(g(a)), x/g(a), y/b} is an mgu of (2.4).
Thus the same mgu was produced here as by Robinsons algorithm in Example
2.14(i).
(ii) Consider the set
{k(z, f (x, b, z)) = k(h(x), f (g(z), y, z))}
(2.5)
associated with the pair of terms k(z, f (x, b, z)) and k(h(x), f (g(z), y, z)). Let us
try to repeat the choices made in (i). By action (1) we get the set
{z = h(x), f (x, b, z) = f (g(z), y, z)}.
Next, choosing the second equation action (1) applies again and yields
{z = h(x), x = g(z), b = y, z = z}.
Choosing the third equation action (4) applies and yields
{z = h(x), x = g(z), y = b, z = z}.
Now, choosing the fourth equation action (3) applies and yields
{z = h(x), x = g(z), y = b}.
Finally, choosing the second equation action (5) applies and yields
{z = h(g(z)), x = g(z), y = b}.
But now choosing the first equation action (6) applies and a failure arises. By the
Unification 2 Theorem 2.16 the set (2.5) has no unifier.
2
Exercise 7 Prove that every mgu produced by Robinsons algorithm can also be produced by the MartelliMontanari algorithm.
Hint. Treat the equations as a sequence instead of as a set. Always choose the first
equation from the left to which an action applies.
2
Exercise 8 Find a set of equations for which the MartelliMontanari algorithm yields
two different mgus.
2
The test x does not occur in t in action (5) is called the occur-check. This test
is also present in other unification algorithms, though in a less conspicuous form.
In Robinsons algorithm it is a part of the test whether a pair of terms is simple.
In most Prolog implementations for efficiency reasons the occur-check is omitted.
By omitting the occur-check in action (5) and deleting action (6) from the Martelli
Montanari algorithm we are still left with two options depending on whether the
substitution {x/t} is performed in t itself. If it is, then divergence can result,
because x occurs in t implies that x occurs in t{x/t}. If it is not, then an incorrect
result can be produced, as in the case of the single equation x = f (x) which yields
the substitution {x/f (x)}. None of these alternatives is desirable. In practise the
second option is chosen.
In Chapter 7 we shall study under which conditions the occur-check can be safely
omitted.
Properties of Mgus
2.7
37
Properties of Mgus
It is worthwhile to note that not all mgus are idempotent and consequently
not all mgus are strong. Indeed, take two identical terms. Then the substitution
{x/y, y/x} is an mgu, but it is not idempotent, because {x/y, y/x}{x/y, y/x} = .
To better understand the role played by the idempotent or, equivalently, strong
mgus, we introduce the following natural notion.
38 Unification
Definition 2.21 A unifier of s and t is called relevant if Var () Var (s)
Var (t).
2
Thus a relevant unifier neither uses nor introduces any new variables. A
simple inspection of the actions performed shows that all mgus produced by the
nondeterministic Robinson algorithm, Robinsons algorithm and by the Martelli
Montanari algorithm are relevant. This observation is used in the proof of the
following result which shows why the idempotent mgus are of interest.
Theorem 2.22 (Relevance) Every idempotent mgu is relevant.
Proof. First we prove that for arbitrary substitutions and
Var () Var () Var ()
(2.6)
by establishing
Dom() Dom() Ran()
(2.7)
(2.8)
and
Properties of Mgus
Proof. By the definition of an mgu and the Renaming Lemma 2.8.
39
2
Finally, the following lemma allows us to search for mgus in an iterative fashion.
Lemma 2.24 (Iteration) Let E1 , E2 be two sets of equations. Suppose that 1 is
an mgu of E1 and 2 is an mgu of E2 1 . Then 1 2 is an mgu of E1 E2 . Moreover,
if E1 E2 is unifiable then an mgu 1 of E1 exists and for any mgu 1 of E1 an
mgu 2 of E2 1 exists, as well.
Proof. If e is an equation of E1 , then it is unified by 1 , so a fortiori by 1 2 . If
e is an equation of E2 , then e1 is an equation of E2 1 . Thus e1 is unified by 2
and consequently e is unified by 1 2 . This proves that 1 2 is a unifier of E1 E2 .
Now let be a unifier of E1 E2 . By the choice of 1 there exists a substitution
1 such that = 1 1 . Thus 1 is a unifier of (E1 E2 )1 and a fortiori of E2 1 .
By the choice of 2 for some 2 we have 1 = 2 2 . Thus = 1 1 = 1 2 2 . This
proves that 1 2 is an mgu of E1 E2 .
Finally, note that if E1 E2 is unifiable, then a fortiori E1 is unifiable. Let
1 be an mgu of E1 . The previously inferred existence of 1 implies that E2 1 is
2
unifiable, so 2 exists.
Exercise 10 Prove the claim corresponding to the Iteration Lemma 2.24 with mgu
everywhere replaced by relevant mgu.
2
As a consequence we obtain the following conclusion which will be needed in the
next chapter.
Corollary 2.25 (Switching) Let E1 , E2 be two sets of equations. Suppose that
1 is an mgu of E1 and 2 is an mgu of E2 1 . Then E2 is unifiable and for every
mgu 10 of E2 there exists an mgu 20 of E1 10 such that
10 20 = 1 2 .
Moreover, 20 can be so chosen that Var (20 ) Var (E1 ) Var (10 ) Var (1 2 ).
Proof. By the Iteration Lemma 2.24 1 2 is an mgu of E1 E2 . So a fortiori E2
is unifiable. Let 10 be an mgu of E2 . Again by the Iteration Lemma 2.24 there
exists an mgu of E1 10 . Choose relevant, so that
Var () Var (E1 ) Var (10 ).
For the third time by the Iteration Lemma 2.24 10 is an mgu of E1 E2 . By
the Equivalence Lemma 2.23 for some renaming such that
Var () Var (10 ) Var (1 2 )
we have 10 = 1 2 and, again by the Equivalence Lemma 2.23, 20 := is an
mgu of E1 10 . Now 10 20 = 1 2 and
Var (20 ) Var () Var () Var (E1 ) Var (10 ) Var (1 2 ).
40 Unification
2
This corollary shows that the order of searching for mgus in an iterative fashion
can be reversed.
Exercise 11 The concept of a unifier generalizes in an obvious way to finite sets of
terms. Let T be a finite set of terms. Prove that there exists a pair of terms s, t such
that for all substitutions we have is a unifier of T iff is a unifier of s and t.
2
2.8
Concluding Remarks
In this chapter we studied substitutions and most general unifiers in detail. The
concept of a substitution in spite of its simplicity is more subtle than it seems and
is a source of a number of common errors.
For example, the procedure discussed in the Composition Lemma 2.3 is usually
formulated in a simpler way:
remove from the set {x1 /t1 , . . . , xn /tn , y1 /s1 , . . . , ym /sm } the bindings xi /ti for which xi = ti and the bindings yj /sj for which yj
{x1 , . . . , xn },
and its outcome is taken as the definition of the composition . Unfortunately, as
pointed out to us by N. Francez (private communication) this simplification is
incorrect. The problem is that the bindings x1 /t1 , . . . , xn /tn , y1 /s1 , . . . , ym /sm do
not need to be pairwise different. So when the set notation is used, the removal of
the bindings of the form yj /sj can have an undesired effect of removing an identical
binding of the form xi /ti . For example, according to the above simplification
{x/y}{y/3, x/3} equals {y/3}, whereas the procedure of the Composition Lemma
2.3 yields the correct result {x/3, y/3}.
We also noted in Example 2.7 that the relation more general than between
two substitutions is quite subtle and is in some cases counterintuitive.
In this chapter we presented two nondeterministic unification algorithms. The
fact that these algorithms are nondeterministic allows us to specialize them in
various ways. In particular a specialization of the first one yielded Robinsons
unification algorithm. However, all three algorithms are inefficient, as for some
inputs they can take an exponential time to compute their mgu.
A standard example is the following pair of two terms, where n > 0: f (x1 , . . ., xn )
and f (g(x0 , x0 ), . . ., g(xn1 , xn1 )). Define now inductively a sequence of terms
t1 , . . ., tn as follows:
t1 := g(x0 , x0 ),
ti+1 := g(ti , ti ).
Bibliographic Remarks
41
It is easy to check that {x1 /t1 , . . ., xn /tn } is then a mgu of the above two terms.
However, a simple proof by induction shows that each ti has more than 2i symbols.
This shows that the total number of symbols in any mgu of the above two terms is
exponential in their size. As the representation of terms as strings or as augmented
trees is common to all three unification algorithms presented in this chapter, we
conclude that each of these algorithms runs in exponential time.
Note that the mgu of the above two terms can be computed using n actions of
the MartelliMontanari algorithm. This shows that the number of actions used in
an execution of the MartelliMontanari algorithm is not the right measure of the
time complexity of this algorithm.
More efficient unification algorithms avoid explicit presentations of the most
general unifiers and rely on different internal representation of terms.
2.9
Bibliographic Remarks
The unification problem was introduced and solved by Robinson [Rob65] who recognized its importance for automated theorem proving. The unification problem
also appeared implicitly in the PhD thesis of Herbrand in 1930 (see [Her71, page
148]) in the context of solving term equations, but in an informal way and without
proofs. The MartelliMontanari algorithm presented in Section 2.6 is based upon
Herbrands original algorithm.
Eder [Ede85] provides a systematic account of the properties of substitutions and
unifiers. Lassez et al. [LMM88] give a tutorial presentation of various basic results
on unification. Properties of idempotent substitutions and unifiers are extensively
studied in Palamidessi [Pal90]. Efficient unification algorithms are presented in
Paterson and Wegman [PW78] and Martelli and Montanari [MM82]. A thorough
analysis of the time complexity of various unification algorithms is carried out in
Albert et al. [ACF93]. For a recent survey on unification see Baader and Siekmann
[BS94].
The Relevance Theorem 2.22 has been discovered independently by a number of
researchers. The proof given here is a modification of the one due to C. Palamidessi
(unpublished). Robinson [Rob92] provides an interesting account of the history of
the unification algorithms.
2.10
Summary
In this chapter we studied the unification problem. To this end we introduced the
basic concepts, namely
terms,
substitutions,
renamings,
42 Unification
unifiers,
most general unifiers (in short mgus),
and proved their elementary properties. Then we studied in detail three unification
algorithms:
the nondeterministic Robinson algorithm,
Robinsons algorithm,
the MartelliMontanari algorithm,
and proved various properties of mgus. In particular, we considered
idempotent mgus,
relevant mgus.
2.11
References
[ACF93] L. Albert, R. Casas, and F. Fages. Average case analysis of unification algorithms. Theoretical Computer Science, 113(1, 24):334, 1993.
[BS94]
[Ede85]
[Fit90]
[Her71]
[Kow74]
R.A. Kowalski. Predicate logic as a programming language. In J. L. Rosenfeld, editor, Information Processing 74, pages 569574. North-Holland, Amsterdam, 1974.
A. Martelli and U. Montanari. An efficient unification algorithm. ACM Transactions on Programming Languages and Systems, 4:258282, 1982.
[Pal90]
C. Palamidessi. Algebraic properties of idempotent substitutions. In Proceedings of the 17th International Colloquium on Automata, Languages and
Programming, Warwick, England, 1990. Full version available as Technical
Report TR-33/89, Dipartimento di Informatica, Universit`
a di Pisa.
References
43
[PW78]
[Rob65]
[Rob92]
Chapter 3
After this tour of unification we move on to the main topic of this book: logic
programs. We begin by defining their syntax. Logic programs compute by means of
the resolution method, called SLD-resolution. Unification forms a basic ingredient
of this resolution method which explains why we have dealt with it first.
This computational interpretation of logic programs is called procedural interpretation. It explains how logic programs compute. The detailed knowledge of
this interpretation is needed both to understand properly the foundations of logic
programming and to explain the computation mechanism of Prolog.
At the end of Section 1.4, we have already said that the grasp of the procedural
interpretation is crucial for a proper understanding of Prolog. While Prolog differs
from logic programming, it can be naturally introduced by defining the computation mechanism of logic programs first and then by explaining the differences.
This process has the additional advantage of clarifying certain design decisions
(like the choice of the search mechanism and the omission of the occur-check in
the unification algorithm) and of shedding light on the resulting dangers (like the
possibility of divergence or the occur-check problem).
In the next section we begin by extending the language of terms, originally
defined in Section 2.1, to the language of programs. This brings us to the definition
of programs and queries. In Section 3.2 we define the SLD-derivations. These are
computations used to compute answers to a query.
In Section 3.3 we compare this computation process with computing in the imperative and functional programming style. This allows us to understand better
the specific aspects of the logic programming style. Then in Section 3.4 we study
properties of resultants, which are syntactic constructs that allow us to explain
what is proved after each step of the SLD-derivations. The results obtained are
used in Section 3.5 to prove the fundamental properties of the SLD-derivations, like
similarity w.r.t. renaming and lifting. In Section 3.6 we consider what additional
properties hold when we restrict the choice of the mgus in the SLD-derivations.
Next, in Section 3.7, we study selection rules. The results obtained are applied
44
45
in the subsequent section, 3.8, to the study of SLD-trees natural search spaces
arising in the study of the SLD-resolution.
The properties of SLD-derivations and SLD-trees established in this chapter shed
light on the computation process used in logic programming and help to understand
better the differences between logic programming and Prolog.
3.1
We extend now the language of terms to the language of programs. First, we add
to the alphabet
relation symbols denoted by p, q, r, . . .,
reversed implication, that is: .
As in the case of function symbols, we assume that the class of relation symbols
may vary. In addition, we assume that each relation symbol has a fixed arity
associated with it. When the arity is 0, the relation symbol is usually called a
propositional symbol .
Next, we define atoms, queries, clauses and programs as follows:
if p is an n-ary relation symbol and t1 , . . . , tn are terms then p(t1 , . . . , tn ) is
an atom,
a query is a finite sequence of atoms,
a clause is a construct of the form H B, where H is an atom and B is a
query; H is called its head and B its body,
a program is a finite set of clauses.
In mathematical logic it is customary to write H B as B H. The use of
reversed implication is motivated by the procedural interpretation according to
which for H := p(s) the clause H B is viewed as part of the declaration of the
relation p.
For further analysis we also introduce the following notion:
a resultant is a construct of the form A B, where A and B are queries.
Atoms are denoted by A, B, C, H, . . ., queries by Q, A, B, C, . . ., clauses by
c, d, . . ., resultants by R and programs by P . The empty query is denoted by
2. When B is empty, H B is written H and is called a unit clause.
This use of the upper case letters should not cause confusion with the use of
upper case letters for variables in Prolog programs. In fact, in Prolog programs we
shall rather use upper case letters from the end of the alphabet. From the context
it will be always clear to what syntactic entity we refer to.
Another regrettable, terminological difference between logic programming and
Prolog is that the word atom has a completely different meaning in the context of
3.2
SLD-derivations
Informally, the computation process within the logic programming framework can
be explained as follows. A program P can be viewed as a set of axioms and a query
SLD-derivations
47
(A, B, C)
A, B, C =
c
and call it an SLD-derivation step. H B is called its input clause. If the clause
c is irrelevant we drop a reference to it.
2
Note that the SLD-resolvent was built using a specific variant of the clause
instead of the clause itself. Thanks to it the definition of the SLD-resolvent does
not depend on the accidental choice of variables in the clause. The following
example illustrates this point.
Example 3.3 Take the query Q := p(x) and the clause c := p(f (y)) . Then
the empty query is an SLD-resolvent of Q and c. The same is the case for c :=
p(f (x)) even though the atoms p(x) and p(f (x)) do not unify.
2
We can present an SLD-derivation step in the form of a rule:
A, B, C
H B
(A, B, C)
where A, B, C and H B are variable disjoint, is an mgu of B and H and
H B is a variant of c.
Thus a resolvent of a non-empty query and a clause is obtained by the following
successive steps.
Selection: select an atom in the query,
Renaming: rename (if necessary) the clause,
Instantiation: instantiate the query and the clause by an mgu of the selected
atom and the head of the clause,
Replacement: replace the instance of the selected atom by the instance of
the body of the clause.
So the above definition of a resolvent is a generalization of the variable-free case
where the resolvents were constructed solely by means of a replacement.
By iterating SLD-derivation steps we obtain an SLD-derivation. In order to
obtain most general answers to the original query some syntactic restrictions need
to be imposed on the mgus and the input clauses used. Their impact is discussed
in Section 3.4. The formal definition is as follows.
SLD-derivations
n+1
1
Q1 Qn =
Definition 3.4 A maximal sequence Q0 =
c1
cn+1 Qn+1
derivation steps is called an SLD-derivation of P {Q0 } if
49
of SLD-
Q0 , . . ., Qn+1 , . . . are queries, each empty or with one atom selected in it,
1 , . . ., n+1 , . . . are substitutions,
c1 , . . ., cn+1 , . . . are clauses of P ,
and for every step the following condition holds:
Standardization apart: the input clause employed is variable disjoint from
the initial query Q0 and from the substitutions and the input clauses used at
earlier steps. More formally:
Var (c0i ) (Var (Q0 )
i1
[
j=1
i
for i 1, where c0i is the input clause used in the step Qi1 =
Qi .
ci
sum(x,0,x) ,
sum(x,s(y),s(z)) sum(x,y,z).
In the SLD-derivations considered below the input clauses at the level i are obtained
from the program clauses by adding the subscript i to all its variables which were
used earlier in the derivation. In this way the standardization apart condition is
satisfied.
The following is a successful SLD-derivation of the query sum(s(s(0)), s(s(0)), z):
1
2
3
sum(s(s(0)), s(0), z1 ) =
sum(s(s(0)), 0, z2 ) =
2,
sum(s(s(0)), s(s(0)), z) =
2
2
1
(3.1)
where
1 = {x/s(s(0)), y/s(0), z/s(z1)},
2 = {x2 /s(s(0)), y2 /0, z1 /s(z2 )},
3 = {x3 /s(s(0)), z2 /s(s(0))}.
The corresponding computed answer substitution is
1 2 3 | {z} = {z/s(z1 )}{z1 /s(z2 )}{z2 /s(s(0))} | {z} = {z/s(s(s(s(0))))}.
More informally, we found a value for z for which sum(s2 (0), s2 (0), z) holds,
namely s4 (0). The intermediate values of z generated in this SLD-derivation are
s(z1 ), s2 (z2 ) and s4 (0).
51
Now consider the query sum(x, y, z). Repeatedly using clause 2 we obtain the
following infinite SLD-derivation:
1
2
sum(x1 , y1 , z1 ) =
sum(x2 , y2 , z2 ) . . .
sum(x, y, z) =
2
2
where
1 = {x/x1 , y/s(y1 ), z/s(z1 )},
2 = {x1 /x2 , y1 /s(y2 ), z1 /s(z2 )},
...
(3.2)
3.3
sum(x,0) x,
sum(x,s(y)) s(sum(x,y)),
3.4
Resultants
Resultants
53
n+1
1
Q1 Qn =
Q0 =
c1
cn+1 Qn+1
(3.3)
Let for i 0
Ri := Q0 1 . . .i Qi .
We call Ri the resultant of level i of the SLD-derivation (3.3).
Example 3.10 Reconsider the SLD-derivation (3.1) of Example 3.7. It has the
following four resultants:
of level 0: sum(s(s(0)), s(s(0)), z) sum(s(s(0)), s(s(0)), z),
of level 1: sum(s(s(0)), s(s(0)), s(z1)) sum(s(s(0)), s(0), z1 ),
of level 2: sum(s(s(0)), s(s(0)), s(s(z2))) sum(s(s(0)), 0, z2 ),
of level 3: sum(s(s(0)), s(s(0)), s(s(s((0))))) 2.
2
Exercise 13 Compute the resultants of the SLD-derivation (3.2) considered in Example 3.7.
i
[
(3.4)
j=1
where 1 , . . ., n , . . . are the substitutions used. The claim then follows by standardization apart.
Base. i = 0. Obvious.
Induction step.
Note that if Ri =
Var (Q)
i+1
[
j=1
2
Exercise 14 Show that one can strengthen the claim of the Disjointness Lemma 3.11
to
Var (Rj ) Var (di+1 ) = ,
where i j 0.
1
R1 Rn =
R0 =
c1
cn+1 Rn+1
derivation step Qi =
ci+1 Qi+1 of the SLD-derivation (3.3), that is by selecting in
the antecedent Qi of Ri the same atom as in Qi , and by using the same input
clause and the same mgu. Note that the Disjointness Lemma 3.11 ensures that for
i+1
i 0 indeed Ri =
ci+1 Ri+1 .
Finally, we prove the following technical result which shows that, if the atoms are
selected in the same positions, the property of being an instance of propagates
through the derivation of resultants.
R1 and R0 =
R10 are two
Lemma 3.12 (Propagation) Suppose that R =
c
c
SLD-resultant steps such that
R is an instance of R0 ,
in R and R0 atoms in the same positions are selected.
Then R1 is an instance of R10 .
Resultants
55
(3.5)
(3.6)
For some with Var () Var (R, R0 ) we have R = R0 and for some with
Var () Var (c1 , c01 ) we have c1 = c01 . Let
R := Q A, B, C,
R0 := Q0 A0 , B 0 , C0 ,
c1 := H B,
c01 := H 0 B0 .
Then
R1 = (Q A, B, C),
R10 = (Q0 A0 , B0 , C0 )0 .
By (3.5) and (3.6) Var () Var () = , so , the union of and , is
well-defined. Moreover,
(Q0 A0 , B0 , C0 )( ) = Q0 A0 , B0 , C0 = Q A, B, C,
so
R1 = (Q0 A0 , B0 , C0 )( ).
(3.7)
Q1 and Q0 =
Q01 are two
Corollary 3.13 (Propagation) Suppose that Q =
c
c
SLD-derivation steps such that
Q is an instance of Q0 ,
in Q and Q0 atoms in the same positions are selected.
Then Q1 is an instance of Q01 .
Exercise 15
(i) Prove the Propagation Corollary 3.13.
(ii) Consider two SLD-derivation steps which differ only in the choice of the variants of
the input clause. Prove that the resulting SLD-resolvents are variants of each other. 2
Exercise 16 Analyze the proof of the Propagation Lemma 3.12 and show that its
conclusion also holds when the first SLD-resultant step is of the form R =
R1 , where
d
d is an instance of c.
2
3.5
Properties of SLD-derivations
choice
choice
choice
choice
of
of
of
of
the
the
the
the
Properties of SLD-derivations
57
1
Q1 Qn =
:= Q0 =
c1
cn+1 Qn+1
1
0
Q01 Q0n =
0 := Q00 =
c1
cn+1 Qn+1
is a lift of if
is of the same or smaller length than 0 ,
Q0 is an instance of Q00 ,
for i 0, in Qi and Q0i atoms in the same positions are selected.
Note that this definition is meaningful because in both SLD-derivations the sequence of clauses is the same, so for i 0, Qi and Q0i have the same number of
atoms.
Example 3.15 Consider the SLD-derivation (3.1) of Example 3.7 and the following successful SLD-derivation:
1
2
3
sum(x, y, z) =
sum(x1 , y1 , z1 ) =
sum(x2 , y2 , z2 ) =
2
2
2
1
(3.8)
where 3 = {x2 /x, y2 /0, z2 /x}. Note that (3.8) is a lift of the SLD-derivation (3.1),
since
(3.1) and (3.8) are of the same length,
sum(s(s(0)), s(s(0)), z) is an instance of sum(x, y, z),
in both SLD-derivations at each step the same clauses are used and atoms in
the same positions are selected.
In addition, observe that the SLD-derivation (3.2) is not a lift of the SLDderivation (3.1), because at the third step different clauses are used.
2
The following important result shows that in lifts the property of being an instance of propagates through the derivation.
Theorem 3.16 (Instance) Consider an SLD-derivation and its lift 0 . Then
for i 0, if the resultant Ri of level i of exists, then so does the resultant Ri0 of
level i of 0 and Ri is an instance of Ri0 .
Proof. It suffices to consider the corresponding derivations of resultants. The claim
then follows by induction on i using the definition of a lift and the Propagation
Lemma 3.12.
2
We now apply the Instance Theorem 3.16 to a study of SLD-derivations of queries
which are variants of each other.
n+1
1
Q1 Qn =
:= Q0 =
c1
cn+1 Qn+1
and
0
0
n+1
1
0
Q01 Q0n =
0 := Q00 =
c1
cn+1 Qn+1 .
As in the case of a lift this definition is meaningful because in both SLDderivations the sequence of the clauses used is the same. Thus two SLD-derivations
are similar if
their initial queries are variants of each other,
they have the same length,
for every SLD-derivation step
the input clauses employed are variants of each other,
atoms in the same positions are selected.
Q01 is a lift of Q =
Q1 if
SLD-derivation step Q0 =
c
c
Properties of SLD-derivations
Q is an instance of Q0 ,
in Q and Q0 atoms in the same positions are selected,
Q1 is an instance of Q01 .
59
Q0 =
Q01
c
Q =
Q1 ,
c
where the vertical arrow indicates the is more general than relation. Note that
in each step the same clause is used.
Q1
Lemma 3.21 (One Step Lifting) Consider an SLD-derivation step Q =
c
0
0
0
and a variant c of c, variable disjoint with Q. Then for some and Q1 ,
0
Q =
Q01 is a lift of Q =
Q1 .
c
c
Proof. First we establish the following observation.
Claim 1 Suppose that the atoms A and H are variable disjoint and unify. Then
A also unifies with any variant H 0 of H variable disjoint with A.
Proof. For some , such that Dom() Var (H 0 ), we have H = H 0 . Let be a
unifier of A and H. Then
A = A = H = H 0 ,
so A and H 0 unify.
1
Q2
Q =
c
for some 1 and Q2 , where c1 is the input clause used. Moreover, the same atom
A is selected in Q, both in the original and in the above SLD-derivation step.
Let H be the head of c1 . Note that A and H unify, since by the choice of c1 we
have A1 = H1 and H = H. Hence
2
Q =
Q3
c
for some 2 and Q3 , where c1 is the input clause used and A is the selected atom.
Again by Claim 1
0
Q01
Q =
c
for some 0 and Q01 , where c0 is the input clause used and A is the selected atom.
0
3.6
In Section 2.7 we observed that the mgus produced by the nondeterministic Robinson algorithm and by the MartelliMontanari algorithm are always relevant. This
fact leads to a natural restriction in the definition of the SLD-derivations in that
all the mgus used are relevant.
Can we then prove additional properties of SLD-derivations? This question is of
interest not only for the foundations of the SLD-resolution but also for the study of
Prolog. The reason is that the unification algorithms used in practically all Prolog
implementations are based on some modification of the nondeterministic Robinson
algorithm or of the MartelliMontanari algorithm.
The answer to this question is affirmative. Consider the following property of
successful SLD-derivations. Here and below we assume that the composition of
substitutions binds stronger than the restriction | Var (X) of a substitution.
Definition 3.24 Let be a successful SLD-derivation of a query Q with the c.a.s.
1 0
Q is the first step of and is the c.a.s. of the suffix of
. Suppose that Q =
0
starting at Q . We say that enjoys the composition property if
= 1 | Var (Q).
2
61
n+1
1
Q1 Qn =
:= Q0 =
c1
cn+1 Qn+1
with the sequence d1 , . . ., dn+1 , . . . of input clauses used, such that each i (i 1) is
idempotent. Then for i 1, 1 . . .i is idempotent. In particular, if is successful,
then its c.a.s. is idempotent.
Proof. We prove the claim by induction together with the statement that for i 1
i
[
(Var (dj 1 . . .i ).
(3.9)
j=1
First note that by the Relevance Theorem 2.22 each i is a relevant mgu, so for
i 1,
Var (Qi ) Var (Qi1 i ) Var (di i ).
(3.10)
So the induction base holds. For the induction step in the proof (3.9) notice that
Var (Qi+1 )
{(3.10)}
Var (Qi i+1 ) Var (di+1 i+1 )
{induction hypothesis (3.9)}
Var (Q0 1 . . .i+1 )
i
[
j=1
i+1
[
j=1
Selection Rules
63
Note 3.28 (Closure) For every selection rule the computed instances of every
query are closed under renaming.
Proof. Consider a computed instance Q0 of a query Q. Let be a renaming and n
the last mgu used in a successful SLD-derivation of Q of which Q0 is the computed
instance. By the Equivalence Lemma 2.23 n is an mgu (of the same two atoms),
as well. Now using in the last step of the original SLD-derivation n instead of n
we obtain a successful SLD-derivation via the same selection rule of which Q0 is
the computed instance.
2
Now suppose that only relevant (respectively only idempotent) mgus are used
and consider the program P := {p(f (y)) } and Q := p(x). Then the only
computed instances of Q are of the form p(f (z)), where z 6= x. But p(f (x)) is a
variant of p(f (z)), so we conclude that when we restrict our attention to relevant
(respectively idempotent) mgus, then the Closure Note 3.28 does not hold any
more.
Exercise 19 In Apt [Apt90] a different definition of SLD-derivation is used. It is
assumed there that all mgus used are relevant and the standardization apart condition
is formulated as follows:
the input clause employed is variable disjoint from the initial query Q0 and
from the input clauses used at earlier steps.
(i) Prove then that the standardization apart condition used here holds.
(ii) Prove the Disjointness Lemma 3.11 and Exercise 14 for the case of this alternative
definition.
2
3.7
Selection Rules
Let us discuss now the impact of choice (A) of Section 3.5, of the selection of an
atom in a query. It is in general dependent on the whole history of the derivation
up to the current resolvent. This motivates the following definition.
Definition 3.29
Let INIT stand for the set of initial fragments of SLD-derivations in which
the last query is non-empty. By a selection rule R we mean a function which,
when applied to an element of INIT yields an occurrence of an atom in its
last query.
Given a selection rule R, we say that an SLD-derivation is via R if all
choices of the selected atoms in are performed according to R. That is, for
each initial fragment < of ending with a non-empty query Q, R( < ) is the
selected atom of Q.
2
Exercise 20
(i) Prove the Selection Note 3.31.
(ii) Usually the following definition of a selection rule is used: it is a function which
given a non-empty query selects from it an occurrence of an atom. Give an example of
an SLD-derivation which is not via any selection rule in this sense.
2
The most natural selection rule is the leftmost selection rule according to which
always the leftmost atom is chosen in a query. This is the rule used in Prolog. The
result below shows that given a query Q, for each selection rule R the set of c.a.s.s
of the successful SLD-derivations of Q via R is the same.
First we establish the following auxiliary result which is of independent interest.
Lemma 3.32 (Switching) Consider a query Qn with two different atoms A1 and
A2 . Suppose that
n+1
n+2
1
:= Q0 =
Q1 Qn =
c1
cn+1 Qn+1 =
cn+2 Qn+2
is an SLD-derivation where
A1 is the selected atom of Qn ,
A2 n+1 is the selected atom of Qn+1 .
0
0
Then for some Q0n+1 , n+1
and n+2
0
0
n+1
n+2
= n+1 n+2 ,
there exists an SLD-derivation
0
n+1
0
n+2
1
0
Q1 Qn =
0 := Q0 =
c1
cn+2 Qn+1 =
cn+1 Qn+2
where
Selection Rules
65
(3.11)
0
Var (n+2
) Var (n+1 n+2 ) Var (A1 = H1 ) Var (A2 = H2 ).
(3.12)
and
0
0
0
= H1 , so n+2
is an mgu of A1 n+1
= H1 .
But by the standardization apart H1 n+1
Moreover, by the Disjointness Lemma 3.11 and the standardization apart
(3.13)
0
Qn =
cn+2 Qn+1 ,
Qn+2
{definition of an SLD-resolvent}
(A, B1 , B)n+1 n+2 , B2 n+2 , Cn+1 n+2
{Var (H2 B2 ) Var (n+1 ) = by standardization apart}
(A, B1 , B, B2 , C)n+1 n+2
{(3.11)}
0
0
(A, B1 , B, B2 , C)n+1
n+2
{(3.13)}
0
0
0
0
0
An+1
n+2
, B1 n+2
, (B, B2 , C)n+1
n+2
,
so
0
n+2
Q0n+1 c=
Qn+2 ,
n+1
where H1 B1 is the input clause used. This shows that
0
n+1
0
n+2
1
Q0 =
Q1 Qn c=
Q0n+1 =
c1
cn+1 Qn+2
n+2
Exercise 22 Formulate a special case of the Switching Lemma 3.32 which applies to
the SLD-derivations via the leftmost selection rule.
2
We can now prove the desired result.
Theorem 3.33 (Independence) For every successful SLD-derivation of P
{Q} and a selection rule R there exists a successful SLD-derivation 0 of P {Q}
via R such that
the c.a.s.s of and 0 are the same,
and 0 are of the same length.
Proof. Call two SLD-derivations of P {Q} equivalent if
they have the same length,
they are both successful,
their c.a.s.s are equal.
SLD-trees
67
Let
1
n
:= Q0 =Q
1 = Qn ,
where Qn = 2. Consider the least i such that in Qi the atom selected in differs
from the atom A selected by R. is successful, so for some j > 0 the instance
Ai+1 . . .i+j of A is the selected atom of Qi+j . If such an i does not exist (that is,
if is via R), then we let i := n and j := 0.
Intuitively, i is the first place where deviates from the selection rule R and
j is the delay of w.r.t. R. Informally, the following diagram illustrates the
situation:
via R
... i + j
i+j+1
3.8
SLD-trees
Finally, let us discuss the impact of choice (B) of Section 3.5, that is the choice
of the program clause applicable to the selected atom. Assume that choice (A) is
given by means of a selection rule R.
When searching for a successful derivation of a query, SLD-derivations are constructed with the aim of generating the empty query. The totality of these derivations forms a search space. One way of organizing this search space is by dividing
In other words, an SLD-tree is successful if a branch of it is a successful SLDderivation and an SLD-tree is finitely failed if all its branches are failed SLDderivations.
The SLD-trees for a given query can differ in size and form.
Example 3.36 Consider the following program PATH :
1.
2.
3.
SLD-trees
69
path(x, c)
1
{x/c, x1 /c}
success
{x/b, y/c}
path(c, c)
1
arc(c, y2 ), path(y2 , c)
fail
{x2 /c}
success
Figure 3.1 An SLD-tree
Exercise 24 Reconsider the SUMMER program considered in Example 3.1. Draw the
SLD-trees for SUMMER {happy} via the leftmost selection rule and via the rightmost
selection rule.
2
Note that it is not the case that all SLD-derivations of P {Q} via a selection
rule R are present as branches in every SLD-tree for P {Q} via R. Still, the
results of the previous sections imply a limited property of this kind.
In Definition 3.17 we defined when two SLD-derivations are similar. In an analogous way we can define when two initial fragments of SLD-derivations are similar.
Definition 3.37 We call a selection rule R variant independent if in all initial
fragments of SLD-derivations which are similar, R chooses the atom in the same
position in the last query.
2
For example, the leftmost selection rule is variant independent. However, a
selection rule which chooses the leftmost atom if the last query contains the variable
x and otherwise the rightmost atom, is not variant independent. Indeed, take the
program {p(y) p(y)}, the query p(x), q(x) and its two SLD-resolvents p(x), q(x)
and p(y), q(y). Then in the first query the first atom is chosen and in the second
query the second one.
Clearly every SLD-derivation is via a variant independent selection rule, because
we can extend the fragment of the selection rule employed in an appropriate way.
Exercise 25 Prove that every SLD-tree is via a variant independent selection rule. 2
success
{y/c, x2 /c}
arc(x, c)
{x/c, x1 /c}
{x/b}
{y/b}
arc(x, b)
fail
Figure 3.2 Another SLD-tree
Concluding Remarks
3.9
71
Concluding Remarks
3.10
Bibliographic Remarks
Summary
73
3.11
Summary
3.12
References
[LS91]
Chapter 4
To understand the meaning of logic programs, we now define their semantics. This
interpretation of logic programs explains what logic programs compute, in contrast
to the procedural interpretation which, as already noted in Chapter 3, explains how
logic programs compute. It is called declarative interpretation. So the declarative
interpretation abstracts from the details of the computation process and focuses
on the semantic relationship between the studied notions.
The claim that logic programming supports declarative programming refers to
the ability of using the declarative interpretation instead of the procedural interpretation when developing logic programs and analyzing their behaviour. As
already mentioned in Section 1.2, this reduction considerably simplifies the task of
program verification.
Let us now explain the structure of this chapter. In the next section we define
algebras, which allow us to assign a meaning (or semantics) to terms. Then, in Section 4.2 we extend algebras to interpretations, which allow us to assign a meaning
to programs, resultants and queries. In Section 4.3 we relate the procedural and
declarative interpretation of logic programs by proving the soundness of the SLDresolution. This result shows that the computed answer substitutions are correct
answer substitutions or in other words that the results computed by the successful
SLD-derivations semantically follow from the given program.
To prove a converse result we introduce first in Section 4.4 term interpretations.
The appropriate form of the completeness of SLD-resolution is then proved in
Section 4.5. In Section 4.6 we return to the term models and discuss the least term
models. To prove various characterizations of these models we establish first in
Section 4.7 the basic results on fixpoints. The appropriate characterization results
are then proved in Section 4.8.
In Section 4.9 we introduce another natural (and in fact more often studied)
class of interpretations of logic programs so-called Herbrand interpretations
and provide in Section 4.10 various characterizations of the least Herbrand models,
analogous to those of the least term models.
75
4.1
Algebras
We begin by defining the meaning of terms. An algebra (sometimes called a preinterpretation) J for a language of terms L consists of:
a non-empty set D, called the domain of J,
an assignment to each n-ary function symbol f in L of a mapping fJ from
Dn to D.
In particular, to each constant c in L an element cJ of D is assigned.
We now assign to terms elements of the domain of an algebra. We do this by
using the notion of a state. A state over the domain D is a mapping assigning each
variable an element from D. Given now a state over D, we extend its domain
to all terms, that is we assign a term t an element (t) from D, proceeding by
induction as follows:
(f (t1 , . . . , tn )) = fJ ((t1 ), . . . , (tn )).
So (f (t1 , . . . , tn )) is the result of applying the mapping fJ to the sequence of values
(already) associated by with the terms t1 , . . . , tn . Observe that for a constant c,
we have (c) = cJ , so (c) does not depend on .
Exercise 28 Prove that for a ground term t, (t) has the same value for all .
Interpretations
4.2
77
Interpretations
Next, we define the meaning of queries, resultants and clauses. To this end we
introduce the notion of an interpretation I for a language L of programs. It
consists of an algebra J with a domain D for the language of terms defined by L
extended by
an assignment, to each n-ary relation symbol p in L, of a subset pI , of Dn .
We then say that I is based on J and denote for uniformity the mapping fJ by fI .
D is considered to be the domain of I, as well.
Example 4.2 Let us extend now the language of terms considered in Example
4.1 by a binary relation <. We provide two interpretations for this language of
programs.
(i) First, we extend the algebra of Example 4.1(i) by assigning to < the set
{(a, b) | a and b are natural numbers such that a < b}.
(ii) As another interpretation consider the extension of the algebra of Example
4.1(ii) according to which the set {(a, a) | a is a real number} is assigned to <.
2
From now on we fix a language L of programs. By an expression we mean an
atom, query, resultant or a clause defined in L.
We now define a relation I |= E between an interpretation I for L, a state
over the domain of I and an expression E. Intuitively, I |= E means that E is
true when its variables are interpreted according to .
If p(t1 , . . . , tn ) is an atom, then
I |= p(t1 , . . . , tn ) iff ((t1 ), . . . , (tn )) pI ,
if A1 , . . ., An is a query, then
I |= A1 , . . ., An iff I |= Ai for i [1, n],
if A B is a resultant, then
I |= A B iff I |= A under the assumption of I |= B.
In particular, if H B is a clause, then
I |= H B iff I |= H under the assumption of I |= B,
and for a unit clause H
I |= H iff I |= H.
Finally, we say that an expression E is true in the interpretation I and write
I |= E, when for all states we have I |= E. Note that the empty query 2 is
true in every interpretation I. When E is not true in I, we write I 6|= E.
For further analysis it is useful to extend the class of considered syntactic constants by introducing for an expression E its universal closure, written as E and
its existential closure, written as E. We define their semantics by
I |= E iff for all states , I |= E,
I |= E iff for some state , I |= E.
Thus for any expression E we have I |= E iff I |= E.
Given a set S of expressions or their closures we say that an interpretation I is
a model of S if all elements of S are true in I. In particular, an interpretation I is
a model for program P if every clause from P is true in I.
Exercise 30 Consider the language of programs discussed in Example 4.2. Take the
clause x y < z + 1 x y < z.
(i) Prove that the interpretation given in Example 4.2(i) is a model of this clause.
(ii) Prove that this clause is not true in the interpretation given in Example 4.2(ii).
Given two sets of expressions or their closures S and T , we say that S semantically implies T or that T is a semantic consequence of S, if every model of S is
also a model of T . We write then S |= T and omit the { } brackets if any of these
sets has exactly one element. S and T are semantically equivalent if both S |= T
and T |= S hold. In the sequel we assume tacitly several, easy to prove properties
of semantic consequence and semantic equivalence. The specific ones used in the
next section are listed in the following exercise.
Exercise 31 Let E, F be expressions, S, T, U sets of expressions and A, B, C queries.
Prove that
(i) E |= E, for all .
(ii) E |= E, for all .
(iii) If E and F are variants, then E |= F and F |= E.
(iv) S {E} |= E.
(v) S |= T and T |= U implies S |= U .
(vi) If E |= F , then S |= E implies S |= F .
(vii) If S |= A B, then S |= A, C B, C and S |= C, A C, B for all C.
(viii) If S |= A B and S |= B C, then S |= A C.
4.3
79
Those familiar with the basics of first-order logic undoubtedly noticed that, as
already stated in Section 3.1, the above definition of the semantics allows us to
interpret the clause H B1 , . . . , Bn as the universally quantified implication
x1 . . .xk (B1 . . . Bn H),
where x1 , . . . , xk are the variables which occur in H B1 , . . . , Bn .
However, in case of queries a mismatch between the interpretation suggested in
Chapter 3 and here arises, since we now interpret the query A1 , . . . , An as
x1 . . .xk (A1 . . . An ),
where x1 , . . . , xk are the variables which occur in A1 , . . . , An , so the variables are
quantified universally and not existentially. The point is that whenever a successful SLD-derivation of the query A1 , . . . , An exists, then the computed instance
(A1 , . . . , An ) of it is semantically implied by the program. As a result the existential closure x1 . . .xk (A1 . . . An ) of this query is also semantically implied
by the program. This is the consequence of the soundness of SLD-derivation.
Let us make these claims precise. For the rest of this chapter, we fix an arbitrary
program P . We have already mentioned in Chapter 3 that when reasoning about
SLD-resolution it is useful to use resultants. In particular, the following lemma
is now helpful. It justifies the informal statements concerning resultants, made in
Section 3.4.
Lemma 4.3 (Resultant)
Corollary 4.5 (Soundness) Suppose that there exists a successful SLD-derivation of P {Q}. Then P |= Q.
2
The Soundness Theorem 4.4 motivates the following definition, which provides
a declarative counterpart of the notion of the computed answer substitution.
Definition 4.6 Suppose that P |= Q. Then | Var (Q) is called a correct answer
substitution of Q and Q is called a correct instance of Q.
2
A natural question arises whether a converse of the above corollary or of the
Soundness Theorem 4.4 can be proved, that is whether a certain form of completeness of SLD-resolution can be shown. To handle this question we introduce a
special class of interpretations of logic programs, called term interpretations. The
domain of these interpretations consists of all terms.
Term Interpretations
4.4
81
Term Interpretations
Term Interpretations
83
happy
summer
warm
summer
2
For readers familiar with the basics of the proof theory the following intuitive
explanation of implication trees can be useful. Given a program P , turn it into a
proof system by writing each clause A B1 , . . ., Bn in inst(P ) as a proof rule:
B1 , . . ., Bn
A
When n = 0 such a proof rule degenerates to an axiom. Then the implication trees
w.r.t. P coincide with the proofs of atoms in the above proof system. So an atom
has an implication tree w.r.t. P iff it is provable in this proof system.
An implication tree is a purely syntactic concept. Now we use them to construct
a specific term model of a program P .
Lemma 4.12 ( C(P ) ) The term interpretation
C(P ) := {A | A has an implication tree w.r.t. P }
is a model of P .
4.5
The Soundness Theorem 4.4 relates provability with truth. The converse relation
is provided by the completeness theorems. We establish here the strongest version
which shows the existence of successful derivations independently of the selection
rule.
Theorem 4.13 (Strong Completeness of SLD-resolution) Suppose that
P |= Q. Then for every selection rule R there exists a successful SLD-derivation
of P {Q} via R with the c.a.s. such that Q is more general than Q.
The key step in the proof of this theorem we present here is the following purely
proof-theoretical result which relates two concepts of provability that by means
of implication trees and that by means of SLD-resolution. We need an auxiliary
concept first.
Definition 4.14 Given a program P and a query Q, we say that Q is n-deep if
every atom in Q has an implication tree w.r.t. P and the total number of nodes
in these implication trees is n.
2
Thus a query is 0-deep iff it is empty.
Lemma 4.15 (Implication Tree) Suppose that Q is n-deep for some n 0.
Then for every selection rule R there exists a successful SLD-derivation of P {Q}
via R with the c.a.s. such that Q is more general than Q.
Proof. We construct by induction on i [0, n] a prefix
i
1
Q0 =Q
1 = Qi
of an SLD-derivation of P {Q} via R and a sequence of substitutions 0 , . . ., i ,
such that for the resultant Ri := Ai Qi of level i
Q = Ai i ,
Qi i is (n i)-deep.
85
(4.1)
Let be a renaming such that c is variable disjoint with Q and with the
substitutions and the input clauses used in the prefix constructed so far. Further,
let be the union of i | Var (Ri ) and ( 1 ) | Var (c). By the Disjointness Lemma
3.11 is well-defined. acts on Ri as i and on c as 1 . This implies that
B = Bi = H = H( 1 ) = (H),
so B and H unify. Define i+1 to be an mgu of B and H. Then there is i+1
such that
= i+1 i+1 .
(4.2)
Qi is of the form A, B, C. Let Qi+1 := (A, B, C)i+1 be the next resolvent in the
SLD-derivation being constructed. Then Ai i+1 Qi+1 is the resultant of level
i + 1. We have
Q
=
{induction hypothesis}
Ai i
=
{definition of }
Ai
=
{(4.2)}
Ai i+1 i+1 ,
and
Qi+1 i+1
= (A, B, C)i+1 i+1
=
{(4.2)}
(A, B, C)
=
{definition of }
Ai , B, Ci .
Exercise 35 The above exercise suggests an alternative proof of the Strong Completeness Theorem 4.13 which uses a simpler induction. Complete this alternative proof.
Hint. The details are rather delicate. Use the Composition Theorem 3.25 and Claim 1
used in its proof.
2
As a conclusion, we obtain the converse of the Soundness Corollary 4.5, which
is often referred to as the Completeness of SLD-resolution.
Corollary 4.16 (Completeness) Suppose that P |= Q. Then there exists a
successful SLD-derivation of P {Q}.
Proof. C(P ) is a model of P , so C(P ) |= Q. By Exercise 33 and the Substitution
Closure Note 4.9 for some substitution , C(P ) |= Q. So, on account of the Term
Interpretation Lemma 4.7(ii), in the terminology of the proof of Theorem 4.13 Q
is n-deep for some n 0. The result now follows by the Implication Tree Lemma
4.15.
2
87
Note that the Strong Completeness Theorem 4.13 does not allow us to conclude
that the computed and correct answer substitutions coincide. In fact, they do not.
Indeed, take P := {p(y) } and Q := p(x) and assume that the language L has a
constant, say a. Then {y/a} is a correct answer substitution, but not a c.a.s.
The Strong Completeness Theorem 4.13 is important. Together with the Soundness Theorem 4.4 it shows that a close correspondence exists between the declarative and procedural definition of logic programs independently of the chosen selection rule. However, this correspondence is not precise because, as we already have
seen, the computed and correct answer substitutions do not need to coincide.
4.6
The term model C(P ) turned out to be crucial for proving strong completeness of
the SLD-resolution. Let us investigate now its properties. Recall that we identified
each term interpretation with a set of atoms. A term model of a set of expressions
S is called the least term model of S if it is included in every other term model of
S. First, we have the following result.
Theorem 4.17 (Least Term Model) C(P ) is the least term model of P .
Proof. Let I be a term model of P . Then I is also a model of inst(P ). We
prove that A C(P ) implies I |= A by induction on the number i of nodes in the
implication tree of A w.r.t. P . The claim then follows by the Term Interpretation
Lemma 4.7(ii).
Base. i = 1. Then A is in inst(P ), so I |= A.
Induction step. Suppose A is the root of an implication tree w.r.t. P with i > 1
nodes. Then for some B1 , . . ., Bn (n > 1) the clause A B1 , . . ., Bn is in inst(P )
and every Bj (j [1, n]) has an implication tree w.r.t. P with kj < i nodes. By
induction hypothesis I |= Bj for j [1, n]. But I |= A B1 , . . ., Bn , so I |= A. 2
Exercise 36 Prove that P |= Q implies that for some substitution , P |= Q
without referring to the procedural interpretation. This provides an alternative proof of
the Completeness Corollary 4.16.
Hint. Use the above theorem.
2
Another property of the term model C(P ) shows that it is semantically equivalent
to the declarative interpretation of the program.
Theorem 4.18 (Semantic Equivalence) For an atom A we have P |= A iff
C(P ) |= A.
Proof. First we prove the following claim.
(4.3)
89
4.7
First, we need to fix the structure over which the operators are defined. We prefer
to be as general as possible and consider operators over complete partial orderings.
Let us recall here the definitions.
Consider now a partial ordering (A, v ) (for the definition of a partial ordering,
see Section 2.6). Let a A and X A. Then a is called the least element of X
if a X and a v x for all x X. Further, a is called a least upper bound of X if
x v a for all x X and moreover a is the least element of A with this property. By
antisymmetry, if a least upper bound of a set of elements exists, then it is unique.
A partial ordering (A, v ) is called complete (in short, a cpo) if A contains a
least element and if for every increasing sequence
a0 v a1 v a2 . . .
of elements from A, the set
{a0 , a1 , a2 , . . .}
has a least upper bound.
In all subsequent uses of cpos, the set A will consist of a set of subsets of a specific
domain, the least element will be the empty set, the partial ordering relation v
will coincide with the subset relation and the least upper bound operator will
coincide with the set theoretic union. So from now, in all applications, the least
element of a cpo will be denoted by , the partial ordering relation by and the
S
least upper bound operator by .
Consider now an arbitrary, but fixed cpo. We denote its elements by I, J (possibly with subscripts). Given a cpo and a set X = {In | n 0} of its elements,
S
S
S
we denote X by
n=0 In . Note that when I0 I1 . . ., the element
n=0 In is
guaranteed to exist.
Consider an operator T on a cpo. T is called monotonic if I J implies T (I)
T (J), for all I,J. T is called finitary, if for every infinite sequence I0 I1 . . .,
T(
[
n=0
In )
[
n=0
T (In )
In ) =
n=0
T (In )
n=0
holds.
Exercise 38 Prove the equivalence of these two definitions.
As already mentioned in the previous section, any I such that T (I) I is called
a pre-fixpoint of T . If T (I) = I then I is called a fixpoint of T .
We now define powers of an operator T . We put
T 0(I)
= I,
T (n + 1)(I) = T (T n(I)),
S
T (I)
=
n=0 T n(I)
and abbreviate T () to T . Informally, T n(I) is the result of the n-fold
iteration of T starting at I. Thus T n is the result of the n-fold iteration of T
starting at . By the definition of a cpo, when the sequence T n(I) for n 0 is
increasing, T (I) is guaranteed to exist.
Example 4.21 Extend the partial ordering (N, ), where N is the set of natural
numbers, by an element such that n for n 0. Note that the resulting
ordering is a cpo. Consider an operator T on this ordering defined as follows:
T (n) := n + 1 for n N ,
T () := .
Note that T is monotonic, since for m, n N , m n implies m + 1 n + 1 and
m implies m + 1 .
Further, T is continuous, since for any sequence n0 n1 . . . of natural numbers eiS
S
S
ther
n = nj for some j 0 and then
n
= T (nj ) = T (
i=0 T (ni ) =
i=0 ni ),
S i=0 i
S
Sj+1
2
or i=0 ni = and then i=0 T (ni ) = = T () = T ( i=0 ni ).
The following well-known result will be of help.
Theorem 4.22 (Fixpoint) If T is continuous, then T exists and is its least
pre-fixpoint and its least fixpoint.
Proof. First we prove by induction on n that for n 0, T n T (n + 1). The
base case is obvious and the induction step follows by the monotonicity of T .
This implies that T exists. Moreover, T is a fixpoint of T , because by
the observation just made and the continuity of T we obtain
T (T ) = T (
[
n=0
T n) =
[
n=0
T (T n),
91
so
T (T ) =
T n=
n=1
T n,
n=0
4.8
Let us now return to the UP operator. Note that the term interpretations of L
with the usual set theoretic operations and the least element form a cpo, so when
studying this operator we can apply the results of the previous section.
Lemma 4.23 (UP Operator)
(i) UP is finitary.
(ii) UP is monotonic.
Proof.
(i) Consider an infinite sequence I0 I1 . . . of term interpretations and suppose
S
that A UP (
atoms B1 , . . . , Bk , the clause A B1 , . . . , Bk
n=0 In ). Then for some
S
is in inst(P ) and {B1 , . . ., Bk } n=0 In . So for some n we have {B1 , . . ., Bk } In .
Hence A UP (In ).
(ii) Immediate by definition.
Proof. (i) is the contents of the Least Term Model Theorem 4.17 and (ii)(iv) are
immediate consequences of the UP Characterization Lemma 4.20, the UP Operator
Lemma 4.23 and the Fixpoint Theorem 4.22. In turn (v) is a consequence of the
Semantic Equivalence Theorem 4.18 and Exercise 33.
2
C(P ) |= A.
P |= A.
Every SLD-tree for P {A} contains a trivially successful SLD-derivation.
There exists a trivially successful SLD-derivation of P {A}.
Exercise 40
(i) Suppose that there exists a successful SLD-derivation of P {A} with a c.a.s. . Prove
that for every selection rule R there exists a successful SLD-derivation of P {A} via
R with the empty c.a.s.
(ii) Fix a substitution and a program P . Suppose that the query A admits a successful
SLD-derivation via the leftmost selection rule with the c.a.s. . Prove that then so does
the query A, A.
Hint. Use the Closure Note 3.28.
4.9
Herbrand Interpretations
Herbrand Interpretations
93
interpretation of a program one needs to show that this query is true in every model
of the program. An obvious generalization of the Semantic Equivalence Theorem
4.18 to arbitrary queries allows us to reduce this problem to checking that the
query is true in just one model the least term model of the program. This is an
important simplification. However, it is not completely clear how to compute this
model for specific programs. Its characterization provided by the Characterization
Theorem 4.24(iv) is apparently most useful. To this end for a given program P its
operator UP should be studied.
On the other hand, there exists an alternative approach to the problem of determining what semantically follows from a program that seems to be more convenient. It is based on the use of another class of interpretations, called Herbrand
interpretations. They are obtained by choosing as the domain the set of all ground
terms.
Assume now that the set of constants of L is not empty. By the Herbrand
universe HU L for L we mean the set of all ground terms of L. By the above
assumption HU L is non-empty. By the Herbrand base HB L for L we mean the set
of all ground atoms of L.
The Herbrand algebra for L is defined as follows:
its domain is the Herbrand universe HU L ,
if f is an n-ary function symbol in L, then it is assigned the mapping from
(HU L )n to HU L which maps the sequence t1 , . . . , tn of ground terms to the
ground term f (t1 , . . . , tn ).
Now, a Herbrand interpretation for L is an interpretation I based on the Herbrand algebra for L. Thus
if p is an n-ary relation symbol in L, then it is assigned a set pI of n-tuples
of ground terms.
So there exists exactly one Herbrand algebra for L, but there are several Herbrand
interpretations for L.
By a Herbrand model for a set S of expressions we mean a Herbrand interpretation which is a model for S. A Herbrand model of a set of expressions S is called
the least Herbrand model of S if it is included in every other Herbrand model of S.
Thus each Herbrand interpretation for L is uniquely determined by the interpretation of the relation symbols. As in the case of term interpretations, there is a
natural 1-1 correspondence between the Herbrand interpretations and the subsets
of the Herbrand base HB L which is made explicit by the mapping which assigns
to the Herbrand interpretation I the set of ground atoms
{p(t1 , . . ., tn ) | p is an n-ary relation symbol and (t1 , . . ., tn ) pI }.
This allows us to identify Herbrand interpretations for L with (possibly empty)
subsets of the Herbrand base HB L . This is what we shall do in the sequel. It
is important to realize that a set of ground atoms can now denote both a term
95
Exercise 42
(i) Prove that for a Herbrand interpretation I and an expression E
I |= E iff I |= ground(E).
(ii) Prove that this above equivalence does not hold for term interpretations.
Exercise 43 Find a set of ground atoms I, a program P and an atom A such that
when I is interpreted as a Herbrand interpretation, then P |= A,
when I is interpreted as a term interpretation, then P 6|= A.
4.10
We now study the model M(P ) in detail. The following result is a counterpart of
the Least Term Model Theorem 4.17.
Theorem 4.29 (Least Herbrand Model) M(P ) is the least Herbrand model
of P .
Proof. Let I be a Herbrand model of P . Then I is also a model of ground(P ).
We prove that A M(P ) implies I |= A by induction on the number of nodes in
the ground implication tree A w.r.t. P . The claim then follows by the Herbrand
Interpretation Lemma 4.26(ii).
Base. i = 1. Then A is in ground(P ), so I |= A.
Induction step. Suppose A is the root of a ground implication tree w.r.t. P
with i > 1 nodes. Then for some B1 , . . ., Bn (n > 1) the clause A B1 , . . ., Bn
is in ground(P ) and every Bj (j [1, n]) has a ground implication tree w.r.t. P
with kj < i nodes. By induction hypothesis I |= Bj for j [1, n]. But I |=
2
A B1 , . . ., Bn , so I |= A.
Another property of the Herbrand model M(P ) shows that for ground atoms it
is semantically equivalent to the declarative interpretation of the program.
Theorem 4.30 (Ground Equivalence) For a ground atom A we have P |= A
iff M(P ) |= A.
Proof. First we prove the following claim.
Claim If a ground atom A is true in all Herbrand models of P , then P |= A.
Proof. Let I be a model of P . By (4.3) for H B1 , . . ., Bn in ground(P ),
I |= B1 , . . ., I |= Bn implies I |= H.
(4.4)
Let now IH = {A | A is a ground atom and I |= A} denote the Herbrand interpretation corresponding to I. By (4.4) and the Herbrand Interpretation Lemma
4.26(iii) IH is a model of P , as well. Moreover, by the Herbrand Interpretation
Lemma 4.26(ii), the same ground atoms are true in I and IH . From this the claim
follows.
2
Fix now a ground atom A. By the M(P ) Lemma 4.28 M(P ) is a model of P ,
so P |= A implies M(P ) |= A. The converse implication follows by the Herbrand
Interpretation Lemma 4.26(ii), the above claim and the Least Herbrand Model
Theorem 4.29.
2
To study Herbrand models of programs, following Clark [Cla79], we now introduce an operator mapping Herbrand interpretations to Herbrand interpretations,
that is sets of ground atoms to sets of ground atoms.
Definition 4.31 Given a program P we define the immediate consequence operator TP by putting for a Herbrand interpretation I
TP (I) = {H | B (H B ground(P ), I |= B)}.
2
In particular, if A is in P , then every ground instance of A is in TP (I) for every
I. The following observation of van Emden and Kowalski [vEK76] characterizes
the Herbrand models of P by means of the operator TP and explains the interest
in this operator.
Lemma 4.32 (TP Characterization) A Herbrand interpretation I is a model of
P iff TP (I) I.
97
Proof. We have
iff
iff
I is a model of P
{the Herbrand Interpretation Lemma 4.26(ii) }
for every clause H B in ground(P ), I |= B implies H I
{definition of TP operator}
TP (I) I.
2
Thus to study Herbrand models of a program P it suffices to study the prefixpoints of its immediate consequence operator TP . First, we prove a result analogous to the UP Operator Lemma 4.23.
Lemma 4.33 (TP Operator)
(i) TP is finitary.
(ii) TP is monotonic.
Proof.
(i) Consider an infinite sequence I0 I1 . . . of Herbrand interpretations and supS
pose that H TP (
n=0 In ). Then for some B the clause H B is in ground(P )
S
and n=0 In |= B. But the latter implies that for some In , namely the one containing all the atoms of B, In |= B. So H TP (In ).
(ii) Immediate by definition.
Proof. (i) is the contents of the Least Herbrand Model Theorem 4.29 and (ii)
(iv) are immediate consequences of the TP Characterization Lemma 4.32, the TP
Operator Lemma 4.33 and the Fixpoint Theorem 4.22. Finally, (v) is a consequence
of the Ground Equivalence Theorem 4.30.
2
Note that clause (v) of the Characterization Theorem 2 4.34 provides a characterization of the semantic consequence relation P |= A for a ground atom A in
terms of ground implication trees. Another characterization of the model M(P )
can be provided using the procedural interpretation of logic programs.
Definition 4.36 A success set of a program P is the set of all ground atoms A
such that P {A} has a successful SLD-derivation.
2
The following theorem should be compared with the Success 1 Theorem 4.25.
It summarizes the relationship between the declarative interpretation based on
Herbrand models and procedural interpretation of logic programs.
Theorem 4.37 (Success 2) For a ground atom A the following are equivalent.
(i)
(ii)
(iii)
(iv)
M(P ) |= A.
P |= A.
Every SLD-tree for P {A} is successful.
A is in the success set of P .
4.11
99
We defined two models of a program the least term model and the least Herbrand
model. Each of them constitutes a natural interpretation of the meaning of the
program. So let us clarify the relationship between them. First, as an immediate
consequence of the Characterization 1 Theorem 4.24(v) and Characterization 2
Theorem 4.34(v) we obtain
M(P ) = {A | A is a ground atom and A C(P )}.
So the M(P ) model can always be reconstructed from the C(P ) model. The
converse does not hold in general as the following argument from Falaschi et
al. [FLMP93] shows. Consider the program P1 := {p(x) } and the program
P2 := {p(a) , p(b) }, both defined w.r.t. the language L which has only two
constants a, b and no function symbols. Then M(P1 ) = M(P2 ) = {p(a), p(b)}.
Moreover, p(x) C(P1 ). However, p(x) is false in the model I of P2 with the
domain {a, b, c} and pI = {a, b}. So by virtue of the Characterization 1 Theorem
4.24(v), p(x) 6 C(P2 ). This shows that the C(P ) model is not a function of the
M(P ) model.
However, in certain natural situations we can reconstruct the C(P ) model from
the M(P ) model. More precisely, we have the following interesting result due to
Maher [Mah88].
Theorem 4.38 (Reconstruction) Assume that the language L has infinitely
many constants. Then C(P ) = {A | A is an atom and M(P ) |= A}.
Proof. The inclusion C(P ) {A | A is an atom and M(P ) |= A} always holds,
since by the Characterization 1 Theorem 4.24(v)
C(P ) = {A | A is an atom and P |= A}
and by the M(P ) Lemma 4.28 M(P ) is a model of P .
To prove the converse inclusion suppose M(P ) |= A. Let x1 , . . ., xn be the
variables of A and c1 , . . ., cn distinct constants which do not appear in P or A. Let
:= {x1 /c1 , . . ., xn /cn }. Then A is ground and M(P ) |= A, so A M(P ),
that is A has a ground implication tree w.r.t. P . By replacing in this tree every
occurrence of a constant ci by xi for i [1, n] we conclude, by virtue of the choice
of the constants c1 , . . ., cn , that A has an implication tree w.r.t. P , i.e. A C(P ).
2
So, under the assumption of the above theorem,
M(P1 ) = M(P2 ) iff C(P1 ) = C(P2 ),
that is both models identify the same pairs of programs.
4.12
Concluding Remarks
In this chapter we studied the declarative interpretation of logic programs and related it to the procedural interpretation introduced in the previous chapter. The
outcome were two theorems, the Soundness Theorem 4.4 and the Strong Completeness Theorem 4.13. While the Soundness Theorem is pretty natural and its proof
is pretty routine, the Strong Completeness Theorem is quite subtle and in fact, it
is easy to end up with an incorrect formulation of it.
In fact, the following version of the Strong Completeness of SLD-resolution is
claimed in many places:
Suppose that P |= Q. Then for every selection rule R there exists a
successful SLD-derivation of P {Q} via R with the c.a.s. such that
is more general than .
Concluding Remarks
101
Exercise 47
(i) Assume the definition of an SLD-derivation given in Exercise 19 of Chapter 3. Prove
that in this case the above version of the Strong Completeness of SLD-resolution is incorrect, as well.
Hint. Take P := {p(f (y)) }, Q := p(x) and := {x/f (a)}, where x and y are distinct
variables and a is a constant, and use Example 2.7(ii).
(ii) Show that when the definition of SLD-derivation adopted here is used, the counterexample of the hint of (i) is not a counterexample.
2
A small personal story concerning the above Exercise may be of interest to the
reader. The above erroneous version of completeness of SLD-resolution was given
in an early version of Apt [Apt90], where the definition of an SLD-derivation given
in Exercise 19 is adopted. Fortunately, the counterexample given in (i), due to Ch.
Lynch and W. Snyder, was sent to us just in time at the moment of proofreading
the galley proofs. We learned that it is not a correct counterexample in the case of
the definition of SLD-derivation adopted here only five years later, while reading
Shepherdson [She94].
The Strong Completeness Theorem 4.13 allows us to draw some conclusions
about specific programs. To illustrate its use let us return to the SUM program from Example 3.7. Suppose that we checked by some means (for example using the Characterization 2 Theorem 4.34(iv) and the Declarative Semantics Corollary 4.39) that SUM |= sum(x, s(s(0)), s(s(x))). Then we can draw
conclusions about computed instances of various queries. For example, since
sum(x, s(s(0)), s(s(x))) = sum(x, y, z){y/s(s(0)), z/s(s(x))}, we conclude that
there exists a successful SLD-derivation of the query sum(x, y, z) with a c.a.s.
such that sum(x, y, z) is more general than sum(x, s(s(0)), s(s(x))).
In the case of the SUM program the selection rules are hardly of relevance,
but similar conclusions to the above ones, when applied to programs, the clauses
of which have bodies with more than one atom (like the PATH program from
Example 3.36), can additionally stipulate an arbitrary selection rule.
4.13
Bibliographic Remarks
4.14
Summary
In this chapter we studied the semantics of logic programs. To this end we introduced
algebras,
interpretations
and proved soundness of the SLD-resolution. Then we introduced
term interpretations
and used them to prove completeness of the SLD-resolution.
We also introduced
Herbrand interpretations
and provided various characterizations of the least term models and the least Herbrand models.
4.15
[Apt90]
References
K. R. Apt. Logic programming. In J. van Leeuwen, editor, Handbook of
Theoretical Computer Science, pages 493574. Vol. B, Elsevier, Amsterdam,
1990.
References
103
[AvE82]
K. R. Apt and M.H. van Emden. Contributions to the theory of logic programming. Journal of the ACM, 29(3):841862, 1982.
[Cla79]
[DF87]
P. Deransart and G. Ferrand. Programmation en logique avec negation: presentation formelle. Technical Report No. 87/3, Laboratoire dInformatique,
Departement de Mathematiques et dInformatique, Universite dOrleans,
1987.
[Doe93]
[Doe94]
H.C. Doets. From Logic to Logic Programming. The MIT Press, Cambridge,
MA, 1994.
[Hil74]
[Llo84]
[Mah88]
M. J. Maher. Equivalences of logic programs. In J. Minker, editor, Foundations of Deductive Databases and Logic Programming, pages 627658. Morgan
Kaufmann, Los Altos, CA, 1988.
[She94]
[Sig90]
[SK91]
[St
a90]
R. St
ark. A direct proof for the completeness of SLD-resolution. In B
orger,
H. Kleine B
uning, and M.M. Richter, editors, Computer Science Logic 89,
Lecture Notes in Computer Science 440, pages 382383. Springer-Verlag,
Berlin, 1990.
[vEK76]
Chapter 5
We learned in Chapter 3 that logic programs can be used for computing. This
means that logic programming can be used as a programming language. However,
to make it a viable tool for programming the problems of efficiency and of ease of
programming have to be adequately addressed. Prolog is a programming language
based on logic programming and in which these two objectives were met in an adequate way. The aim of this chapter is to provide an introduction to programming
in a subset of Prolog, which corresponds with logic programming. We call this
subset pure Prolog.
Every logic program, when viewed as a sequence instead of as a set of clauses,
is a pure Prolog program, but not conversely, because we extend the syntax by a
couple of useful Prolog features. Computing in pure Prolog is obtained by imposing
certain restrictions on the computation process of logic programming in order to
make it more efficient. All the programs presented here can be run using any
well-known Prolog system.
To provide a better insight into the programming needs, we occasionally explain
some features of Prolog which are not present in pure Prolog.
In the next section we explain various aspects of pure Prolog, including its syntax,
the way computing takes place and an interaction with a Prolog system. In the
remainder of the chapter we present several pure Prolog programs. This part is
divided according to the domains over which computing takes place. So, in Section
5.2 we give an example of a program computing over the empty domain and in
Section 5.3 we discuss programming over finite domains.
The simplest infinite domain is that of the numerals. In Section 5.4 we present a
number of pure Prolog programs computing over this domain. Then, in Section 5.5
we introduce a fundamental data structure of Prolog that of lists and provide
several classic programs that use them. In the subsequent section an example
of a program is given which illustrates Prologs use of terms to represent complex
objects. Then in Section 5.7 we introduce another important data structure that
of binary trees and present various programs computing over them. We conclude
104
Introduction
105
the chapter by trying to assess in Section 5.8 the relevant aspects of programming
in pure Prolog. We also summarize there the shortcomings of this subset of Prolog.
5.1
Introduction
5.1.1
Syntax
When presenting pure Prolog programs we adhere to the usual syntactic conventions of Prolog. So each query and clause ends with the period . and in the unit
clauses is omitted. Unit clauses are called facts and non-unit clauses are
called rules. Of course, queries and clauses can be broken over several lines. To
maintain the spirit of logic programming, when presenting the programs, instead
of Prologs :- we use here the logic programming .
By a definition of a relation symbol p in a given program P we mean the set of
all clauses of P which use p in their heads. In Prolog terminology relation symbol
is synonymous with predicate.
Strings starting with a lower-case letter are reserved for the names of function or
relation symbols. For example f stands for a constant, function or relation symbol.
Additionally, we use here integers as constants. In turn, each string starting with
a capital letter or (underscore) is identified with a variable. For example Xs is
a variable. Comment lines start by the % symbol.
There are, however two important differences between the syntax of logic programming and of Prolog which need to be mentioned here.
Ambivalent Syntax
In first-order logic and, consequently, in logic programming, it is assumed that
function and relation symbols of different arity form mutually disjoint classes of
symbols. While this assumption is rarely stated explicitly, it is a folklore postulate
in mathematical logic which can be easily tested by exposing a logician to Prolog
syntax and wait for his protests. Namely, in contrast to first-order logic, in Prolog
the same name can be used for function or relation symbols of different arity.
Moreover, the same name with the same arity can be used for function and relation
symbols. This facility is called ambivalent syntax .
A function or a relation symbol f of arity n is then referred to as f/n. So in a
Prolog program we can use both a relation symbol p/2 and function symbols p/1
and p/2 and build syntactically legal facts like p(p(a,b), [c,p(a)]).
In the presence of ambivalent syntax the distinction between function symbols
and relation symbols and, consequently, between terms and atoms, disappears
but in the context of queries and clauses it is clear which symbol refers to which
syntactic category. The ambivalent syntax facility allows us to use the same name
for naturally related function or relation symbols.
In the presence of the ambivalent syntax we need to modify the MartelliMonta-
5.1.2
Computing
We now explain the computation process used in Prolog. First of all, the leftmost
selection rule is used. To simplify the subsequent discussion we now introduce the
following terminology. By an LD-resolvent we mean an SLD-resolvent w.r.t. the
leftmost selection rule and by an LD-tree an SLD-tree w.r.t. the leftmost selection
rule. The notions of LD-resolution and LD-derivation have the expected meaning.
Next, the clauses are tried in the order in which they appear in the program text.
So the program is viewed as a sequence and not as a set of clauses. In addition, for
the efficiency reasons, the occur-check is omitted from the unification algorithm.
We adopt these choices in pure Prolog.
The Strong Completeness of SLD-resolution (Theorem 4.13) tells us that (up
to renaming) all computed answers to a given query can be found in any SLDtree. However, without imposing any further restrictions on the SLD-resolution,
the computation process of logic programming, the resulting programs can become
hopelessly inefficient even if we restrict our attention to the leftmost selection rule.
In other words, the way the answers to the query are searched for becomes crucial
from the efficiency point of view.
If we traverse an LD-tree by means of breadth-first search, that is level by level,
it is guaranteed that an answer will be found, if there is any, but this search process
Introduction
107
can take exponential time in the depth of the tree and also requires exponential
space in the depth of the tree to store all the visited nodes. If we traverse this tree
by means of a depth-first search (to be explained more precisely in a moment),
that is informally, by pursuing the search first down each branch, often the answer
can be found in linear time in the depth of the tree but a divergence may result in
the presence of an infinite branch.
In Prolog the latter alternative, that is the depth-first search, is taken. Let us
explain now this search process in more detail.
Depth-first Search
By an ordered tree we mean here a tree in which the direct descendants of every
node are totally ordered.
The depth-first search is a search through a finitely branching ordered tree, the
main characteristic of which is that for each node all of its descendants are visited
before its siblings lying to its right. In this search no edge of the tree is traversed
more than once.
Depth-first search can be described as follows. Consider a finitely branching
ordered tree all the leaves of which are marked by a success or a fail marker.
The search starts at the root of the tree and proceeds by descending to its first
descendant. This process continues as long as a node has some descendants (so it
is not a leaf).
If a leaf marked success is encountered, then this fact is reported. If a leaf marked
fail is encountered, then backtracking takes place, that is the search proceeds by
moving back to the parent node of the leaf whereupon the next descendant of this
parent node is selected. This process continues until control is back at the root
node and all of its descendants have been visited. If the depth-first search enters
an infinite path before visiting a node marked success, then a divergence results.
In the case of Prolog the depth-first search takes place on an LD-tree for the
query and program under consideration. If a leaf marked success (that is the node
with the empty query) is encountered, then the associated c.a.s. is printed and the
search is suspended. The request for more solutions (;) results in a resumption
of the search from the last node marked success until a new noded marked success
is visited. If the tree has no (more) nodes marked success, then failure is reported,
by printing the answer no.
For the LD-tree given in Figure 3.1 the depth-first search is depicted in Figure
5.1.
The above description of the depth-first search is still somewhat informal, so we
provide an alternative description of it which takes into account that in an LD-tree
the nodes lying to the right of an infinite branch will not be visited. To this end we
define the subtree of the LD-tree which consists of the nodes that will be generated
during the depth-first search. As the order of the program clauses is now taken
into account this tree will be ordered.
success
fail
success
Introduction
109
p
p
exp.
exp.
exp.
q
r
p
r
fail
p
exp.
q
s
q
r
s
fail fail
p
exp.
success
r
s
fail fail
Figure 5.2 Step-by-step construction of the Prolog tree for the program P1 and
the query p
In Figure 5.2 we show a step-by-step construction of the Prolog tree for the
query p. Note that this tree is finite and the empty query is eventually marked
with success. In other words this tree is successful.
(ii) Consider now the following program P2 :
p
p.
q
q
s
q.
r.
s.
s.
The last clause forms the only difference with the previous program. In Figure 5.3
we depict a step-by-step construction of the Prolog tree for the query p. Note that
this tree is infinite and the empty query is never visited. In this case the Prolog
tree is infinite and unsuccessful.
2
The step-by-step construction of the Prolog tree generates a sequence of consecutively selected nodes; in Figure 5.2 these are the following five queries: p,
q, r, s and 2. These nodes correspond to the nodes successively visited during the depth-first search over the corresponding LD-tree with the only difference
that the backtracking to the parent node has become invisible. Finally, note
that the unmarked leaves of a Prolog tree are never visited during the depth-first
search. Consequently, their descendants are not even generated during the depthfirst search.
exp.
exp.
exp.
q
r
p
p
q
exp.
q
r
fail
exp.
r
fail
...
s
s
Figure 5.3 Step-by-step construction of the Prolog tree for the program P2 and
the query p
To summarize, the basic search mechanism for answers to a query in pure Prolog is a depth-first search in an LD-tree. The construction of a Prolog tree is
an abstraction of this process but it approximates it in a way sufficient for our
purposes.
Note that the LD-trees can be obtained by a simple modification of the above
expansion process by disregarding the order of the descendants and applying the
expand operator to all unmarked queries each time. This summarizes in a succinct
way the difference between the LD-trees and Prolog trees.
Exercise 48 Characterize the situations in which the Prolog tree and the corresponding LD-tree coincide if the markers and the order of the descendants are disregarded.
2
So pure Prolog differs from logic programming in a number of aspects. Consequently, after explaining how to program in the resulting programming language
we shall discuss the consequences of the above choices in the subsequent chapters.
Outcomes of Prolog Computations
When considering pure Prolog programs it is important to understand what are the
possible outcomes of Prolog computations. For the sake of this discussion assume
that in LD-trees:
the descendants of each node are ordered in a way conforming to the clause
ordering,
Introduction
success
success
111
fail
...
(infinite branch)
Figure 5.4 A query diverges
fail
success
...
(infinite branch)
success
success
...
5.1.3
Pragmatics of Programming
Introduction
5.1.4
113
Here is the shortest possible description of how a Prolog system can be used. For
a more complete description the reader is referred to a language manual. The
interaction starts by typing cprolog for C-Prolog or sicstus for SICStus Prolog.
There are some small differences in interaction with these two systems which we
shall disregard. The system replies by the prompt | ?-. Now the program can
be read in by typing [file-name] followed by the period .. Assuming that the
program is syntactically correct, the system replies with the answer yes followed
by the prompt | ?-. Now a query to the program can be submitted, by typing
it with the period . at its end. The system replies are of two forms. If the
query succeeds, the corresponding computed answer substitution is printed in an
equational form; yes is used to denote the empty substitution.
At this point typing the return key terminates the computation, whereas typing
; followed by the return key is interpreted as the request to produce the next
computed answer substitution. If the query or the request to produce the next
answer (finitely) fails, no is printed. Below, we use queries both to find one
answer (the first one found by the depth-first search procedure) and to find all
answers. Finally, typing halt. finishes the interaction with the system.
Here is an example of an interaction with C-Prolog:
cprolog
C-Prolog version 1.5
| ?- [test].
% read in the file called test;
test consulted 5112 bytes 0.15 sec.
yes
% the file read in;
| ?- app([a,b], [c,d], Zs). % compute an answer to the query
% app([a,b], [c,d], Zs);
Zs = [a,b,c,d]
% an answer is produced;
yes
| ?- sum(0, s(0), Z).
Z = s(0) ;
%
%
%
%
no
| ?- halt.
% no more solutions;
% leave the system.
5.2
Prolog provides three built-in nullary relation symbols true/0, fail/0 and
repeat/0. true/0 is defined internally by the single clause:
true.
so the query true always succeeds. fail/0 has the empty definition, so the query
fail always fails. Finally, repeat/0 is internally defined by the following clauses:
repeat.
repeat repeat.
The qualification built-in means that these relations cannot be redefined, so
clauses, the heads of which refer to the built-in relations, are ignored. In more
modern versions of Prolog, like SICStus Prolog, a warning is issued in case such
an attempt at redefining a built-in relation is encountered.
Exercise 50
(i) Draw the LD-tree and the Prolog tree for the query repeat, fail.
(ii) The command write(a) of Prolog prints the string a on the screen and the command nl produces a new line. What is the effect of the query repeat, write(a),
nl, fail? Does this query diverge or potentially diverge?
2
Finite Domains
5.3
115
Finite Domains
Slightly less trivial domains are the finite ones. With each element in the domain there corresponds a constant in the language; no other function symbols are
available.
Even such limited domains can be useful. Consider for example a simple database
providing an information which countries are neighbours. To save toner let us
consider Central America (see Figure 5.7).
Belize
Guatemala
Honduras
El Salvador
Nicaragua
Costa Rica
Panama
neighbour(nicaragua, X).
X = honduras ;
X = costa_rica ;
no
which countries have both Honduras and Costa Rica as a neighbour?
| ?- neighbour(X, honduras), neighbour(X, costa_rica).
X = nicaragua ;
no
The query neighbour(X, Y) lists all pairs of countries in the database (we omit
the listing) and, not unexpectedly, we have
| ?- neighbour(X, X).
no
Exercise 51 Formulate a query that computes all the triplets of countries which are
neighbours of each other.
2
Exercise 52
(i) Define a relation diff such that diff(X, Y) iff X and Y are different countries. How
many clauses are needed to define diff?
(ii) Formulate a query that computes all the pairs of countries that have Guatemala as
a neighbour.
2
Finite Domains
117
Some more complicated queries require addition of rules to the considered program. Consider for example the question
which countries can one reach from Guatemala by crossing one other country?
It is answered by first formulating the rule
one crossing(X, Y) neighbour(X, Z), neighbour(Z, Y), diff(X, Y).
where diff is the relation defined in Exercise 52, and adding it to the program
CENTRAL AMERICA. Now we obtain
| ?- one_crossing(guatemala, Y).
Y = honduras ;
Y = el_salvador ;
Y = nicaragua ;
no
This rule allowed us to mask the local variable Z. A variable of a clause is called
local if it occurs only in its body. One should not confuse anonymous variables
with local ones. For example, replacing in the above clause Z by would change
its meaning, as each occurrence of denotes a different variable.
Next, consider the question
which countries have Honduras or Costa Rica as a neighbour?
In pure Prolog, clause bodies are simply sequences of atoms, so we need to define
a new relation by means of two rules:
neighbour h or c(X) neighbour(X, honduras).
neighbour h or c(X) neighbour(X, costa rica).
and add them to the above program. Now we get
| ?- neighbour_h_or_c(X).
X = guatemala ;
X = el_salvador ;
5.4
Numerals
Natural numbers can be represented in many ways. Perhaps the most simple
representation is by means of a constant 0 (zero), and a unary function symbol s
(successor). We call the resulting ground terms numerals. Formally, numerals are
defined inductively as follows:
0 is a numeral,
if x is a numeral, then s(x), the successor of x, is a numeral.
Numeral
This definition directly translates into the following program:
% num(X) X is a numeral.
num(0).
num(s(X)) num(X).
Program: NUMERAL
It is easy to see that
for a numeral sn (0), where n 0, the query num(sn (0)) successfully terminates,
Numerals
119
for a ground term t which is not a numeral, the query num(t) finitely fails.
The above program is recursive, which means that its relation, num, is defined
in terms of itself. In general, recursion introduces a possibility of non-termination.
For example, consider the program NUMERAL1 obtained by reordering the clauses
of the program NUMERAL and take the query num(Y) with a variable Y. As Y unifies
with s(X), we see that, using the first clause of NUMERAL1, num(X) is a resolvent
of num(Y). By repeating this procedure we obtain an infinite computation which
starts with num(Y). Thus the query num(Y) diverges w.r.t. NUMERAL1.
In contrast, the query num(Y), when used with the original program NUMERAL,
yields a c.a.s. {Y/0}. Upon backtracking the c.a.s.s {Y/s(0)}, {Y/s(s(0))}, . . .
are successively produced. So, in the terminology of Section 5.1, the query num(Y)
produces infinitely many answers and a fortiori potentially diverges.
Exercise 55 Draw the LD-tree and the Prolog tree for the query num(Y) w.r.t. to the
programs NUMERAL and NUMERAL1.
2
So we see that termination depends on the clause ordering, which considerably
complicates an understanding of the programs. Therefore it is preferable to write
the programs in such a way that the desired queries terminate for all clause orderings, that is that these queries universally terminate.
Another aspect of the clause ordering is efficiency. Consider the query num(sn (0))
with n > 0. With the program NUMERAL1 the first clause will be successfully used
for n times, then it will fail upon which the second clause will be successfully
used. So in total n+2 unification attempts will be made. With the clause ordering
used in the program NUMERAL this number equals 2n+1, so is n+2.
Of course, the above query is not a typical one but one can at least draw
the conclusion that usually in a term there are more occurrences of the function
symbol s than of 0. Consequently, the first clause of NUMERAL succeeds less often
than the second one.
This seems to suggest that the program NUMERAL1 is more efficient than NUMERAL.
However, this discussion assumes that the clauses forming the definition of a relation are tried sequentially in the order they appear in the program. In most
implementations of Prolog this is not the case. Namely an indexing mechanism
is used (see e.g. At-Kaci [Ait91, pages 6572]), according to which initially the
first argument of the selected atom with a relation p is compared with the first
argument of the head of each of the clauses defining p and all incompatible clauses
are discarded. Then the gain obtained from the clause ordering in NUMERAL1 is
lost.
Addition
The program NUMERAL can only be used to test whether a term is a numeral. Let
us see now how to compute with numerals. Addition is an operation defined on
numerals by the following two axioms of Peano arithmetic (see e.g. Shoenfield
[Sho67, page 22]):
Numerals
121
Notice that we did not enforce anywhere that the arguments of the sum relation
should be terms which instantiate to numerals. Indeed, we obtain the following
expected answer
| ?- sum(a,0,X).
X = a
. . . but also an unexpected one:
| ?- sum([a,b,c],s(0),X).
X = s([a,b,c])
To safeguard oneself against such unexpected (ab)uses of SUM we need to insert
the test num(X) in the first clause, i.e. to change it to
sum(X, 0, X) num(X).
Omitting this test puts the burden on the user; including it puts the burden
on the system each time the first clause of SUM is used, the inserted test is
carried out. Note that with the sum so modified the above considered query sum(X,
s(0), Z) produces infinitely many answers, since num(X) produces infinitely many
answers. These answers are {X/0, Z/s(0)}, {X/s(0), Z/s(s(0))}, etc.
Multiplication
In Peano arithmetic, multiplication is defined by the following two axioms (see
Shoenfield [Sho67, page 22]):
x 0 = 0,
x s(y) = (x y) + x.
They translate into the following program:
Less than
Finally, the relation < (less than) on numerals can be defined by the following two
axioms:
0 < s(x),
if x < y, then s(x) < s(y).
They translate into following program:
% less(X, Y) X, Y are numerals such that X < Y.
less(0, s( )).
less(s(X), s(Y)) less(X, Y).
Program: LESS
It is worthwhile to note here that the above two axioms differ from the formalization of the < relation in Peano arithmetic, where among others the linear ordering
axiom is used:
x < y x = y y < x.
Lists
[.|.]
123
BB
BB
BB
BB
[.|.]
||
||
|
|
||
BB
BB
BB
BB
[.|.]
||
||
|
|
||
AA
AA
AA
A
[ ]
5.5
Lists
To represent sequences in Prolog we can use any constant, say 0 and any binary
function symbol, say f . Then the sequence a, b, c can be represented by the term
f (a, f (b, f (c, 0))). This representation trivially supports an addition of an element
at the front of a sequence if e is an element and s represents a sequence, then
the result of this addition is represented by f (e, s). However, other operations
on sequences, like the deletion of an element or insertion at the end have to be
programmed. A data structure which supports just one operation on sequences
an insertion of an element at the front is usually called a list.
Lists form such a fundamental data structure in Prolog that special, built-in
notational facilities for them are available. In particular, the pair consisting of a
constant [] and a binary function symbol [.|..] is used to define them. Formally,
lists are defined inductively as follows:
[] is a list,
if xs is a list, then [x | xs] is a list; x is called its head and xs is called its
tail .
[] is called the empty list.
For example, [s(0)|[]] and [0|[X|[]]] are lists, whereas [0|s(0)] is not,
because s(0) is not a list. In addition, the tree depicted in Figure 5.8 represents
the list [a|[b|[c|[]]]].
As already stated, lists can also be defined using any pair consisting of a constant
and a binary function symbol. (Often the pair nil and cons is used.) However, the
use of the above pair makes it easier to recognize when lists are used in programs.
This notation is not very readable, and even short lists become then difficult to
parse. So the following shorthands are introduced inductively for n 1:
[s0 |[s1 , ..., sn |t]] abbreviates to [s0 , s1 , ..., sn |t],
[s0 , s1 , ..., sn |[ ]] abbreviates to [s0 , s1 , ..., sn ].
Thus for example, [a|[b|c]] abbreviates to [a,b|c], and [a|[b,c|[]]] abbreviates to [a,b,c].
= [a | [b | c]].
yes
| ?- X = [a | [b, c | []]].
X = [a,b,c]
| ?- [a,b,c] = [a | [b, c | []]].
yes
To enhance the readability of the programs that use lists we incorporate the
above notation and abbreviations into pure Prolog.
The above abbreviations easily confuse a beginner. To test your understanding
of this notation please solve the following exercise.
Exercise 59 Which of the following terms are lists:
[a,b], [a|b], [a|[b|c]], [a,[b,c]], [a,[b|c]], [a|[b,c]]?
2
Further, to enhance the readability, we also use in programs the names ending
with s to denote variables which are meant to be instantiated to lists. Note that
the elements of a list need not to be ground.
We now present a pot-pourri of programs that use lists.
List
The definition of lists directly translates into the following simple program which
recognizes whether a term is a list:
% list(Xs) Xs is a list.
list([]).
list([ | Ts]) list(Ts).
Program: LIST
Lists
125
Length
The length of a list is defined inductively:
the length of the empty list [] is 0,
if n is the length of the list xs, then n+1 is the length of the list [x|xs].
This yields the following program:
% len(Xs, X) X is the length of the list Xs.
len([], 0).
len([ | Ts], s(N)) len(Ts, N).
Program: LENGTH
which can be used to compute the length of a list in terms of numerals:
| ?- len([a,b,a,d],N).
N = s(s(s(s(0))))
Less expectedly, this program can also be used to generate a list of different
variables of a given length. For example, we have:
| ?-
len(Xs, s(s(s(s(0)))) ).
Xs = [_A,_B,_C,_D]
( A, B, C, D are variables generated by the Prolog system). We shall see at the
end of this section an example of a program where such lists will be of use.
Member
Note that an element x is a member of a list l iff
x is the head of l or
x is a member of the tail of l.
This leads to the following program which tests whether an element is present in
the list:
yes
Next, we can generate all members of a list (this is a classic example from the
original C-Prolog Users Manual ):
| ?-
X = tom ;
X = dick ;
X = harry ;
no
In addition, as already mentioned in Chapter 1, we can easily find all elements
which appear in two lists:
| ?- member_both(X, [1,2,3], [2,3,4,5]).
X = 2 ;
X = 3 ;
no
Again, as in the case of SUM, some ill-typed queries may yield a puzzling answer:
| ?- member(0,[0 | s(0)]).
yes
Recall that [0 | s(0)] is not a list. A no answer to such a query can be enforced
by replacing the first clause by
member(X, [X | Xs]) list(Xs).
Lists
127
Subset
The MEMBER program is used in the following program SUBSET which tests whether
a list is a subset of another one:
% subset(Xs, Ys) each element of the list Xs is a member of the list Ys.
subset([], ).
subset([X | Xs], Ys) member(X, Ys), subset(Xs, Ys).
augmented by the MEMBER program.
Program: SUBSET
Note that multiple occurrences of an element are allowed here. So we have for
example
| ?- subset([a, a], [a]).
yes
Append
More complex lists can be formed by concatenation. The inductive definition is as
follows:
the concatenation of the empty list [] and the list ys yields the list ys,
if the concatenation of the lists xs and ys equals zs, the concatenation of
the lists [x | xs] and ys equals [x | zs].
This translates into the perhaps most often cited Prolog program:
% app(Xs, Ys, Zs) Zs is the result of concatenating the lists Xs and Ys.
app([], Ys, Ys).
app([X | Xs], Ys, [X | Zs]) app(Xs, Ys, Zs).
Program: APPEND
Note that the computation of concatenation of two lists takes linear time in the
length of the first list. Indeed, for a list of length n, n + 1 calls of the app relation
are generated. APPEND can be used not only to concatenate the lists:
| ?- app([a,b], [a,c], Zs).
Zs = [a,b,a,c]
but also to split a list in all possible ways:
Lists
129
X = b
X1s = [a]
X2s = [a,c]
Zs = [a,a,c] ;
X = a
X1s = [a,b]
X2s = [c]
Zs = [a,b,c] ;
X = c
X1s = [a,b,a]
X2s = []
Zs = [a,b,a] ;
no
To eliminate the printing of the auxiliary variables X1s and X2s we could define
a new relation by the rule
select(X, Xs, Zs) app(X1s, [X | X2s], Xs), app(X1s, X2s, Zs).
and use it in the queries.
Exercise 61 Write a program for concatenating three lists.
Select
Alternatively, we can define the deletion of an element from a list inductively. The
following program performs this task:
% select(X, Xs, Zs) Zs is the result of deleting one occurrence of X
from the list Xs.
select(X, [X | Xs], Xs).
select(X, [Y | Xs], [Y | Zs]) select(X, Xs, Zs).
Program: SELECT
Now we have
| ?- select(a, [a,b,a,c], Zs).
Zs = [b,a,c] ;
Zs = [a,b,c] ;
no
Lists
131
Ys
Xs
Figure 5.9 Prefix of a list
Ys = [we,here,are] ;
Ys = [we,are,here] ;
Ys = [are,here,we] ;
Ys = [are,we,here] ;
no
Permutation1
An alternative version uses the SELECT program to remove an element from the
list:
% perm(Xs, Ys) Ys is a permutation of the list Xs.
perm([], []).
perm(Xs, [X | Ys])
select(X, Xs, Zs),
perm(Zs, Ys).
augmented by the SELECT program.
Program: PERMUTATION1
Prefix and Suffix
The APPEND program can also be elegantly used to formalize various sublist operations. An initial segment of a list is called a prefix and its final segment is called
a suffix . Using the APPEND program both relations can be defined in a straightforward way:
% prefix(Xs, Ys) Xs is a prefix of the list Ys.
prefix(Xs, Ys) app(Xs, , Ys).
augmented by the APPEND program.
Program: PREFIX
Figure 5.9 illustrates this situation in a diagram.
Sublist
Using the prefix and suffix relations we can easily check whether one list is a
(consecutive) sublist of another one. The program below formalizes the following
definition of a sublist:
the list as is a sublist of the list bs if as is a prefix of a suffix of bs.
% sublist(Xs, Ys) Xs is a sublist of the list Ys.
sublist(Xs, Ys) app( , Zs, Ys), app(Xs, , Zs).
augmented by the APPEND program.
Program: SUBLIST
In this clause Zs is a suffix of Ys and Xs is a prefix of Zs. The diagram in Figure
5.11 illustrates this relation.
This program can be used in an expected way, for example,
| ?- sublist([2,6], [5,2,3,2,6,4]).
yes
and also in a less expected way,
Lists
133
| ?- sublist([1,X,2], [4,Y,3,2]).
X = 3
Y = 1 ;
no
Here as an effect of the call of SUBLIST both lists become instantiated so that the
first one becomes a sublist of the second one. At the end of this section we shall
see a program where this type of instantiation is used in a powerful way to solve a
combinatorial problem.
Exercise 63 Write another version of the SUBLIST program which formalizes the following definition:
the list as is a sublist of the list bs if as is a suffix of a prefix of bs.
Naive Reverse
To reverse a list, the following program is often used:
% reverse(Xs, Ys) Ys is the result of reversing the list Xs.
reverse([], []).
reverse([X | Xs], Ys) reverse(Xs, Zs), app(Zs, [X], Ys).
augmented by the APPEND program.
Program: NAIVE REVERSE
This program is very inefficient and is often used as a benchmark program. It
leads to a number of computation steps, which is quadratic in the length of the
list. Indeed, translating the clauses into recurrence relations over the length of the
lists we obtain for the first clause:
r(x + 1) = r(x) + a(x),
a(x) = x + 1,
and for the second one:
r(0) = 1.
This yields r(x) = x (x + 1)/2 + 1.
Reverse with Accumulator
A more efficient program is the following one:
% reverse(Xs, Ys) Ys is the reverse of the list Xs.
reverse(X1s, X2s) reverse(X1s, [], X2s).
% reverse(Xs, Ys, Zs) Zs is the result of concatenating
the reverse of the list Xs and the list Ys.
reverse([], Xs, Xs).
reverse([X | X1s], X2s, Ys) reverse(X1s, [X | X2s], Ys).
1
2
3
reverse([a, b, c], [ ], Ys) =
reverse([b, c], [a], Ys) =
reverse([a, b, c], Ys) =
1
3
3
4
5
reverse([c], [b, a], Ys) =
reverse([ ], [c, b, a], Ys) =
2,
3
2
reverse([a,b,a,d], [X|Ls]).
Ls = [a,b,a]
X = d
Exercise 64 Write a program which computes the last element of a list directly, without the use of other programs.
So far we have used the anonymous variables only in program clauses. They can
also be used in queries. They are not printed in the answer, so we obtain
| ?- reverse([a,b,a,d], [X|_]).
X = d
Recall, that each occurrence of is interpreted as a different variable, so we
have (compare it with the query neighbour(X, X) used on page 116)
| ?- neighbour(_,_).
yes
One has to be careful and not to confuse the use of with existential quantification. For example, note that we cannot eliminate the printing of the values of
X1s and X2s in the query
| ?- app(X1s, [X | X2s], [a,b,a,c]), app(X1s, X2s, Zs).
by replacing them by , i.e. treating them as anonymous variables, as each of
them occurs more than once in the query.
Lists
135
Palindrome
Another use of the REVERSE program is present in the following program which
tests whether a list is a palindrome.
% palindrome(Xs) the list Xs is equal to its reverse.
palindrome(Xs) reverse(Xs, Xs).
augmented by the REVERSE program.
Program: PALINDROME
For example:
| ?-
palindrome(
[t,o,o,n, n,e,v,a,d,a, n,a, c,a,n,a,d,a, v,e,n,n,o,o,t]
).
yes
It is instructive to see for which programs introduced in this section it is possible to run successfully ill-typed queries, i.e. queries which do not have lists as
arguments in the places one would expect a list from the specification. These are
SUBSET, APPEND, SELECT and SUBLIST. For other programs the ill-typed queries
never succeed, essentially because the unit clauses can succeed only with properly
typed arguments.
A Sequence
The following delightful program (see Coelho and Cotta [CC88, page 193]) shows
how the use of anonymous variables can dramatically improve the program readability. Consider the following problem: arrange three 1s, three 2s, ..., three 9s in
sequence so that for all i [1, 9] there are exactly i numbers between successive
occurrences of i.
The desired program is an almost verbatim formalization of the problem in
Prolog.
% sequence(Xs) Xs is a list of 27 elements.
sequence([ , , , , , , , , , , , , , , , , , , , , , , , , , , ]).
% question(Ss) Ss is a list of 27 elements forming the desired sequence.
question(Ss)
sequence(Ss),
sublist([1, ,1, ,1], Ss),
sublist([2, , ,2, , ,2], Ss),
sublist([3, , , ,3, , , ,3], Ss),
sublist([4, , , , ,4, , , , ,4], Ss),
sublist([5, , , , , ,5, , , , , ,5], Ss),
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,6, , , ,
, ,7, , ,
, , ,8, ,
, , , ,9,
,
,
,
,
,
,
,
,
,6], Ss),
, , ,7], Ss),
, , , , ,8], Ss),
, , , , , , ,9], Ss).
5.6
Complex Domains
By a complex domain we mean here a domain built from some constants by means
of function symbols. Of course, both numerals and lists are examples of such
domains. In this section our interest lies in domains built by means of other,
application dependent, function symbols. Such domains correspond to compound
data types in imperative and functional languages.
A Map Colouring Program
As an example consider the problem of colouring a map in such a way that no
two neighbours have the same colour. Below we call such a colouring correct. A
solution to the problem can be greatly simplified by the use of an appropriate data
representation. The map is represented below as a list of regions and colours as a
list of available colours. This is hardly surprising.
The main insight lies in the representation of regions. In the program below each
region is determined by its name, colour and the colours of its neighbours, so it is
Complex Domains
137
5.7
Binary Trees
Binary trees form another fundamental data structure. Prolog does not provide
any built-in notational facilities for them, so we adopt the following inductive
definition:
void is a (n empty) binary tree,
if left and right are trees, then tree(x, left, right) is a binary tree; x
is called its root, left its left subtree and right its right subtree.
Empty binary trees serve to fill the nodes in which no data is stored. To
visualize the trees it is advantageous to ignore their presence in the binary tree.
Thus the binary tree tree(c, void, void) corresponds to the tree with just one
node the root c, and the binary tree
tree(a, tree(b, tree(d, void, void), tree(e, void, void)),
tree(c, void, void))
can be visualized as the tree depicted in Figure 5.12. So the leaves are represented
by the terms of the form tree(s, void, void).
From now on we abbreviate binary tree to a tree and hope that no confusion
arises between a term that is a (binary) tree and the tree such a term visualizes.
The above definition translates into the following program which tests whether
a term is a tree.
Binary Trees
139
Frontier
The frontier of a tree is a list formed by its leaves. Recall that the leaves of a tree
are represented by the terms of the form tree(a, void, void). To compute a
frontier of a tree we need to distinguish three types of trees:
the empty tree, that is the term void,
a leaf, that is a term of the form tree(a, void, void),
a non-empty, non-leaf tree (in short a nel-tree), that is a term tree(x, l,
r), such that either l or r does not equal void.
We now have:
for the empty tree its frontier is the empty list,
for a leaf tree(a, void, void) its frontier is the list [a],
for a nel-tree its frontier is obtained by appending to the frontier of the left
subtree the frontier of the right subtree.
This leads to the following program in which the auxiliary relation nel tree is
used to enforce that a tree is a nel-tree:
% nel tree(t) t is a nel-tree.
nel tree(tree( , tree( , , ), )).
nel tree(tree( , , tree( , , ))).
% front(Tree, List) List is a frontier of the tree Tree.
front(void, []).
front(tree(X, void, void), [X]).
front(tree(X, L, R), Xs)
nel tree(tree(X, L, R)),
front(L, Ls),
front(R, Rs),
app(Ls, Rs, Xs).
augmented by the APPEND program.
Concluding Remarks
141
Program: FRONTIER
Note that the test nel tree(t) can also succeed for terms which are not trees,
but in the above program it is applied to trees only. In addition, observe that the
apparently simpler program
% front(Tree, List) List is a frontier of the tree Tree.
front(void, []).
front(tree(X, void, void), [X]).
front(tree( , L, R), Xs)
front(L, Ls),
front(R, Rs),
app(Ls, Rs, Xs).
augmented by the APPEND program is incorrect. Indeed, the query front(tree(X,
void, void), Xs) yields two different answers: {Xs/[X]} by means of the second
clause and {Xs/[]} by means of the third clause.
5.8
Concluding Remarks
Domain
numerals
lists
trees
5.8.1
Redundant Answers
In certain cases it is difficult to see whether redundancies will occur when generating all answers to a query. Take for instance the program SUBLIST. The list [1, 2]
has four different sublists. However, the query sublist(Xs, [1, 2]) generates in
total six answers:
Concluding Remarks
5.8.2
143
Lack of Types
5.8.3
Termination
In many programs it is not easy to see which queries are guaranteed to terminate.
Take for instance the SUBSET program. The query subset(Xs, s) for a list s
produces infinitely many answers. For example, we have
| ?- subset(Xs, [1, 2, 3]).
Xs = [] ;
Xs = [1] ;
Xs = [1,1] ;
Xs = [1,1,1] ;
Xs = [1,1,1,1] ;
etc.
Consequently, the query subset(Xs, [1, 2, 3]), len(Xs, s(s(0))), where
the len relation is defined by the LENGTH program, does not generate all subsets
of [1, 2, 3], but diverges after producing the answer Xs = [1,1].
5.9
Bibliographic Remarks
The notion of a Prolog tree is from Apt and Teusink [AT95]. Almost all programs
listed here were taken from other sources. In particular, we heavily drew on two
books on Prolog Bratko [Bra86] and Sterling and Shapiro [SS86]. The book of
Coelho and Cotta [CC88] contains a large collection of interesting Prolog programs.
The book of Clocksin and Mellish [CM84] explains various subtle points of the
language and the book of OKeefe [OK90] discusses in depth the efficiency and
pragmatics of programming in Prolog.
We shall return to Prolog in Chapters 9 and 11.
5.10
Summary
5.11
References
[Ait91] H. At-Kaci. Warrens Abstract Machine. MIT Press, Cambridge, MA, 1991.
[AT95] K.R. Apt and F. Teusink. Comparing negation in logic programming and in
Prolog. In K.R. Apt and F. Turini, editors, Meta-logics and Logic Programming,
pages 111133. The MIT Press, Cambridge, MA, 1995.
[Bra86] I. Bratko. PROLOG Programming for Artificial Intelligence. International
Computer Science Series. Addison-Wesley, Reading, MA, 1986.
[CC88] H. Coelho and J. C. Cotta. Prolog by Example. Springer-Verlag, Berlin, 1988.
[CM84] W.F. Clocksin and C.S. Mellish. Programming in Prolog. Springer-Verlag,
Berlin, second edition, 1984.
[CW93] M. Carlsson and J. Widen. SICStus Prolog Users Manual. SICS, P.O. Box
1263, S-164 28 Kista, Sweden, January 1993.
[OK90] R.A. OKeefe. The Craft of Prolog. MIT Press, Cambridge, MA, 1990.
References
145
L. Sterling and E. Shapiro. The Art of Prolog. MIT Press, Cambridge, MA,
1986.
Chapter 6
Termination
With this chapter we begin our study of the verification of pure Prolog programs.
We start by observing that the use of the leftmost selection rule combined with
the depth-first search in the resulting search trees makes pure Prolog and logic
programming different. As a consequence the completeness results linking the procedural and declarative interpretation of logic programs cannot be directly applied
to reason about the termination of pure Prolog programs. Indeed, even when the
completeness result guarantees an existence of a solution to a query, a Prolog system will miss a solution if all success nodes lie to the right of an infinite path in
the search tree, that is if, in the terminology of Section 5.1, the query diverges. So
termination is the key issue.
The aim of this chapter is to provide a method for proving the termination of
logic and pure Prolog programs. By termination we mean here finiteness of all
possible SLD-derivations of the initial query w.r.t. the leftmost selection rule.
This notion of termination is called universal termination in Section 5.1. It does
not depend on the ordering of the program clauses.
The main tool used is the multiset ordering discussed in the next section. In
Section 6.2 we fix the language in which the programs and queries are supposed to
be defined and study universal termination w.r.t. all selection rules. This sets a
useful basis for a study of universal termination w.r.t. the leftmost selection rule.
Then, in Section 6.3 we show how this form of termination can be established
for a number of pure Prolog programs and in Section 6.4 characterize the class of
pure Prolog programs that terminate in the sense studied in Section 6.2.
In Section 6.5 we move on to the study of universal termination w.r.t. the
leftmost selection rule. In Section 6.6 we illustrate the usefulness of the proposed
method by applying it successfully to programs studied in Chapter 5 and to which
the method of Section 6.2 does not apply. Then, in Section 6.7 we characterize the
class of pure Prolog programs that terminate w.r.t. the leftmost selection rule in
the sense studied in Section 6.5. Finally, in Section 6.8 we discuss the limitations
of the proposed methods and suggest their improvement.
146
Multiset Ordering
6.1
147
Multiset Ordering
In what follows we shall use a specific well-founded ordering, called the multiset
ordering. A multiset, sometimes called bag, is an unordered sequence. We denote
a multiset consisting of elements a1 , . . ., an by bag (a1 , . . ., an ).
The multiset ordering is an ordering on finite multisets of natural numbers. It is
defined as the transitive closure of the relation in which X is smaller than Y if X
can be obtained from Y by replacing an element a of Y by a finite (possibly empty)
multiset of natural numbers each of which is smaller than a. (For the definition of
the transitive closure of a relation see Section 2.6.)
In symbols, first we define the relation by
X Y iff X = Y {a} Z for some a Y and Z such that b < a for b Z,
where X, Y and Z are finite multisets of natural numbers and then define the
multiset ordering m as the transitive closure of the relation . We denote a by
old(X, Y ) and Z by new(X, Y ).
Example 6.1 Consider two multisets, bag(3, 3, 6, 7) and bag(2, 2, 2, 3, 7, 5). Then
bag(2, 2, 2, 3, 7, 5) m bag(3, 3, 6, 7), because by replacing one occurrence of 3 by
three occurrences of 2 we obtain bag(2, 2, 2, 3, 7, 5) bag(3, 3, 7, 5) and by replacing
6 by 5 we have bag(3, 3, 7, 5) bag(3, 3, 6, 7).
2
To reason about the multiset ordering we use the following classical result of
Konig [Kon27].
Lemma 6.2 (K
onig) An infinite, finitely branching tree has an infinite path.
Proof. Consider an infinite, but finitely branching tree T . We construct by induction an infinite branch
:= n0 , n1 , n2 . . .
in T such that for i 0, ni is the root of an infinite subtree of T .
Base. i = 0. As n0 we take the root of T .
Induction step. By induction hypothesis, ni is the root of an infinite subtree of
T . Since T is finitely branching, there are only finitely many children of ni . At
least one of these children is a root of an infinite subtree of T , so we take ni+1 to
2
be such a child of ni . This completes the inductive definition of .
We also need the following simple observation.
Note 6.3 (Well-foundedness) An ordering is well-founded iff its transitive closure is well-founded.
2
148 Termination
8
8
3
3
7
5
(6.1)
As the root of T we choose a natural number n larger than all the elements of m0 .
Base. i = 0. Extend T by adding all the elements of m0 as the children of n in T .
Induction step. Extend T by adding all the elements of new(mi+1 , mi ) as the
children of old(mi+1 , mi ) in T . In case new(mi+1 , mi ) is empty, no action is performed.
In the case of the multisets studied in Example 6.1 this construction is summarized in Figure 6.1, where 8 is chosen as the root of the tree.
Suppose now by contradiction that is infinite. Then infinitely often the set
new(mi+1 , mi ) is non-empty, since otherwise from some moment on the cardinality
of mi would strictly decrease. Thus infinitely often new elements were added to T ,
i.e. T is infinite. By Konigs Lemma 6.2 T has an infinite branch which contradicts
(6.1).
2
This theorem has a charming interpretation in the form of a ball game (see
Smullyan [Smu79]). Consider balls labelled with natural numbers. Suppose that
we have a box filled with a finite number of such balls. We are allowed to repeatedly
replace a ball by a finite number of balls, all with smaller numbers. For example,
10
a ball with number 100 can be replaced by 1010 balls with number 99. Then
eventually the box will be empty.
Terminating Programs
6.2
Terminating Programs
6.2.1
Syntactic Issues
149
When studying logic and pure Prolog programs we should first fix a first-order
language w.r.t. which they are analyzed. Usually, one associates with the program
the language determined by it its function and relation symbols are the ones
occurring in the program (see, e.g., Lloyd [Llo87] and Apt [Apt90]).
Another choice was made by Kunen [Kun89] who assumed a universal firstorder language L with infinitely many function and relation symbols in each arity,
in which all programs and queries are written. One can think of this language as
the language defined by a Prolog manual, so this choice better reflects the reality
of programming, In practise, of course only finitely many function and relation
symbols will be supported by an implementation.
In this chapter we follow the latter alternative. At first sight a choice of such a
universal language seems to be unusual, because only finitely many function and
relation symbols occur in every program and a query. However, for each query this
set of symbols is different and it is artificial to impose a syntactic restriction on the
queries which may be used for a given program. It is useful to add that the specific
results obtained in this chapter do not depend on this choice of the language.
All considered interpretations are interpretations for this universal language L.
In addition, all notions referring to the syntax, like ground(P ) are defined with
respect to L.
Finally, recall that in Chapter 5 we used ambivalent syntax. To be able to
deal formally with all pure Prolog programs we allow ambivalent syntax in L as
well. Thus, L is not a first-order language, but rather an extension of a first-order
language in which ambivalent syntax is used. This generalization does not lead to
any complications and allows us to use here all the formal results established in
Chapters 24.
6.2.2
Recurrent Programs
150 Termination
independently from the adopted selection rule. When studying Prolog programs,
one is actually interested in proving the termination of a given program not only
for all ground queries but also for a class of non-ground queries constituting the
intended queries. The method of proving termination considered here will allow
us to identify such a class of non-ground queries for each program. As we shall see
below, many Prolog programs, including SUM, LIST and APPEND are terminating.
To prove that a program is terminating the following concepts due to Bezem
[Bez93] and Cavedon [Cav89] play a crucial role.
Definition 6.6
A level mapping for a program P is a function | | : HB P N of ground
atoms to natural numbers. For A HB P , |A| is the level of A.
A clause of P is called recurrent with respect to a level mapping | |, if for
every ground instance A A, B, B of it
|A| > |B|.
A program P is called recurrent with respect to a level mapping | |, if all its
clauses are. P is called recurrent if it is recurrent with respect to some level
mapping.
2
First, following Bezem [Bez93], let us lift the concept of level mapping to
non-ground atoms.
Definition 6.7
An atom A is called bounded with respect to a level mapping | |, if for some
k 0 for every ground instance A0 of A we have |A0 | k. For A bounded
w.r.t. | |, we define |A|, the level of A w.r.t. | |, as the maximum | | takes
on ground(A).
A query is called bounded with respect to a level mapping | |, if all its atoms
are. For Q := A1 , . . . , An bounded w.r.t. | |, we define |Q|, the level of Q
w.r.t. | |, as the multiset bag (|A1 |, . . ., |An |). If |Ai | k for i [1, n], we say
that Q is bounded by k.
2
Strictly speaking, for an atomic query A, the notation |A| has now a double
meaning, depending whether we view A as an atom or as a query. In the sequel it
will be always clear which of these two interpretations is used.
6.2.3
We now prove that every recurrent program is terminating. To this end the concept
of boundedness is helpful, as the following lemma shows. Recall that m stands
for the multiset ordering defined in Section 6.1.
Terminating Programs
151
152 Termination
Proof. Every ground query is bounded.
These corollaries can be easily applied to various pure Prolog programs. The
level mapping can be usually defined as a simple function of the terms of the ground
atom. The following natural concept will be useful.
Define by induction a function | |, called listsize, which assigns natural numbers
to ground terms:
|[x|xs]| = |xs| + 1,
|f (x1 , . . ., xn )| = 0 if f 6= [ . | . ].
In particular, |[ ]| = 0. Note that for a list xs, |xs| equals its length.
For queries with one atom it is often easy to establish boundedness by proving
a stronger property.
Definition 6.11 Let | | be a level mapping. An atom A is called rigid w.r.t. | | if
| | is constant on the set ground(A) of ground instances of A.
2
Obviously, rigid atoms are bounded. Let us see now how the above results can
be applied to prove termination.
Example 6.12 Consider the program LIST:
% list(Xs) Xs is a list.
list([]).
list([ | Ts]) list(Ts).
Define
|list(t)| = |t|.
It is straightforward to see that LIST is recurrent w.r.t. | | and that for a list
t, the atom list(t) is rigid w.r.t. | |. By the Termination 1 Corollary 6.10 we
conclude that LIST is terminating and by the Finiteness 1 Corollary 6.9 for a list
t, all SLD-derivations of LIST {list(t)} are finite.
2
6.3
Applications
Applications
153
154 Termination
In each case we obtain different class of bounded queries. The level mapping
|app(xs, ys, zs)| = min(|xs|, |zs|)
combines the advantages of both of them. APPEND is easily seen to be recurrent
w.r.t. this level mapping and if xs is a list or zs is a list, app(xs, ys, zs)
is bounded (though not rigid). By the Termination 1 Corollary 6.10 APPEND is
terminating and by the Finiteness 1 Corollary 6.9 if xs is a list or zs is a list, all
SLD-derivations of APPEND {app(xs, ys, zs)} are finite.
Select
Next, analyze the program SELECT:
% select(X, Xs, Zs) Zs is the result of deleting one occurrence of X
from the list Xs.
select(X, [X | Xs], Xs).
select(X, [Y | Xs], [Y | Zs]) select(X, Xs, Zs).
As in the case of the APPEND program, it is more advantageous to use the level
mapping
|select(x, ys, zs)| = min(|ys|, |zs|).
Then SELECT is recurrent w.r.t | | and if ys is a list or zs is a list, all SLDderivations of SELECT {select(x, ys, zs)} are finite.
Sum
Now consider the program SUM:
% sum(X, Y, Z) X, Y, Z are numerals such that Z is the sum of X and Y.
sum(X, 0, X).
sum(X, s(Y), s(Z)) sum(X, Y, Z).
Again, it is more advantageous to use here the level mapping
|sum(x, y, z)| = min(size(y), size(z)),
where for a term t, size(t) denotes the number of symbols in t.
Then SUM is recurrent w.r.t. | | and for a ground y or z, sum(x, y, z) is
bounded w.r.t. | |. By the Termination 1 Corollary 6.10 SUM is terminating and
by the Finiteness 1 Corollary 6.9 for a ground y or z, all SLD-derivations of SUM
{sum(x, y, z)} are finite.
155
Palindrome
In some circumstances the level mappings needed to prove termination are a bit
artificial since a strict decrease of the level mapping is required from the clause
head to the atoms of the clause body. We shall return to this problem in Section
6.8. An example is the case of the program PALINDROME:
% palindrome(Xs) the list Xs is equal to its reverse.
palindrome(Xs) reverse(Xs, Xs).
% reverse(Xs, Ys) Ys is the reverse of the list Xs.
reverse(X1s, X2s) reverse(X1s, [], X2s).
% reverse(Xs, Ys, Zs) Zs is the result of concatenating
the reverse of the list Xs and the list Ys.
reverse([], Xs, Xs).
reverse([X | X1s], X2s, Ys) reverse(X1s, [X | X2s], Ys).
We leave it to the reader to check that PALINDROME is indeed recurrent w.r.t.
the following level mapping | |:
|palindrome(xs)| = 2 |xs| + 2,
|reverse(xs, ys)| = 2 |xs| + 1,
|reverse(xs, ys, zs)| = 2 |xs| + |ys|.
Consequently, again by the Finiteness 1 Corollary 6.9 for a ground xs all SLDderivations of PALINDROME {palindrome(xs)} are finite.
Exercise 67 Prove that the programs NUMERAL, LESS and LENGTH are terminating. 2
6.4
In this section we prove the converse of the Termination 1 Corollary 6.10. This
provides us with an exact characterization of terminating programs.
In what follows with a query Q we associate a tree of SLD-derivations of P {Q}.
In this tree, for every query its children consist of all its SLD-resolvents modulo
renaming w.r.t. all program clauses and all atoms. More precisely, we introduce
the following definition.
Definition 6.13 An S-tree for P {Q} is a tree such that
its branches are SLD-derivations of P {Q},
every node Q has exactly one descendant for every atom A of Q and every
clause c from P which is applicable to A. This descendant is a resolvent of
Q and c w.r.t. A.
2
Informally, an S-tree for P {Q} groups all SLD-derivations of P {Q} provided
choices (C) and (D) defined in Section 3.5 are discarded.
156 Termination
Lemma 6.14 (S-tree 1) An S-tree for P {Q} is finite iff all SLD-derivations of
P {Q} are finite.
Proof. By definition the S-trees are finitely branching. The claim now follows by
Konigs Lemma 6.2.
2
This lemma allows us to concentrate on S-trees when studying termination.
Exercise 68 Let P be a program and Q a query.
(i) Prove that an S-tree for P {Q} is finite iff all S-trees for P {Q} are finite.
(ii) Prove that if an S-tree for P {Q} is finite, then all S-trees for P {Q} have the
same height.
2
For a program P and a query Q, we denote by nodesP (Q) the number of nodes in
an S-tree for P {Q}. The above exercise shows that this notation is well-defined.
The following properties of S-trees will be needed.
Lemma 6.15 (S-tree 2) Let P be a program and Q a query such that an S-tree
for P {Q} is finite. Then
(i) for all substitutions , nodesP (Q) nodesP (Q),
(ii) for all atoms A of Q, nodesP (A) nodesP (Q),
(iii) for all non-root nodes H in the S-tree for P {Q}, nodesP (H) < nodesP (Q).
Proof.
(i) Immediate by the Lifting Theorem 3.22.
(ii), (iii) Immediate by the definition.
157
(6.2)
6.5
6.5.1
Motivation
158 Termination
% app3(Xs, Ys, Zs, Us) Us is the result of concatenating the lists
Xs, Ys and Zs.
app3(Xs, Ys, Zs, Us) app(Xs, Ys, Vs), app(Vs, Zs, Us).
augmented by the APPEND program.
Program: APPEND3
It is easy to prove that APPEND3 is recurrent by using the following level mapping
| |:
|app(xs, ys, zs)| = min(|xs|, |zs|),
|app3(xs, ys, zs, us)| = |xs| + |us| + 1.
Thus by the Termination 1 Corollary 6.10 APPEND3 is terminating. Now, a typical
use of the program involves a query of the form app3(xs, ys, zs, Us), where
xs, ys, zs are lists and Us is a variable. We shall show at the end of this section
that all LD-derivations of app3(xs, ys, zs, Us) are finite.
On the other hand it is easy to see that an infinite SLD-derivation exists when
the rightmost selection rule is used. As a consequence of the Finiteness 1 Corollary
6.9 the query app3(xs, ys, zs, Us) is not bounded, although according to the
terminology of Section 5.1 it universally terminates, that is it can be evaluated by
a finite Prolog computation.
(ii) Next, we consider a program which is not terminating but is such that all LDderivations starting with a ground query are finite. An example of such a program
is NAIVE REVERSE:
% reverse(Xs, Ys) Ys is a reverse of the list Xs.
reverse([], []).
reverse([X | Xs], Ys)
reverse(Xs, Zs),
app(Zs, [X], Ys).
augmented by the APPEND program.
It is easy to check that the ground query reverse(xs, ys), for a list xs with at
least two elements and an arbitrary list ys has an infinite SLD-derivation, obtained
by using the selection rule which selects the leftmost atom at the first two steps and
the second leftmost atom afterwards. Thus REVERSE is not terminating. However,
one can show that all LD-derivations starting with a query reverse(s,y) for s
ground (or s list) are finite.
2
To cope with these difficulties we first modify the definition of a terminating
program in such a way that it takes into account the leftmost selection rule.
Definition 6.19 A program is called left terminating if all its LD-derivations starting with a ground query are finite.
2
The notion of left termination is clearly more appropriate for the study of Prolog
programs than that of a terminating program.
159
Acceptable Programs
To prove that a program is left terminating, and to characterize the queries that
terminate w.r.t. such a program, we introduce the following concepts from Apt
and Pedreschi [AP93].
Definition 6.20 Let P be a program, | | a level mapping for P and I an interpretation of P .
A clause of P is called acceptable with respect to | | and I, if I is its model
and for every ground instance A A, B, B of it such that I |= A
|A| > |B|.
In other words, for every ground instance A B1 , . . ., Bn of the clause
|A| > |Bi | for i [1, n
],
where
n
= min({n} {i [1, n] | I 6|= Bi }).
A program P is called acceptable with respect to | | and I, if all its clauses are.
P is called acceptable if it is acceptable with respect to some level mapping
and an interpretation of P .
2
The use of the premise I |= A forms the only difference between the concepts of
recurrence and acceptability. Intuitively, this premise expresses the fact that when
in the evaluation of the query A, B, B using the leftmost selection rule the atom
B is reached, the atoms A are already resolved. Consequently, by the soundness
of the SLD-resolution (Theorem 4.4), these atoms are all true in I.
Alternatively, we may define n
by
n
=
n if I |= B1 , . . ., Bn ,
i if I |= B1 , . . ., Bi1 and I 6|= B1 , . . ., Bi .
160 Termination
6.5.3
We now proceed in an analogous way to Section 6.2 and prove that every acceptable
program is left terminating. To this end we use again the notion of boundedness.
The concept of a bounded query employed here differs from that introduced in
Definition 6.7 in that it takes into account the interpretation I. This results in a
more complicated definition.
Given a set A, denote now the set of all subsets of A by P(A). In what follows,
assume that the maximum function max : P(N ) N {}, from the set of all
subsets of natural numbers to the set of natural numbers augmented by , is
defined as
max S =
0 if S = ,
n if S is finite and non-empty, and n is the maximum of S,
if S is infinite.
Then, assuming that n < for all natural numbers n, max S < iff the set S
is finite.
Definition 6.22 Let P be a program, || a level mapping for P , I an interpretation
of P and k a natural number.
A query Q is called bounded by k w.r.t. | | and I if for every ground instance
A, B, B of it such that I |= A
|B| k.
A query Q is called bounded w.r.t. | | and I if it is bounded by some k w.r.t.
| | and I.
With each query Q with n atoms we associate n sets of natural numbers
defined as follows, for i [1, n]:
|Q|Ii := {|Ai | | A1 , . . ., An is a ground instance of Q and I |= A1 , . . ., Ai1 }.
With a query Q with n atoms, bounded w.r.t. | | and I, we associate the
following multiset |Q|I of natural numbers:
|Q|I := bag (max |Q|I1 , . . ., max |Q|In ).
2
Note that |Q|Ii is constructed as follows. First one takes the set of those ground
instances of Q the first i 1 atoms of which are true in I. Then |Q|Ii is the set of
the i-th atoms of these instances. The following exercise clarifies the introduced
notions.
161
Exercise 69
(i) Prove that a query Q is bounded by k w.r.t. | | and I if k h for h |Q|I .
(ii) Prove that a query Q is bounded w.r.t. | | and I iff |Q|Ii is finite, for i [1, n]. (This
shows that, for a bounded query Q, |Q|I is indeed a multiset of natural numbers.)
(iii) Prove that a query Q is bounded w.r.t. | | and HB L iff it is bounded w.r.t. | | in the
sense of Definition 6.7.
2
Note that the first atom of a bounded query is bounded and that a query with
only one atom is bounded iff this atom is bounded.
We now prove a lemma analogous to the Boundedness 1 Lemma 6.8.
Lemma 6.23 (Boundedness 2) Let P be a program that is acceptable w.r.t. a
level mapping | | and an interpretation I. Let Q1 be a query that is bounded w.r.t.
| | and I, and let Q2 be an LD-resolvent of Q1 and of a clause from P . Then
(i) Q2 is bounded w.r.t. | | and I,
(ii) |Q2 |I m |Q1 |I .
Proof. An LD-resolvent of a query and a clause is obtained by means of the
following three operations:
instantiation of the query,
instantiation of the clause,
replacement of the first atom, say H, of a query by the body of a clause
whose head is H.
Thus the lemma is an immediate consequence of the following claims in which we
refer to the given level mapping and interpretation I.
Claim 1 An instance Q0 of a bounded query Q is bounded and |Q0 |I m |Q|I .
Proof. It suffices to note that |Q0 |Ii |Q|Ii for i [1, n], where n is the number of
2
atoms in Q (and Q0 ).
Claim 2 An instance of an acceptable clause is acceptable.
Proof. Obvious.
162 Termination
Proof. We have
<
max|B1 , . . ., Bn , C1 , . . ., Cm |Ii
{Definition 6.22}
max{|Bi0 | | B10 , . . ., Bn0 is a ground instance of B
0
and I |= B10 , . . ., Bi1
}
{for some A0 , A0 B10 , . . ., Bn0 is a ground instance of A B}
max{|Bi0 | | A0 B10 , . . ., Bn0 is a ground instance of A B
0
and I |= B10 , . . ., Bi1
}
{Definition 6.20 and the fact that for finite R, S,
x S y R : x < y implies max S < max R}
max{|A0 | | A0 is a ground instance of A}
{Definition 6.22}
max|A, C1 , . . ., Cm |I1 .
2
2
2
Applications
163
Corollary 6.24 (Finiteness 2) Let P be an acceptable program and Q a bounded query. Then all LD-derivations of P {Q} are finite.
Proof. By the Boundedness 2 Lemma 6.23 and the Multiset Theorem 6.4.
Let us see now how these results can be used to establish left termination of
specific programs.
Example 6.26 We use here the function defined in Subsection 6.2.2, listsize | |,
which assigns natural numbers to ground terms. Reconsider the APPEND3 program
of Example 6.18. Define
|app(xs, ys, zs)| = |xs|,
|app3(xs, ys, zs, us)| = |xs| + |ys| + 1.
and take the Herbrand interpretation
I := {app(xs, ys, zs) | |xs| + |ys| = |zs|}
ground(app3(Xs, Ys, Zs, Us)).
It is easy to see that I is a model of APPEND3. Indeed, we have |[]|+|ys| =
|ys| and if |xs|+|ys| = |zs|, then |[x|xs]|+|ys| = 1+|xs|+|ys| = 1+|zs|
= |[x|zs]|, so I is a model of APPEND. Further, the clause defining the app3
relation is obviously true in I.
We already noted in Section 6.3 that APPEND is recurrent w.r.t. | |. To see that
the clause defining the app3 relation is acceptable w.r.t. | | and I it suffices to note
that |xs|+|ys|+1 > |xs| and that |xs|+|ys| = |vs| implies |xs|+|ys|+1 >
|vs|.
We conclude by the Termination 2 Corollary 6.25 that APPEND3 is left terminating. In addition, for all lists xs, ys, zs and an arbitrary term u, the query
app3(xs,ys,zs,u) is bounded w.r.t. | | and I, so by the Finiteness 2 Corollary
6.24 all LD-derivations of APPEND3 {app3(xs, ys, zs, u)} are finite.
2
6.6
Applications
We now show the usefulness of the results established in the previous section by
means of two further examples. In the following, we present the proof of acceptability (w.r.t. a level mapping | | and an interpretation I) of a given clause
164 Termination
c := A0 A1 , . . ., An by means of the following proof outline:
{f0 }
A0
A1 ,
{f1 }
..
.
{t0 }
{t1 }
{tn1 }
An1 ,
{fn1 }
{tn }
An .
{fn }
Here, ti and fi , for i [0, n] are integer expressions and statements respectively,
such that all ground instances of the following properties are satisfied:
Applications
165
{|xs| + 1}
{min(|x1s|, |xs|)}
{min(|x1s|, |zs|)}
{|zs| + 1}
Using the Termination 2 Corollary 6.25 we conclude that PERMUTATION is left terminating. Moreover, we obtain that, for a list s and a term t, the atom perm(s,t)
is rigid and hence bounded. Consequently, by the Finiteness 2 Corollary 6.24, all
LD-derivations of PERMUTATION {perm(s, t)} are finite.
Sequence
The choice of the level mapping and of the model can affect the class of queries
whose termination can be established. To see this consider the program SEQUENCE:
% sequence(Xs) Xs is a list of 27 elements.
sequence([ , , , , , , , , , , , , , , , , , , , , , , , , , , ]).
% question(Ss) Ss is a list of 27 elements forming the desired sequence.
question(Ss)
sequence(Ss),
sublist([1, ,1, ,1], Ss),
sublist([2, , ,2, , ,2], Ss),
sublist([3, , , ,3, , , ,3], Ss),
sublist([4, , , , ,4, , , , ,4], Ss),
sublist([5, , , , , ,5, , , , , ,5], Ss),
sublist([6, , , , , , ,6, , , , , , ,6], Ss),
sublist([7, , , , , , , ,7, , , , , , , ,7], Ss),
166 Termination
sublist([8, , , , , , , , ,8, , , , , , , , ,8], Ss),
sublist([9, , , , , , , , , ,9, , , , , , , , , ,9], Ss).
% sublist(Xs, Ys) Xs is a sublist of the list Ys.
sublist(Xs, Ys) app( , Zs, Ys), app(Xs, , Zs).
augmented by the APPEND program.
It is straightforward to verify that SEQUENCE is recurrent (so by the Generalization 1 Note 6.21 acceptable when the model HB L is used) w.r.t. the level mapping
| | defined by
|question(xs)|
|sequence(xs)|
|sublist(xs, ys)|
|app(xs, ys, zs)|
=
=
=
=
|xs| + 23,
0,
|xs| + |ys| + 1,
min (|xs|, |zs|).
Consequently, by the Finiteness 1 Corollary 6.9, for all ground terms s, all SLDderivations (thus a fortiori all LD-derivations) of SEQUENCE {question(s)} are
finite. However, with this choice of the level mapping we face the problem that the
atom question(Ss) is not bounded. Consequently, we cannot use the Finiteness 2
Corollary 6.24 to prove termination of this query w.r.t. the leftmost selection rule.
The situation is analogous to that of the program APPEND3 of Example 6.18(i).
To prove this stronger termination property we change the above level mapping
by putting
|question(xs)| = 50,
and choose any model I of SEQUENCE such that for a ground s
I |= sequence(s) iff s is a list of 27 elements.
Then SEQUENCE is acceptable w.r.t. | | and I. Moreover, the query question(Ss)
is now bounded w.r.t. | | and, consequently, by the Finiteness 2 Corollary 6.24, all
LD-derivations of SEQUENCE {question(Ss)} are finite.
Exercise 70 Provide a proof outline showing that with the above choice of the level
mapping and the interpretation SEQUENCE is acceptable.
Exercise 71 The level mapping used in the proof of acceptability of SEQUENCE implies that for all lists s,t all LD-derivations of sublist(s,t) are finite. Show that the
requirement that s is a list is not needed to draw this conclusion.
2
6.7
We now characterize the class of left terminating programs by proving the converse
of the Termination 2 Corollary 6.25. To this end we proceed analogously as in the
case of terminating programs and analyze the size of finite LD-trees.
167
We need the following analog of the S-tree 2 Lemma 6.15, where for a program
P and a query Q we now denote by lnodesP (Q) the number of nodes in an LD-tree
for P {Q}. Exercise 72 shows that this notation is well-defined.
Lemma 6.27 (LD-tree) Let P be a program and Q a query such that an LD-tree
for P {Q} is finite. Then
(i) for all substitutions , lnodesP (Q) lnodesP (Q),
(ii) for all prefixes Q0 of Q, lnodesP (Q0 ) lnodesP (Q),
(iii) for all non-root nodes Q0 in the LD-tree for P {Q}, lnodesP (Q0 ) <
lnodesP (Q).
Proof.
(i) Immediate by the Lifting Theorem 3.22.
(ii) Consider a prefix Q0 := A1 , . . ., Ak of Q := A1 , . . ., An (n k). By an appropriate renaming of variables, we can assume on the account of the Variant Corollary
3.19 that all input clauses used in an LD-tree for P {Q0 } have no variables in
common with Q. We can now transform the LD-tree for P {Q0 } into an initial
subtree of an LD-tree for P {Q} by replacing in it a node B by B, Ak+1 , . . ., An ,
where is the composition of the mgus used on the path from the root Q0 to the
node B. This implies the claim.
(iii) Immediate by the definition.
We can now demonstrate the desired result which is a counterpart of the Recurrence Theorem 6.16.
Theorem 6.28 (Acceptability) Let P be a left terminating program. Then for
some level mapping | | and an interpretation I of P
(i) P is acceptable w.r.t. | | and I,
(ii) for every query Q, Q is bounded w.r.t. | | and I iff all LD-derivations of
P {Q} are finite.
Proof. Define the level mapping by putting for A HB L
|A| = lnodesP (A).
Since P is left terminating, this level mapping is well defined. Next, choose
I := {A HB L | there is a successful LD-derivation of P {A}}.
168 Termination
By the Success Theorem 2 4.37 we have I = M(P ), so I is a model of P .
First we prove one implication of (ii).
(ii1) Consider a query Q such that all LD-derivations of P {Q} are finite. We
prove that Q is bounded by lnodesP (Q) w.r.t. | | and I.
To this end take ` |Q|I . For some ground instance A1 , . . ., An of Q and
i [1, n
], where
n
= min({n} {i [1, n] | I 6|= Ai }),
we have ` = |Ai |. We now calculate
=
=
lnodesP (Q)
{LD-tree Lemma 6.27 (i)}
lnodesP (A1 , . . ., An )
{LD-tree Lemma 6.27 (ii)}
lnodesP (A1 , . . ., An )
{LD-tree Lemma 6.27 (iii), noting that for j [1, n
1]
there is a successful LD-derivation of P {A1 , . . ., Aj }}
lnodesP (Ai , . . ., An )
{LD-tree Lemma 6.27 (ii)}
lnodesP (Ai )
{definition of | |}
|Ai |
`.
* An Improvement
169
lnodesP (B1 , . . ., Bn )
6.8
* An Improvement
6.8.1
Motivation
The notions of recurrence and of acceptability stipulate that level mappings decrease from clause heads to clause bodies. This is used for two different purposes:
(i) in (mutually) recursive calls, to ensure termination of (mutually) recursive
procedures, and
(ii) in non- (mutually) recursive calls, to ensure that non- (mutually) recursive
procedures are called with terminating queries.
Although a decreasing of the level mappings is apparently essential for the first
purpose, this is not the case for the second purpose, since a weaker condition can
be adopted to ensure that non-recursive procedures are properly called.
In this section we elaborate on this idea, by presenting alternative definitions
of recurrence and of acceptability, that we qualify with the prefix semi. These
notions are actually proved equivalent to the original ones, but they give rise to
more flexible proof methods.
Following the intuition that recursive and non-recursive procedures should be
handled separately in proving termination, we introduce a natural ordering over
the relation names occurring in a program P , with the intention that for relations p
and q, p w q holds if p can call q. The next definition makes this concept precise
by defining first when two relation symbols occurring in a program are mutually
recursive.
We use here the notion of a transitive reflexive closure of a relation defined in
Section 2.6.
Definition 6.30 Let P be a program and p, q be relation symbols occurring in it.
We say that p refers to q in P if there is a clause in P that uses p in its head
and q in its body.
170 Termination
We say that p depends on q in P and write p w q, if (p, q) is in the transitive,
reflexive closure of the relation refers to.
We say that p and q are mutually recursive and write p ' q, if p w q and
q w p. In particular, p and p are mutually recursive.
2
We also write p = q when p w q and q 6w p. According to the above definition,
p ' q := p w q and q w p means that p and q are mutually recursive and
p = q := p w q and q 6w p means that p calls q as a subprogram.
Exercise 73 Prove that for every program the ordering = over its relation symbols is
well-founded.
6.8.2
Semi-recurrent programs
* An Improvement
171
Proof. In order to define the level mapping || ||, we first introduce a mapping
| | from the relation symbols of P to natural numbers such that, for two relation
symbols p, q occurring in P ,
p ' q implies |p| = |q|,
p = q implies |p| > |q|.
(6.3)
(6.4)
6.8.3
Semi-acceptable programs
172 Termination
The use of the premise I |= A forms the only difference between the concepts of
semi-recurrence and semi-acceptability.
The following observations are immediate. The first one is a counterpart of the
Generalization 1 Note 6.21.
Note 6.36 (Generalization 2) A program P is semi-recurrent w.r.t. | | iff it is
semi-acceptable w.r.t. | | and HB L .
2
Lemma 6.37 (Semi-acceptability) If a program P is acceptable w.r.t. | | and
I, then it is semi-acceptable w.r.t. | | and I.
2
In addition, the following analog of the Reduction 1 Lemma 6.33 holds.
Lemma 6.38 (Reduction 2) If a program is semi-acceptable w.r.t. | | and I,
then it is acceptable w.r.t. a level mapping || || and the same interpretation I.
Moreover, for each atom A, if A is bounded w.r.t. | |, then A is bounded w.r.t.
|| ||.
2
Exercise 74 Prove the Reduction 2 Lemma 6.38.
6.8.4
Examples
Concluding Remarks
173
Sequence
It is easy to see that SEQUENCE is semi-acceptable w.r.t. the level mapping | |
defined by
|question(xs)|
|sequence(xs)|
|sublist(xs, ys)|
|app(xs, ys, zs)|
=
=
=
=
48,
0,
|xs| + |ys|,
min (|xs|, |zs|).
and (as before) any model I of SEQUENCE such that for a ground s
I |= sequence(s) iff s is a list of 27 elements.
Again, in the above level mapping it was possible to disregard the accumulated
use of +1s. In addition, it is somewhat more natural to use 48 instead of 50 as
in Section 6.6, because for a ground list s of 27 elements we have
|sublist([9, , , , , , , , , , 9, , , , , , , , , , 9], s)| = 48.
Exercise 75 Show that the program PERMUTATION is left terminating by exhibiting a
more natural level mapping than the one given in Section 6.6 and using the notion of
semi-acceptability.
2
6.9
Concluding Remarks
Now that we have presented a method allowing us to deal with the termination of
logic and pure Prolog programs let us assess its merits and limitations. As pointed
out at the beginning of this chapter, termination is one of the basic problems one
has to deal with when studying the correctness of logic and pure Prolog programs.
The method proposed here provides us with some insights into the nature of the
problem and offers some heuristics which can be helpful when dealing with specific
programs.
First, note that by the Success 2 Theorem 4.37, the least Herbrand model
uniquely determines ground queries which succeed and terminate w.r.t. the leftmost selection rule. By the Lifting Corollary 3.23 all generalizations of these ground
queries also succeed but only in the case of logic programming. In pure Prolog such
a generalization can fail to terminate. So first we should think in terms of ground
queries and then lift each of them, but carefully, so that left termination w.r.t.
the leftmost selection rule is preserved.
Further, observe that the proposed method to prove left termination requires in
general only a limited declarative knowledge about the considered program in the
form of a model in which only certain properties of the program are valid. Often
this model is easy to guess and usually it can be defined in terms of simple relations
174 Termination
involving such elementary concepts as the listsize function or the size of a ground
term. In fact, this method seems to capture the employed informal reasoning.
The formal results justify this approach (Finiteness 1 Corollary 6.9 and Finiteness 2 Corollary 6.24) and also show its limitations (Equivalence 1 Corollary 6.17
and Equivalence 2 Corollary 6.29).
In the terminology of Section 5.1 we dealt here only with universal termination.
This allows us to prove termination independently of the clause ordering. Still,
there are natural queries which terminate only in a weaker sense. As an example
consider the program
% even(X) X is an even numeral.
even(0).
even(s(s(X))) even(X).
augmented by the LESS program.
Then the pure Prolog query even(X), less(X, s10 (0)) generates the first five
even numerals and then diverges. In the terminology of Section 5.1 this query
potentially diverges.
The method proposed in this chapter cannot be used to reason about this query.
Even though some methods dealing with potential divergence (sometimes called
existential termination) were proposed in the literature, we are not aware of any
intuitive and simple method which could use an informal reasoning.
6.10
Bibliographic Remarks
This chapter follows the exposition of Apt and Pedreschi [AP94], where refinements of the presented methods that deal with modular termination proofs are
also discussed. These modifications were applied there to a number of non-trivial
examples including the MAP COLOR program. Dershowitz [Der87] discussed in detail
various uses of the multiset ordering in the area of term rewriting systems.
The results of Sections 6.3 and 6.4 are from Bezem [Bez93]. The function listsize
defined in Section 6.2.2 was first considered in Ullman and van Gelder [UvG88].
The termination of logic programs has been a subject of intense research in
recent years. Without aiming at completeness let us mention here the following
related work.
Vasak and Potter [VP86] identified two forms of termination for logic programs
the existential and universal one and characterized the class of universal
terminating queries for a given program with selected selection rules. However,
this characterization cannot be easily used to prove termination.
Baudinet [Bau88] presented a method for proving existential termination of the
Prolog program in which with each program a system of equations is associated
whose least fixpoint is the meaning of the program. By analyzing this least fixpoint
various termination properties can be proved.
Summary
175
6.11
Summary
In this chapter we provided a method for proving termination of logic and pure
Prolog programs. To this end we introduced
the multiset ordering
176 Termination
and combined it with the use of the
level mappings,
recurrent programs,
bounded queries,
to deal with logic programs. Then we modified this approach to deal with the
termination of pure Prolog programs by introducing the notion of
acceptable programs.
6.12
References
[AP93]
[AP94]
K. R. Apt and D. Pedreschi. Modular termination proofs for logic and pure
Prolog programs. In G. Levi, editor, Advances in Logic Programming Theory,
pages 183229. Oxford University Press, Oxford, 1994.
[Apt90] K. R. Apt. Logic programming. In J. van Leeuwen, editor, Handbook of Theoretical Computer Science, pages 493574. Vol. B, Elsevier, Amsterdam, 1990.
[Bau88] M. Baudinet. Proving termination properties of Prolog programs. In Proceedings of the 3rd Annual Symposium on Logic in Computer Science (LICS), pages
336347. IEEE Computer Society, Edinburgh, 1988.
[BCF91] A. Bossi, N. Cocco, and M. Fabris. Termination of logic programs by exploiting
term properties. In S. Abramsky and T.S.E. Maibaum, editors, Proceedings
CCPSD-TAPSOFT 91, Lecture Notes in Computer Science 494, pages 153
180. Springer-Verlag, Berlin, 1991.
[Bez93]
M. A. Bezem. Strong termination of logic programs. Journal of Logic Programming, 15(1 & 2):7998, 1993.
[BS94]
[K
on27] D. K
onig. Uber
eine Schluweise aus dem Endlichen ins Unendliche. Acta Litt.
Ac. Sci., 3:121130, 1927.
References
177
[Pie91]
[Pl
u90a] L. Pl
umer. Termination Proofs for Logic Programs. Lecture Notes in Artificial
Intelligence 446, Springer-Verlag, Berlin, 1990.
[Pl
u90b] L. Pl
umer. Termination proofs for logic programs based on predicate inequalities. In D. H. D. Warren and P. Szeredi, editors, Proceedings of the Seventh International Conference on Logic Programming, pages 634648. The MIT Press,
Cambridge, MA, 1990.
[RKS92] K. Rao, D. Kapur, and R.K. Shyamasundar. A transformational methodology
for proving termination of logic programs. In Proceedings of the Fifth Conference on Computer Science Logic, Lecture Notes in Computer Science 626,
pages 213226. Springer-Verlag, Berlin, 1992.
[SD94]
Chapter 7
179
It provides sufficient conditions for proving occur-check freedom for sets of term
equations.
In Section 7.3 we introduce modes. Modes indicate which argument positions
of a relation symbol should be viewed as an input and which as an output. So,
informally, modes indicate how a relation symbol is to be turned into a function
symbol. Then we define for programs and queries the property of being well-moded.
In Section 7.4 we apply this property to prove occur-check freedom.
However, the property of being well-moded is rather restrictive. In particular,
it requires that atomic queries are ground in their input positions. Therefore, in
Section 7.5, we introduce another property of programs and queries, called nice
modedness which, as shown in Section 7.6, allows us to prove occur-check freedom
for another class of programs and queries.
The above results show that by means of simple syntactic checks it is possible
to establish occur-check freedom. However, the occur-check problem is in general
undecidable, so the above results cannot provide a complete solution to the occurcheck problem.
To address this issue in general, we propose program transformations which
allow us to insert so-called occur-checks in the program and the query. To relate
the behaviour of the original and of the transformed program, in Section 7.7 we
introduce the notion of an unfolding. Unfolding is a general technique allowing us
to increase program efficiency. Then, in Section 7.8, we show how every program
and a query can be transformed into a new program and a new query in which
only one single relation needs to be dealt with by the unification algorithm with
the occur-check. Unfolding allows us to relate this new program to the original
one.
7.1
7.2
NSTO Lemma
To prove that a program is occur-check free we need to have some means to establish
that a set of equations is NSTO. In this section we provide a criterion that will be
sufficient for our purposes. We need some preparatory definitions first.
NSTO Lemma
181
Definition 7.3
We call a family of terms linear if every variable occurs at most once in it.
We call a set of equations left linear if the family of terms formed by their
left-hand sides is linear.
2
Thus a family of terms is linear iff no variable has two distinct occurrences in
any term and no two terms have a variable in common.
Definition 7.4 Let E be a set of equations. We denote by E the following
relation defined on the elements of E:
e1 E e2 iff the left-hand side of e1 and the right-hand side of e2 have a variable
in common.
2
So for example x = f (y) E a = x for any E containing these two equations.
Note that if a variable occurs both in the left-hand and right-hand side of an
equation e of E, then e E e.
We can now prove the desired result from Deransart et al. [DFT91].
Lemma 7.5 (NSTO) Suppose that the equations in E can be reoriented in such
a way that the resulting set F is left linear and the relation F is acyclic. Then
E is NSTO.
Proof. Call a set of equations good if it satisfies the assumptions of the lemma.
We prove two claims.
Claim 1 Goodness is preserved by the actions of the MartelliMontanari algorithm.
183
This shows that if action (6) can be applied to a set of equations E, then for any
set of equations F resulting from a reorientation of the equations of E the relation
2
F is cyclic, and consequently E is not good.
The desired conclusion now follows immediately from Claims 1 and 2.
Example 7.6 As an illustration of the use of the above result consider the set of
equations
E := {z = f (v), f (v) = z, x = f (y), u = a, g(u, z, z) = y}.
Then E is NSTO because the following reorientation of it satisfies the assumptions
of the NSTO Lemma 7.5:
{z = f (v), x = f (y), u = a, y = g(u, z, z)}.
Note that the resulting set of equations has one element less than the original set.
2
Exercise 76 Prove the following properties of sets of equations.
(i) E {f (s1 , . . ., sn ) = f (t1 , . . ., tn )} is NSTO iff E {s1 = t1 , . . ., sn = tn } is NSTO.
(ii) E {x = x} is NSTO iff E is NSTO.
(iii) E {s = t} is NSTO iff E {t = s} is NSTO.
Exercise 77 Call an equation semi-ground if one side of it is ground and call a set of
equations semi-ground if all its elements are semi-ground. Prove that for E1 semi-ground,
E1 E2 is NSTO iff E2 is NSTO.
2
Note that these exercises allow us to strengthen the NSTO Lemma 7.5 by applying it to a larger class of sets of equations.
7.3
For further analysis we introduce modes. Modes indicate how the arguments of a
relation should be used.
Definition 7.7 Consider an n-ary relation symbol p. By a mode for p we mean a
function mp from {1, . . ., n} to the set {+, }. If mp (i) = +, we call i an input
position of p and if mp (i) = , we call i an output position of p (both w.r.t. mp ).
By a moding we mean a collection of modes, each for a different relation symbol.
2
i1
[
Var (tj ).
j=1
(ii) A clause
p0 (t0 , sn+1 ) p1 (s1 , t1 ), . . ., pn (sn , tn )
is called well-moded if for i [1, n + 1]
Var (si )
i1
[
Var (tj ).
j=0
185
(i [1, n]) every variable occurring in an input position of a body atom occurs
either in an input position of the head (j = 0) or in an output position of an
earlier (j [1, i 1]) body atom,
(i = n + 1) every variable occurring in an output position of the head occurs
in an input position of the head (j = 0) or in an output position of a body
atom (j [1, n]).
Note that a unit clause p(s, t) is well-moded iff Var (t) Var (s), whereas a query
with only one atom is well-moded iff this atom is ground in its input positions.
Intuitively, in a Prolog computation of a well-moded query A1 , . . ., An and wellmoded program P , data passes from the input positions of A1 to the output
positions of A1 , then to the input positions of A2 , etc., until it reaches the output
positions of An .
And within each well-moded clause H B1 , . . ., Bn of P a Prolog computation
begins by passing data to the input positions of the H, from which it is passed
to the input positions of B1 , etc., until it reaches the output positions of Bn from
which it is finally passed to the output positions of H.
A test whether a query or clause is well-moded can be efficiently performed by
noting that a query Q is well-moded iff every first from the left occurrence of a
variable in Q is within an output position. And a clause p(s, t) B is well-moded
iff every first from the left occurrence of a variable in the sequence s, B, t is within
an input position of p(s, t) or within an output position in B. (We assume in this
description that in every atom the input positions occur first.)
The following lemma shows the persistence of the notion of well-modedness.
Lemma 7.9 (Well-modedness) An SLD-resolvent of a well-moded query and a
well-moded clause is well-moded.
Proof. An SLD-resolvent of a query and a clause is obtained by means of the
following three operations:
instantiation of a query,
instantiation of a clause,
replacement of an atom, say H, of a query by the body of a clause whose
head is H.
So we only need to prove the following two claims.
Claim 1 An instance of a well-moded query (respectively clause) is well-moded.
Proof.
It suffices to note that for any sequences of terms s, t1 , . . . , tn and a
S
S
2
substitution , Var (s) nj=1 Var (tj ) implies Var (s) nj=1 Var (tj ).
Claim 2 Suppose that A, H, C is a well-moded query and H B is a well-moded
clause. Then A, B, C is a well-moded query.
Then all
2
This allows us to draw the following conclusion which is not needed for the study
of the occur-check problem but is of independent interest.
Corollary 7.11 (Computed Answer) Let P and Q be well-moded. Then for
every computed answer substitution for P {Q}, Q is ground.
Proof. Let x stand for the sequence of all variables that appear in Q. Let p be
a new relation of arity equal to the length of x and with all positions moded as
input. Then Q, p(x) is a well-moded query, because every variable occurring in a
well-moded query has an occurrence within an output position.
Suppose now that is a computed answer substitution for P {Q}. Then p(x)
is a query in an SLD-derivation of P {Q, p(x)}. By the Well-modedness Corollary
7.10 p(x) is well-moded, that is ground. This implies the claim.
2
Exercise 78 Let P be well-moded and a c.a.s. of P {p(s, t)}. Prove that then
Var (t) Var (s).
Hint. Let r be a new relation of arity equal to the length of s and with all positions
moded as output and q be a new relation of arity equal to the length of t and with all
positions moded as input. Consider the query r(s), p(s, t), q(t).
2
Exercise 79 Let P and A, B be well-moded. Suppose that B is a descendant of A, B
in an LD-derivation of P {A, B}, where is the composition of the mgus used in
resolving A. Prove that A is ground.
2
187
7.3.1
Examples
Let us consider now some examples. They show how and to what extent the
notion of well-modedness can be applied to specific programs. When dealing with
the programs below we apply the Computed Answer Corollary 7.11. Note that this
corollary refers to an arbitrary selection rule.
Append
First, consider the program APPEND:
app([], Ys, Ys).
app([X | Xs], Ys, [X | Zs]) app(Xs, Ys, Zs).
with the mode app(+,+,-). It is easy to check that APPEND is then well-moded.
Indeed, the following inclusions obviously hold:
Var (Ys) Var ([ ], Ys),
Var (Xs, Ys) Var ([X|Xs], Ys),
Var ([X|Zs]) Var ([X|Xs], Ys) Var (Zs).
We conclude that for ground s,t all computed answer substitutions for APPEND
{app(s, t, u)} are such that u is ground.
Exercise 80 Check that APPEND is well-moded with the following modes:
(i) app(-,-,+),
(ii) app(+,-,+),
(iii) app(-,+,+).
This example and the exercise indicate various natural uses of the APPEND program. They show that in general it is not clear how to mode the relation app in
such a way as to conform to the intuition concerning the use of modes.
Moreover, not all uses of APPEND can be properly taken into account by means
of modes. First of all, APPEND can be used to concatenate non-ground lists. In
this case the considered query is not well-moded. Further, there is no way to
mode the relation app so that the program APPEND and the query app([X,2],
[Y,U], [3,Z,0,Z]) are well-moded. Note that this query succeeds with the c.a.s.
{X/3, Z/2, Y/0, U/2} and app([X,2], [Y,U], [3,Z,0,Z]) {X/3, Z/2, Y/0, U/2} is
ground. However, this fact cannot be established on the basis of the Computed
Answer Corollary 7.11.
Permutation
To make certain programs well-moded a relation has to be moded in two different
ways. For example, take the program PERMUTATION:
% perm(Xs, Ys) Ys is a permutation of the list Xs.
perm([], []).
perm(Xs, [X | Ys])
app(X1s, [X | X2s], Xs),
app(X1s, X2s, Zs),
perm(Zs, Ys).
augmented by the APPEND program.
Conforming to the customary use of this program, we wish to use the moding
perm(+,-) for the perm relation.
Exercise 81 Prove that there is no way to extend the moding perm(+,-) by assigning
a single mode to the relation app, so that PERMUTATION becomes well-moded.
2
To extend the moding perm(+,-) so that the PERMUTATION program becomes
well-moded, we thus need to use different modings for the relation app. It is easy to
check that with the mode app(-,-,+) for the first call to APPEND and app(+,+,-)
for the second call to APPEND, PERMUTATION is well-moded.
As stated at the beginning of this section, such multiple modes can be formally
handled by renaming of the relations used. Intuitively, each call of the app relation
refers to a different copy of the APPEND program. Formally, it suffices to rename the
second call to APPEND to app1(X1s, X2s, Zs) and add to the program a renamed
version of APPEND which defines the relation app1.
On account of the fact that PERMUTATION is well-moded, we conclude that for s
ground all the computed answer substitutions for PERMUTATION {perm(s, t)}
are such that t is ground.
Note also that in the case of the PERMUTATION program some of its uses cannot
be properly taken into account by means of modes either. Indeed, this program
189
can be also used to compute permutations of a non-ground list. In this case the
relevant query is not well-moded.
Sequence
Finally, we exhibit an example of a natural program such that a customary use of
it is not well-moded for any moding. Namely, consider the SEQUENCE program:
% sequence(Xs) Xs is a list of 27 elements.
sequence([ , , , , , , , , , , , , , , , , , , , , , , , , , , ]).
% question(Ss) Ss is a list of 27 elements forming the desired sequence.
question(Ss)
sequence(Ss),
sublist([1, ,1, ,1], Ss),
sublist([2, , ,2, , ,2], Ss),
sublist([3, , , ,3, , , ,3], Ss),
sublist([4, , , , ,4, , , , ,4], Ss),
sublist([5, , , , , ,5, , , , , ,5], Ss),
sublist([6, , , , , , ,6, , , , , , ,6], Ss),
sublist([7, , , , , , , ,7, , , , , , , ,7], Ss),
sublist([8, , , , , , , , ,8, , , , , , , , ,8], Ss),
sublist([9, , , , , , , , , ,9, , , , , , , , , ,9], Ss).
% sublist(Xs, Ys) Xs is a sublist of the list Ys.
sublist(Xs, Ys) app( , Zs, Ys), app(Xs, , Zs).
augmented by the APPEND program.
We call here a list of 27 elements a desired sequence if it satisfies the description
given on page 135, so it is a sequence containing three 1s, three 2s, ..., three 9s such
that for all i [1, 9] there are exactly i numbers between successive occurrences of
i. Take now the query question(Ss). To get it well-moded we have to use the
mode question(-). This implies that to obtain the clause defining the question
relation well-moded, we have to use the mode sequence(-). But then we cannot
satisfy the requirement of well-modedness for the unit clause defining the sequence
relation.
We conclude that the Computed Answer Corollary 7.11 cannot be applied to
SEQUENCE {sequence(Ss)}.
Exercise 82 Consider other programs discussed in Chapter 5. Find natural modings
in which these programs are used and check which programs are then well-moded.
2
7.4
After this incursion into the world of modes let us return to our original problem
that of establishing that a program is occur-check free. In this section we show
Reorient it as follows:
H
A
H H
A
H
A
F := {iA
1 = i1 , . . ., im = im , o1 = o1 , . . ., on = on }.
We now apply this result to the study of pure Prolog programs. To this end we
need to consider LD-derivations. The following notion is due to Dembi
nski and
Maluszy
nski [DM85].
191
The above exercise is applicable to some pure Prolog programs, for example to
LESS and LENGTH. However, most natural programs do not satisfy its assumption,
so it is of limited use. Hence we should rather try to exploit the Occur-check 1
Theorem 7.16 in a different way. To apply this theorem to specific programs we
need to have a means to establish its second assumption. But the definition of a
well-moded program is designed in such a way that the following result holds.
Lemma 7.17 (Data Drivenness) Let P and Q be well-moded. Then all LDderivations of P {Q} are data driven.
Proof. Note that the first atom of a well-moded query is ground in its input
positions, so the conclusion follows by the Well-modedness Corollary 7.10.
2
This brings us to the following conclusion which involves the concept of wellmodedness.
Corollary 7.18 (Occur-check 1) Let P and Q be well-moded. Suppose that
the head of every clause of P is output linear.
Then P {Q} is occur-check free.
Proof. By the Occur-check 1 Theorem 7.16 and the Data Drivenness Lemma 7.17.
2
Examples
This Corollary can be easily applied to various pure Prolog programs. We limit
ourselves here to the programs considered in Section 7.3.1. We use here the conclusions there established, namely that these programs are well-moded in the discussed
modings.
Append
First, consider the program APPEND with the mode app(+,+,-). It is easy to see
that in this mode the head of every clause is output linear. We conclude that for
s and t ground, APPEND {app(s, t, u)} is occur-check free.
Append, again
In addition, in the mode app(-,-,+) the head of every clause of APPEND is output
linear. Again, we conclude that for u ground, APPEND {app(s, t, u)} is occurcheck free.
Exercise 85 Draw the appropriate conclusions concerning the occur-check freedom for
APPEND used in the following modes: app(+,-,+), app(-,+,+).
2
Permutation
Finally, consider the program PERMUTATION with the previously considered moding,
that is perm(+,-), app(-,-,+) for the first call to APPEND and app(+,+,-) for the
second call to APPEND.
Again, the heads of all clauses are output linear. We obtain that for s ground,
PERMUTATION {perm(s, t)} is occur-check free.
To apply the Occur-check 1 Corollary 7.18 to specific programs it is natural to
start by moding the relations used in the query so that this query becomes wellmoded. The important clue comes from the fact that the input positions of the
first atom of a well-moded query are filled in by ground terms. Then one should
try to mode other relations used in the program, so that the remaining conditions
of this Corollary are satisfied.
Exercise 86 Consider the program PALINDROME:
% palindrome(Xs) the list Xs is equal to its reverse.
palindrome(Xs) reverse(Xs, Xs).
% reverse(Xs, Ys) Ys is the reverse of the list Xs.
reverse(X1s, X2s) reverse(X1s, [], X2s).
% reverse(Xs, Ys, Zs) Zs is the result of concatenating
the reverse of the list Xs and the list Ys.
reverse([], Xs, Xs).
reverse([X | X1s], X2s, Ys) reverse(X1s, [X | X2s], Ys).
Prove that for s ground, PALINDROME { palindrome(s)} is occur-check free.
7.5
193
The above conclusions are of a restrictive kind, because in each case we had
to assume that the input positions of one atom queries are ground. Moreover,
for some natural programs the above results are not applicable. For example,
the Occur-check 1 Corollary 7.18 cannot be used to establish that SEQUENCE
{question(Ss)} is occur-check free. Indeed, we have already noted in Section
7.3.1, that there is no way to mode this program and query so that both of them
are well-moded.
To deal with these difficulties we now consider different syntactic restrictions.
Definition 7.19
A query p1 (s1 , t1 ), . . ., pn (sn , tn ) is called nicely moded if t1 , . . . , tn is a linear
family of terms and for i [1, n]
Var (si ) (
n
[
Var (tj )) = .
(7.1)
j=i
A clause
p0 (s0 , t0 ) p1 (s1 , t1 ), . . ., pn (sn , tn )
is called nicely moded if p1 (s1 , t1 ), . . ., pn (sn , tn ) is nicely moded and
Var (s0 ) (
n
[
Var (tj )) = .
(7.2)
j=1
Thus, assuming that in every atom the input positions occur first, a query is
nicely moded if
every variable occurring in an output position of an atom does not occur
earlier in the query.
And a clause is nicely moded if
every variable occurring in an output position of a body atom occurs neither
earlier in the body nor in an input position of the head.
Note that a one atom query is nicely moded iff it is output linear and input
output disjoint.
Intuitively, the concept of being nicely moded prevents a speculative binding
of the variables which occur in output positions these variables are required
to be fresh, that is to say not used before the Prolog computation reaches the
output positions in which they occur.
The following lemma shows the persistence of the notion of being nicely moded
when the leftmost selection rule is used.
7.5.1
Examples
The main use of the notion of nicely modedness lies in ensuring that the output positions of the atoms selected in the LD-derivations do not share variables,
both between themselves and with the input positions. To get familiar with the
definition let us now consider the programs analyzed in Section 7.3.1.
Append
Note that APPEND is nicely moded in the mode app(+,+,-). Indeed, the first
clause is a unit clause and hence nicely moded. For the second clause it suffices
to note that its body app(Xs, Ys, Zs) is output linear and inputoutput disjoint
and hence nicely moded as a query. Moreover, Var ([X|Xs], Ys) Var (Zs) = so
condition (7.2) is satisfied.
Append, again
It is equally straightforward to check that in the mode app(-,-,+) APPEND is nicely
moded, as well.
Exercise 88 Check whether APPEND is nicely moded in the modes app(+,-,+) and
app(-,+,+).
Permutation
Consider now PERMUTATION with the previously considered moding, so perm(+,-)
and app(-,-,+) for the first call to APPEND and app(+,+,-) for the second call to
APPEND. We just checked that in each of these two modes APPEND is nicely moded,
so it suffices to consider the clauses defining the relation perm. The first clause is
a unit clause and hence nicely moded. For the second clause notice that the query
app(X1s, [X | X2s], Xs), app(X1s, X2s, Zs), perm(Zs, Ys)
is nicely moded because
195
7.6
Let us now return to the problem of proving occur-check freedom. In this section
we show how the notion of nice modedness can be of use. The presentation is
analogous to that of Section 7.4. So first we introduce the following concept.
Definition 7.22 We call an LD-derivation output driven if all atoms selected in
it are output linear and inputoutput disjoint.
2
This brings us to the following alternative way of proving occur-check freedom.
Theorem 7.23 (Occur-check 2) Suppose that
the head of every clause of P is input linear,
all LD-derivations of P {Q} are output driven.
Then P {Q} is occur-check free.
Proof. Let A and H be as in the proof of the Occur-check 1 Theorem 7.16. The
NSTO via Modes Lemma 7.14 applies and yields that A = H is NSTO.
2
To apply this result to specific programs it suffices to link it with the concept of
nice modedness. The following lemma is analogous to the Data Drivenness Lemma
7.17 and clarifies our interest in nicely moded programs.
Lemma 7.24 (Output Drivenness) Let P and Q be nicely moded. Then all
LD-derivations of P {Q} are output driven.
7.6.1
Examples
Let us see now how this corollary can be applied to the previously studied programs.
We use here the conclusions established in Subsection 7.5.1, namely that these
programs are nicely moded in the modings discussed there and in each case apply
the Occur-check 2 Corollary 7.25.
Append
Consider APPEND with the mode app(+,+,-). Clearly, the head of every clause is
input linear. We conclude that when the query app(s, t, u) is nicely moded,
so when u is linear and Var (s, t) Var (u) = , APPEND {app(s, t, u)} is
occur-check free.
Append, again
In the mode app(-,-,+) the head of every clause of APPEND is input linear, as well.
We obtain that when s,t is a linear family of terms and Var (s, t) Var (u) = ,
APPEND {app(s, t, u)} is occur-check free.
Permutation
It is straightforward to check that the heads of all clauses of PERMUTATION are input
linear. We conclude that when t is linear and Var (s) Var (t) = , PERMUTATION
{perm(s, t)} is occur-check free.
Sequence
Finally, consider the program SEQUENCE. The heads of its clauses are obviously
input linear in the moding discussed before. We now obtain that for every term s,
SEQUENCE {question(s)} is occur-check free.
Palindrome
So far it seems that the Occur-check 2 Corollary 7.25 allows us to draw more
useful conclusions than the Occur-check 1 Corollary 7.18. However, reconsider the
program PALINDROME discussed in Exercise 86.
197
Exercise 89 Show that no moding exists in which PALINDROME is nicely moded with
the heads of all clauses being input linear.
This exercise implies that the Occur-check 2 Corollary 7.25 cannot be applied to
this program.
A natural question arises of how to apply the above corollary to specific programs. We have already noticed in Section 7.4 that in the case of well-moded
queries the input positions of the first atom are filled in by ground terms. This
helps to find the appropriate modings so that the Occur-check 1 Corollary 7.18
could be applied.
However, no analogous property holds for for the nicely moded queries. As a
result it is not clear how to mode the relations so that the Occur-check 2 Corollary
7.25 could be applied. For example, we have already noticed in Section 7.3 that it
is not clear how to mode the relation app when considering the query app([X,2],
[Y,U], [3,Z,0,Z]). Note that to conclude that APPEND {app([X,2], [Y,U],
[3,Z,0,Z])} is occur-check free it suffices to use the mode app(-,-,+) and apply
a conclusion established above.
As observed in Chadha and Plaisted [CP94], to apply the Occur-check 2 Corollary 7.25 it is probably more natural to investigate first all the modings for which
the program is nicely moded and the heads of all clauses are input linear. Then one
should check for which modings the given query is nicely moded. To this end in
Chadha and Plaisted [CP94] two efficient algorithms are proposed for generating
modings with the minimal number of input positions, for which the program is
nicely moded.
Exercise 90 Prove that in the case of APPEND out of eight modes only for five of them
are the conditions of the Occur-check 2 Corollary 7.25 satisfied. Show that out of these
five modes only the mode app(-,-,+) can be used to deal with the occur-check freedom
for the query app([X,2], [Y,U], [3,Z,0,Z]).
2
Exercise 91 Consider the MEMBER program:
% member(Element, List) Element is an element of the list List.
member(X, [X | ]).
member(X, [ | Xs]) member(X, Xs).
Prove that when X 6 Var (t), MEMBER {member(X, t)} is occur-check free.
Exercise 92
(i) Suppose that all LD-derivations of P {Q} are both data and output driven. Prove
that then P {Q} is occur-check free.
(ii) Let P and Q be well-moded and nicely moded. Prove that then P {Q} is occurcheck free.
2
7.7
* Unfolding
* Unfolding
199
Exercise 93
(i) Compute the result of unfolding the member-atom in the second clause of the
SUBSET program:
% subset(Xs, Ys) each element of the list Xs is a member of the list Ys.
subset([], ).
subset([X | Xs], Ys) member(X, Ys), subset(Xs, Ys).
augmented by the MEMBER program.
(ii) Compute the result of unfolding of all the sublist-atoms in the program SEQUENCE
discussed in the previous sections.
2
A program and its unfolding are closely related as the following theorem by
Kawamura and Kanamori [KK88] shows.
Theorem 7.28 (Unfolding 1) Let P be a program, P1 an unfolding of P and Q
a query. Then is a c.a.s. for P {Q} iff is a c.a.s. for P1 {Q}.
Proof. Omitted.
Note that this theorem states nothing about termination. In fact, the following
simple example shows that in general termination with respect to a program and
its unfolding can differ.
Example 7.29 Consider the following program P :
r p, q.
p p.
By unfolding q in the first clause of P we actually delete this clause and obtain
the following program P1 :
p p.
Now the only LD-derivation of P {r} is infinite, whereas the only LD-derivation
2
of P1 {r} is failed, so finite.
It is useful, however, to point out that unfolding of a program maintains universal
termination of a query in the sense defined in Section 5.1. Namely, the following
result was established by Bossi and Cocco [BC94]. It also justifies the intuition
that unfolding improves efficiency.
Theorem 7.30 (Unfolding 2) Let P be a program, P1 an unfolding of P and
Q a query. Suppose that all LD-derivations of P {Q} are finite. Then all LDderivations of P1 {Q} are finite. Moreover, the height of all LD-trees for P1 {Q}
is then smaller than or equal to the height of all LD-trees for P {Q}.
Proof. Omitted. The reader is referred to Exercise 72 of Section 6.7 for the
justification of the reference to the height of all LD-trees.
2
Exercise 94 Prove that an unfolding of a well-moded program is well-moded.
7.8
So far we explained how to prove the occur-check freedom for certain programs and
queries. But, as already noted at the beginning of this chapter, it is very easy to run
into difficulties due to the occur-check problem. In fact, for almost every program
of Chapter 5 we can find a query which causes the occur-check problem. Consider
for example the MEMBER program. Take then the query member(Y, [f(Y)]). Then
one of the available sets of equations is { Y = X, f(Y) = X, [] = Xs } which
is subject to occur-check. This set of equations is generated during a program
execution and we obtain the already familiar interrupted listing:
| ?- member(Y, [f(Y)]).
Y = f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(f(
In general, it is undecidable whether the occur-check can take place in a program
execution (see Deransart and Maluszy
nski [DM85] and Apt and Pellegrini [AP94]),
so the Occur-check 1 Corollary 7.18 and the Occur-check 2 Corollary 7.25 cannot
offer a complete solution to the occur-check problem. The aim of this section is to
explain how this problem can be taken care of by inserting the occur-checks. By
this we mean insertion at the selected places in the program and the query of calls
to the unification algorithm with the occur-check. The following strengthening of
the Occur-check 2 Corollary 7.25 is essential.
Lemma 7.31 (Occur-check) Let P and Q be nicely moded. All sets of equations
which are available in the LD-derivations of P {Q} and are obtained using a clause
whose head is input linear are NSTO.
Proof. By the Nice Modedness Corollary 7.21 all queries in all LD-derivations of
P {Q} are nicely moded. But the first atom of a nicely moded query is output
linear and inputoutput disjoint. So when the head of the input clause used is input
linear, by the NSTO via Modes Lemma 7.14 the corresponding set of equations is
NSTO.
2
To use this result we transform a program and a query into a nicely moded
program and a nicely moded query using the relation =oc which is defined by
the single clause
X =oc X.
moded completely input. In the transformed program only this relation symbol
is dealt with by the unification algorithm with the occur-check. The subscript oc
was added to distinguish it from the Prolog built-in = which, according to our
assumption, performs unification without the occur-check. Note that the clause X
=oc X is not input linear, so the Occur-check 2 Corollary 7.25 cannot be applied
in its presence.
201
The following result summarizes the effect of the yet to be defined program and
query transformations.
Theorem 7.32 (Occur-check Insertion) For every program P and query Q
there exists a program P 0 and a query Q0 such that
Proof. The idea is to replace the variables which contradict nice modedness by
fresh variables. Consider a clause H B. Assume for simplicity that in every
atom input positions occur first. We say that a given occurrence of a variable x
in B contradicts nice modedness of H B if x occurs in an output position of an
atom in B and x occurs earlier in B or in an input position of H.
Consider now an occurrence of x in B which contradicts nice modedness. Let
A be the atom in B in which this occurrence of x takes place and let z be a fresh
variable. Replace this occurrence of x in A by z and denote the resulting atom A0 .
Replace A in B by A0 , z =oc x.
Scan now B and perform this replacement repeatedly for all occurrences of variables which contradict the nice modedness of the original clause H B. Call
the resulting sequence of atoms B0 . It is easy to see that H B0 is nicely moded.
Note that by unfolding the inserted calls of =oc in H B0 , we obtain the original
clause H B.
The same transformation applied to an arbitrary query transforms it into a
nicely moded query. Finally, a similar transformation ensures that the head H
of H B is input linear. It suffices to repeatedly replace every occurrence of a
variable x which contradicts linearity of H by a fresh variable z and replace B by
z =oc x, B. Clearly, the head of the resulting clause H B0 is input linear and
this transformation does not destroy the nice modedness of the clause. Again, the
original clause H B can be obtained by unfolding the inserted calls of =oc .
The claim now follows by the Occur-check Lemma 7.31.
2
The Unfolding 1 Theorem 7.28 and Unfolding 2 Theorem 7.30 show that the
behaviour of an unfolded program is closely related to the original program. So it
is justified to summarize the above result by saying that every program and query
is equivalent to a nicely moded program and nicely moded query such that the
heads of all clauses, except X =oc X, are input linear. In the Prolog execution of
the latter program and query only the inserted calls of =oc need to be dealt with
by means of a unification algorithm with the occur-check. These inserted calls of
=oc can be viewed as the overhead needed to implement correctly the original
program without the occur check.
7.9
Concluding Remarks
The original motivation of the implementers of Prolog for omiting the occur-check
from the unification algorithm was to ensure that the unification of a variable and
a term can be done in constant time. This special case of unification often takes
place for example, when the output positions of the selected atoms are filled in
by different variables.
Bibliographic Remarks
203
It should be mentioned here that some modern Prolog systems, for example
ECLi PSe [Agg95], allow us to set a flag to perform unification with the occurcheck. This does not obviate the work discussed here, because not all Prolog
systems have this facility and moreover the omission of the occur-check is desired
from the efficiency point of view.
The occur-check freedom is an example of a run-time property, that is a property
which refers to the program execution. In general, such properties are undecidable
and the occur-check freedom is no exception (see Section 7.8). In this chapter we
proposed simple methods allowing us to deal with the occur-check problem. These
methods are based on a syntactic analysis and can be easily implemented. It was
shown in Apt and Pellegrini [AP94] that these methods and their modifications
deal satisfactorily with most common Prolog programs.
Because of the above undecidability results it is easy to find examples of programs
and queries to which these methods cannot be applied. In such situations one can
use the program transformation proposed in the previous section. Note that such a
transformation can be efficiently implemented using two passes through the query
and the program, one to ensure nice modedness and the other to ensure the input
linearity of the heads of the program clauses. Its usefulness is crucially dependent
on the generation of the modes which reflect the use of the program relations.
Finally, a digression about the actions of the MartelliMontanari algorithm. We
showed that whenever the conditions of the Occur-check 1 Corollary 7.18 or of
the Occur-check 2 Corollary 7.25 are satisfied for P and Q, action (6) cannot be
performed in any LD-derivation of P {Q}. In fact, as the proofs of the NSTO
Lemma 7.5 and of the NSTO via Modes Lemma 7.14 show, in these situations
action (3) cannot be performed either, so it can be deleted from the algorithm.
The gain is, however, negligible.
7.10
Bibliographic Remarks
This chapter is based on Apt and Pellegrini [AP94], where refinements of the
presented methods are also discussed that allow us to deal with more complex
Prolog programs. The notion of an NSTO set of equations is from Deransart et al.
[DFT91]. Exercise 76 is from Deransart and Maluszy
nski [DM93].
Modes were first considered in Mellish [Mel81] and more extensively studied
in Reddy [Red84]. The concept of a well-moded program is essentially from
Dembi
nski and Maluszy
nski [DM85], where a more complicated definition is given
and the Data Drivenness Lemma 7.17 is stated without proof.
We used here
an elegant formulation from Rosenblueth [Ros91], which is equivalent to that
of Drabent [Dra87], where well-moded programs are called simple. The Wellmodedness Lemma 7.9 is from Apt and Luitjes [AL95]. In Apt and Pellegrini
[AP94] a weaker version was proved that dealt only with the LD-resolvents. In
Rao [Rao93] the notion of well-modedness is generalized so that the Computed
7.11
Summary
In this chapter we dealt with the occur-check problem. For this purpose we introduced the following notions:
and showed how to use them to prove occur-check freedom by means of syntactic
means. We also dealt with the problem of the insertion of the occur-checks. To
this end we defined
unfolding,
insertion of the occur-checks by means of program and query transformations.
7.12
References
[Agg95] A. Aggoun et al. ECLi PSe 3.5 User Manual. ECRC, Munich, Germany, February 1995.
[AL95]
References
205
[BC94]
[Cla79]
[CP94]
[Dev90] Y. Deville. Logic Programming. Systematic Program Development. International Series in Logic Programming. Addison-Wesley, Reading, MA, 1990.
[DFT91] P. Deransart, G. Ferrand, and M. Teguia. NSTO programs (not subject to
occur-check). In V. Saraswat and K. Ueda, editors, Proceedings of the International Logic Symposium, pages 533547. The MIT Press, Cambridge, MA,
1991.
[DM85] P. Deransart and J. Maluszy
nski. Relating logic programs and attribute grammars. Journal of Logic Programming, 2:119156, 1985.
[Dra87]
[DM85] P. Dembi
nski and J. Maluszy
nski. AND-parallelism with intelligent backtracking for annotated logic programs. In Proceedings of the International Symposium on Logic Programming, pages 2938. IEEE Computer Society, Boston,
MA, 1985.
[DM93] P. Deransart and J. Maluszy
nski. A Grammatical View of Logic Programming.
The MIT Press, Cambridge, MA, 1993.
[Dum92] B. Dumant. Checking soundness of resolution schemes. In K. R. Apt, editor,
Proceedings of the Joint International Conference and Symposium on Logic
Programming, pages 3751. MIT Press, Cambridge, MA, 1992.
[KK88]
T. Kawamura and T. Kanamori. Preservation of stronger equivalence in unfold/fold logic programming transformation. In ICOT Staff, editors, Proceedings of the International Conference on Fifth Generation Computer Systems,
pages 413422. Institute for New Generation Computer Technology, Tokyo,
1988.
[Pla84]
[PP94]
D.A. Rosenblueth. Using program transformation to obtain methods for eliminating backtracking in fixed-mode logic programs. Technical Report 7, Universidad Nacional Autonoma de Mexico, Instituto de Investigaciones en Matematicas Aplicadas y en Sistemas, 1991.
[Son86]
[TS84]
Chapter 8
Partial Correctness
8.1
Introduction
In Chapter 1 we defined partial correctness as the property that the program delivers correct results for relevant queries. However, logic and pure Prolog programs
can yield several answers and consequently partial correctness can be interpreted
in two ways.
Take as an example the already familiar APPEND program. It is natural that for
207
209
8.2
Example 8.3 Consider the following specification for the relation member:
premember = {member(s, t) | t is a list},
postmember = {member(s, t) | s is an element of the list t}.
Then premember holds for member(s,t) iff t is a list and postmember holds for
member(s,t) iff s is an element of the list t. In contrast, the set
{member(s, t) | s is a variable and t is a list},
is not an assertion since it is not closed under substitution.
211
2
2
We can now draw the following conclusions. The first one is analogous to the
Well-modedness Corollary 7.10.
Corollary 8.7 (Well-assertedness) Let P and Q be well-asserted. Then all
queries in all SLD-derivations of P {Q} are well-asserted.
2
The next one deals with LD-derivations and is analogous to the Data Drivenness
Lemma 7.17.
Corollary 8.8 (Pre-assertion) Let P and Q be well-asserted, and let be an
LD-derivation of P {Q}. Then |= pre(A) for every atom A selected in .
Applications
213
8.3
Applications
We now explain how the results of the previous section allow us to establish properties of the form {Q} P Q. For simplicity, in the examples below we restrict our
attention to atomic queries. We shall use the following immediate conclusion of
the Post-assertion Corollary 8.9. Recall from Section 4.2 that for an atom A we
denote by inst(A) the set of all instances of A.
Corollary 8.10 (Partial Correctness) Let P and A be well-asserted, where A
2
is a p-atom. Then {A} P inst(A) postp .
To see the usefulness of this corollary let us apply it to specific programs.
Applications
215
Append, again
Consider now the query app(Xs, Ys, u), where u is a ground list. Note that this
query is not well-asserted w.r.t. the considered specification of app. So to deal with
its partial correctness we need to use a different specification for the app relation.
Now let
preapp = {app(s, t, u) | u is a list},
postapp = {app(s, t, u) | s, t, u are lists and s t = u}.
Exercise 98 Prove that APPEND is well-asserted w.r.t. this specification.
Note that now the query app(Xs, Ys, u) is well-asserted. By the Partial Correctness Corollary 8.10 we conclude that
{app(Xs, Ys, u)} APPEND inst(app(Xs, Ys, u)) postapp .
But it is easy to see that
inst(app(Xs, Ys, u)) postapp = {app(s, t, u) | s t = u},
so we conclude that every successful SLD-derivation of APPEND {app(Xs, Ys, u)}
yields a computed instance app(s,t,u) such that s*t = u.
Notice that for each of the uses of APPEND we considered only ground lists. The
reasons for this restriction will be discussed in Section 8.7.
Exercise 99 To avoid separate consideration of the two uses of APPEND discussed above
we can introduce the following specification which is a set-theoretical union of the previous two ones:
preapp = {app(s, t, u) | s,t are lists or u is a list},
postapp = {app(s, t, u) | s, t, u are lists and s t = u}.
Prove that APPEND is well-asserted w.r.t. this specification.
Sequence
As a somewhat less trivial example consider now the SEQUENCE program:
% sequence(Xs) Xs is a list of 27 elements.
sequence([ , , , , , , , , , , , , , , , , , , , , , , , , , , ]).
% question(Ss) Ss is a list of 27 elements forming the desired sequence.
question(Ss)
sequence(Ss),
sublist([1, ,1, ,1], Ss),
sublist([2, , ,2, , ,2], Ss),
sublist([3, , , ,3, , , ,3], Ss),
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
Applications
217
As an illustration let us check the last claim. Suppose that for some substitution
sublist(Xs, Ys) presublist , app(Us, Zs, Ys) postapp and app(Xs, Vs, Zs)
postapp . This means that Xs, Ys, Us, Zs, Vs are lists such that Us Zs = Ys
and Xs Vs = Zs. So Us (Xs Vs) = Ys. Hence Xs is a sublist of Ys,
that is sublist(Xs, Ys) postsublist .
Finally, we prove that the clause defining the question relation is well-asserted.
The only statement, the proof of which requires some justification, is the following
one:
|= pre(question(Ss)),
post(sequence(Ss)),
post(sublist([1, ,1, ,1], Ss)),
post(sublist([2, , ,2, , ,2], Ss)),
post(sublist([3, , , ,3, , , ,3], Ss)),
post(sublist([4, , , , ,4, , , , ,4], Ss)),
post(sublist([5, , , , , ,5, , , , , ,5], Ss)),
post(sublist([6, , , , , , ,6, , , , , , ,6], Ss)),
post(sublist([7, , , , , , , ,7, , , , , , , ,7], Ss)),
post(sublist([8, , , , , , , , ,8, , , , , , , , ,8], Ss)),
post(sublist([9, , , , , , , , , ,9, , , , , , , , , ,9], Ss)
post(question(Ss)).
So assume that for some substitution the term Ss is such that
it is a list of 27 elements,
for all i [1, 9] a list of the form [i,. . .,i,. . .,i], where there are exactly i
numbers between successive occurrences of i, is a sublist of Ss.
This implies that Ss is a desired sequence, that is question(Ss) postquestion .
We conclude that SEQUENCE is well-asserted. Note that the query question(Ss)
is well-asserted as well. By the Partial Correctness Corollary 8.10 we conclude that
{question(Ss)} SEQUENCE inst(question(Ss)) postquestion ,
which implies that every successful SLD-derivation of SEQUENCE {question(Ss)}
yields a computed instance question(s) such that s is a desired sequence.
Palindrome
Finally, consider the program PALINDROME:
% palindrome(Xs) the list Xs is equal to its reverse.
palindrome(Xs) reverse(Xs, Xs).
% reverse(Xs, Ys) Ys is the reverse of the list Xs.
reverse(X1s, X2s) reverse(X1s, [], X2s).
% reverse(Xs, Ys, Zs) Zs is the result of concatenating
the reverse of the list Xs and the list Ys.
reverse([], Xs, Xs).
reverse([X | X1s], X2s, Ys) reverse(X1s, [X | X2s], Ys).
(8.1)
8.4
219
We now turn our attention to a method that allows us to compute the strongest
postcondition of a query, that is sets of the form sp(Q, P ) where Q is a query and
P a program.
Recall from Section 4.3 (Definition 4.6) that given a program P and a query Q,
the query Q0 is called a correct instance of Q if Q0 is an instance of Q such that
P |= Q0 .
Our approach is based on the following simple result the proof of which uses the
basic properties of the SLD-resolution.
Theorem 8.11 (Intersection) Assume that the Herbrand universe of the underlying language L is infinite. Consider a program P and a query Q. Suppose that
the set Q of ground correct instances of Q is finite. Then sp(Q, P ) = Q.
Proof. First note that
every correct instance Q0 of Q is ground.
(8.2)
Indeed, otherwise, by the fact that the Herbrand universe is infinite, the set Q
would be infinite.
Consider now Q1 Q. By the Strong Completeness Theorem 4.13, there exists
a computed instance Q2 of Q such that Q2 is more general than Q1 . By the
Soundness Theorem 4.4, Q2 is a correct instance of Q, so by (8.2) Q2 is ground.
Consequently Q2 = Q1 , that is Q1 is a computed instance of Q.
Conversely, take a computed instance Q1 of Q. By the Soundness Theorem 4.4
2
Q1 is a correct instance of Q. By (8.2) Q1 is ground, so Q1 Q.
The assumption that the Herbrand universe is infinite is a very natural one since
it can be rephrased as the underlying language L has infinitely many constants
or at least one constant and one (non-nullary) function symbol.
In the sequel we shall use the following consequence of this theorem which explains the relevance of the least Herbrand model M(P ) in our approach.
Corollary 8.12 (Intersection 1) Assume that the Herbrand universe of L is
infinite. Consider a program P and an atom A. Suppose that the set ground(A)
M(P ) is finite. Then sp(A, P ) = ground(A) M(P ).
Proof. It suffices to notice the following string of equivalences:
A is a ground correct instance of A
iff P |= A and A is ground
iff
{Success 2 Theorem 4.37}
M(P ) |= A and A is ground
iff A ground(A) M(P ),
and use the Intersection Theorem 8.11.
Exercise 101 Show that for well-moded programs and queries the assumption that
the Herbrand universe of L is infinite is not needed in the above Corollary.
Hint. Use the Computed Answer Corollary 7.11 and the Well-modedness Theorem 7.12.
2
221
pre :=
ground(prep ),
p is in P
post :=
p is in P
ground(postp ),
223
{TP is monotonic}
TP n pre.
To prove the other inclusion take H TP n pre. Then there exists H
B1 . . . Bm ground(P ) such that
{B1 . . . Bm } TP (n 1).
(8.3)
(8.4)
holds.
Base. m = 0. The claim holds vacuously.
Induction step. m > 0. Assume that {B1 . . . Bm1 } pre. This together with
(8.3) implies {B1 . . . Bm1 } M(pre,post) (P ) and, hence, by the Pre-post Note 8.15
{B1 . . . Bm1 } post holds. Since by assumption H pre, it follows by the
definition of well-assertedness and Exercise 102 that Bm pre. This proves (8.4).
Now by the induction hypothesis, (8.3) and (8.4) imply {B1 . . . Bm } Tpre(P )
(n 1) and consequently H Tpre(P ) n which concludes the main induction
proof.
2
So M(pre,post) (P ) is the least Herbrand model of the program pre(P ). We can
now prove the modifications of the previous two results that dealt with M(P ),
now reformulated for the case of the Herbrand interpretation M(pre,post) (P ).
Corollary 8.17 (Intersection 2) Assume that the Herbrand universe of L is
infinite. Consider a well-asserted program P and a well-asserted atomic query
A. Suppose that the set ground(A) M(pre,post) (P ) is finite. Then sp(A, P ) =
ground(A) M(pre,post) (P ).
(8.5)
Exercise 104 Prove that for a well-asserted program P and a well-asserted query Q
M(P ) |= Q iff M(pre,post) (P ) |= Q.
Hint. Proceed by induction on the number of atoms in Q and use the Pre-post Note
8.15.
2
Theorem 8.18 (Unique Fixed Point 2) Let P be a left well-asserted terminating program. Then M(pre,post) (P ) is a unique fixpoint of Tpre(P ) .
Proof. By Exercise 17 of Section 3.5 the (possibly infinite) program ground(P ) is
left terminating, so a fortiori pre(P ). The result now follows by the M(pre,post) (P )
Theorem 8.16 combined with the Unique Fixed Point 1 Theorem 8.13 applied to
the program ground(P ).
2
Exercise 105 Provide an alternative proof of the implication if P is left terminating
then ground(P ) is left terminating using the Equivalence 2 Corollary 6.29.
Hint. Note that the implication P be acceptable implies that P is left terminating
holds for infinite programs, as well.
2
8.5
Applications
We now show how the last two results can be used to draw stronger conclusions
about the example programs considered in Section 8.3. Throughout this section
we assume that the Herbrand universe of L is infinite so that we can apply the
Intersection 2 Corollary 8.17.
Append
Assume the specification of APPEND considered in Exercise 99. We have already
noted in the previous section then that
M(pre,post) (APPEND) = {app(s,t,u) | s,t,u are ground lists and s*t=u}.
Exercise 106 Provide a direct proof of this fact by checking that the set
{app(s, t, u) | s,t,u are ground lists and s*t=u}
is a fixed point of Tpre(APPEND) .
Applications
225
Now let s,t be ground lists and consider the well-asserted query app(s, t, Zs).
Note that the set ground(app(s, t, Zs)) M(pre,post) (APPEND) consists of just one
element: app(s,t,s*t). Thus by the Intersection 2 Corollary 8.17 we conclude
that
sp(app(s, t, Zs), APPEND) = {app(s, t, s t)}.
Consider now the well-asserted query app(Xs, Ys, u), where u is a ground list.
Note that
ground(app(Xs, Ys, u)) M(pre,post) (APPEND) =
{app(s, t, u) | s, t are ground lists, s t = u}.
But each list can be split only in finitely many ways, so the set
ground(app(Xs, Ys, u)) M(pre,post) (APPEND)
is finite. Thus, again by the Intersection 2 Corollary 8.17,
sp(app(Xs, Ys, u), APPEND) = {app(s, t, u) | s, t are ground lists, s t = u}.
Sequence
Next, consider the SEQUENCE program with its specification used in Section 8.3.
Exercise 107 Check then that
M(pre,post) (SEQUENCE) = M(pre,post) (APPEND)
{sublist(s, t) | s, t are ground lists, s is a sublist of t}
{sequence(s) | s is a ground list of length 27}
{question(s) | s is a desired list}
by applying the Unique Fixed Point 2 Theorem 8.18.
Exercise 110 Prove that for the MEMBER program (see Exercise 97) we have for a
ground list s
sp(member(X, s), MEMBER) = {member(a, s) | a is an element of s}.
2
8.6
Absence of Failures
Concluding Remarks
227
LD-derivation of APPEND {app(s, t, Zs)}. All that we proved there was that
app(s,t,s*t) is the only possible computed instance of this query.
On the other hand, the method considered in Sections 8.4 and 8.5 does allow
us to draw such a conclusion, because we were able to prove there that the set
sp(app(s, t, Zs), APPEND) is non-empty.
In other words, the use of the strongest postconditions allows us to establish the
absence of failures. However, to prove this property a somewhat simpler method
is sufficient. Namely, we have the following simple consequence of the results
established in Chapter 4.
Corollary 8.19 (Absence of Failures) Assume that the language L has infinitely many constants. Consider a program P and an atom A. Suppose that the set
ground(A) M(P ) is non-empty. Then there exists a successful LD-derivation of
P {A}.
Proof. By the Herbrand Interpretation Lemma 4.26(i) the set ground(A) M(P )
is non-empty iff for some substitution such that A is ground, M(P ) |= A. Now
by the Success 2 Theorem 4.37 we obtain P |= A and by the Strong Completeness
Theorem 4.13 we can draw the desired conclusion.
2
As in Section 8.4 for a well-asserted program P and a well-asserted atomic query
A we can, by virtue of (8.5), refine this result so that the well-typed fragment
M(pre,post) (P ) of the least Herbrand model of P is used.
As a simple application of this result note that for lists s0 , t0 there exists a
successful LD-derivation of APPEND {app(s0 , t0 , Zs)} because the set
ground(app(s0 , t0 , Zs)) {app(s, t, u) | s,t,u are ground lists and s*t=u}
is non-empty.
Note that this result cannot be established using the Intersection 1 Corollary
8.12 because for non-ground lists s0 , t0 the above set is infinite.
Exercise 111 Take a list s such that s = rev(s). Prove that there exists a successful
LD-derivation of PALINDROME {palindrome(s)}.
8.7
Concluding Remarks
With this chapter we conclude the study of correctness of pure Prolog programs.
In general, given a program P and a relevant query Q we would like to establish
the following properties:
all the LD-derivations of P {Q} terminate,
P {Q} is occur-check free,
all successful LD-derivations of P {Q} yield the desired results,
Bibliographic Remarks
229
8.8
Bibliographic Remarks
The partial correctness of logic programs has been studied for a long time. For
early references see, e.g. Clark and Tarnlund [CT77], Clark [Cla79] and Hogger
[Hog84]. In Deransart [Der93] various approaches are discussed and compared.
Among them the most powerful one is the inductive assertion method of Drabent
and Maluszy
nski [DM88] that allows us to prove various program properties that
can be expressed only using non-monotonic assertions, that is assertions not closed
under substitution.
In all these approaches, specifications are associated with the relations occurring
in the program. In Colussi and Marchiori [CM91] an alternative proof method
is introduced in which assertions are attached to the program points. This allows
them to study global run-time properties, such as the variable disjointness of two
atoms during the program execution.
The method used here and based on the notion of a well-asserted query and
program and discussed in Section 8.2, coincides with the partial correctness method
of Bossi and Cocco [BC89], though in our presentation we abstracted from the
concrete syntax introduced in this paper. In Apt and Marchiori [AM94] it is shown
that this method is a special case of the method of Drabent and Maluszy
nski
[DM88].
The approach to partial correctness that aims at computing the strongest postcondition of a query and based on the use of the Intersection Theorem 8.11 and
the Intersection 1 Corollary 8.12 is from Apt [Apt95]. The Unique Fixed Point 1
Theorem 8.13 is from Apt and Pedreschi [AP93].
The refinement of this approach to well-asserted programs and queries, based on
the use of the M(pre,post) (P ) interpretation and the M(pre,post) (P ) Theorem 8.16,
Intersection 2 Corollary 8.17 and the Unique Fixed Point 2 Theorem 8.18, is from
Apt et al. [AGP96].
The Pre-post Note 8.15 is from Ruggieri [Rug94] and Exercise 104 is from Apt
et al. [AGP96].
8.9
Summary
8.10
References
[AGP96] K.R. Apt, M. Gabbrielli, and D. Pedreschi. A closer look at declarative interpretations. Journal of Logic Programming, 28(2): 147180, 1996.
[AM94]
[AP93]
[Apt95]
[BC89]
[Cla79]
[CM91]
L. Colussi and E. Marchiori. Proving correctness of logic programs using axiomatic semantics. In K. Furukawa, editor, Proceedings of the 1991 International Conference on Logic Programming, pages 629644. MIT Press, Cambridge, MA, 1991.
References
231
[CT77]
A. T
arnlund. A first order theory of data and programs. In B.
K. Clark and S-
Gilchrist, editor, Information Processing 77, pages 939944. North-Holland,
Toronto, 1977.
[Der93]
[DM88]
Chapter 9
Every realistic programming language needs to provide some facilities to deal with
arithmetic. In Chapter 5 we formalized natural numbers by means of zero and
the successor function. This representation allows us to write programs that deal
with natural numbers. However, programming based on this formalization is very
laborious and hardly useful for computation.
The aim of this chapter is to discuss Prolog facilities for dealing with numbers or
more generally with arithmetic. For simplicity we limit our attention in this book
to integers.
We begin our presentation by discussing arithmetic operations in the next section. We also explain there Prologs facilities for dealing with the binding power
and for allowing the use of arbitrary function symbols in the so-called infix and
bracketless prefix notation.
Next, we discuss in Section 9.2 arithmetic comparison relations.
Then we resume the style of presentation adopted in Chapter 5, so we introduce
programs according to the domains over which they compute.
More specifically, in Section 9.3 we provide an example of a program computing
over a complex domain (in the sense of Section 5.6) that involves integers. Then,
in Section 9.4, we present programs that compute over an often used domain
lists of integers.
In Section 9.5 we introduce an important programming technique called difference lists, that allows us to concatenate the lists in constant time and illustrate
its use on a number of examples.
Next, in Section 9.6, we consider programs that compute over binary search
trees, a subclass of the binary trees studied in Chapter 5 which remain properly
balanced.
In Section 9.7, we explain Prologs way of evaluating arithmetic expressions
by means of an arithmetic evaluator is and introduce some programs that use
this construct. Finally, in Section 9.8, we summarize the relevant aspects Prologs
arithmetic facilities and assess their shortcomings.
232
Operators
9.1
233
Operators
addition, written as +,
subtraction, written as -,
multiplication, written as *,
integer division, written as //,
remainder of the integer division, written as mod,
fx,
fy,
xf,
yf.
The mnemonics yfy is not allowed, as it would imply both left and right associativity and would thus permit the interpretation of a term of the form s f t f
u both as (s f t) f u and s f (t f u). Consequently, it would not provide a
unique interpretation to the term.
The declaration of an operator g is a statement of the form
:- op(pr, mn, g).
written in the program before the first use of g; pr is the priority of g and mn is
the mnemonic of g.
(Following our convention of adhering to logic programming notation we should
actually use in the above declaration instead of :-. As such declarations
do not have a logical interpretation we shall rather use Prologs :-.)
Formally, in presence of operator declarations, terms are defined inductively as
follows, where with each term we associate a priority in the form of a natural
number between 0 and 1200 inclusive and an interpretation in the sense mentioned
above:
a variable is a term with priority 0 and itself as its interpretation,
if t is a term with interpretation i(t), then (t) is a term with priority 0 and
interpretation i(t) (that is, bracketing reduces the priority to 0),
if f is an n-ary function symbol and t1 , . . ., tn are terms with respective
interpretations i(t1 ), . . ., i(tn ), then f (t1 , . . ., tn ) is a term with priority 0 and
interpretation f (i(t1 ), . . ., i(tn )),
if f is a binary operator with priority pr and s and t are terms with respective
priorities pr(s) and pr(t) and interpretations i(s) and i(t), then sf t is a term
with priority pr and interpretation f (i(s), i(t)), according to the table below
and subject to the corresponding conditions:
mnemonics
xfx
xfy
yfx
conditions
pr(s) < pr, pr(t) < pr
pr(s) < pr, pr(t) pr
pr(s) pr, pr(t) < pr
Operators
235
if f is a unary operator with priority pr and s is a term with priority pr(s) and
interpretation i(s), then the following is a term with priority pr and interpretation f (i(s)), according to the table below and subject to the corresponding
condition:
term
fs
fs
sf
sf
mnemonics
fx
fy
xf
yf
condition
pr(s) < pr
pr(s) pr
pr(s) < pr
pr(s) pr
The arithmetic operators are disambiguated by declaring them internally as follows (the declarations are the ones used in SICStus Prolog):
::::-
op(500,
op(500,
op(400,
op(300,
Here a list notation is used to group together the declarations of the operators with
the same mnemonics and priority.
Returning to our original examples of possibly ambiguous arithmetic terms, we
now see that 4+3*5 is a term with priority 500 and interpretation +(4, *(3,5)),
-3+4 is a term with priority 500 and interpretation +(-(3), 4) and 12//4//3 is a
term with priority 400 and interpretation //(//(12,4), 3). In addition, note that
the declaration of negation of a natural number with the mnemonics fx implies
that - - 3 is not a (legal) term. In contrast, -(-3) is a (legal) term.
The list of arithmetic operators introduced at the beginning of this section together with infinitely many integer constants: 0, -1, 1, -2, 2, . . . determines a
language of terms in the sense just defined. We call terms defined in this language
arithmetic expressions and introduce the abbreviation gae for ground arithmetic
expressions.
It is worthwhile mentioning that Prolog built-in operators, in particular arithmetic operators, can also be written in the customary prefix notation. (The comma
, though requires a slightly different treatment: in SICStus Prolog it has to be
written then as ,, like for instance in ,(A,B).) In particular each arithmetic
expression can also be written as a term that is its interpretation in the sense discussed above. In the case of ground arithmetic expressions both forms are equal.
For example, we have
| ?- 4+3*5 =:= +(4, *(3,5)).
yes
Finally, let us mention that Prolog also allows floating point numbers (called
floats) but we shall not discuss them here.
9.2
With each gae we can uniquely associate its value, computed in the expected way.
Prolog allows us to compare the values of gaes by means of arithmetic comparison
relations (in short comparison relations). The following six comparison relations
are provided:
237
This ensures for instance that the atom 3+4=5*7 stands for (3+4)=(5*7), because
the priority of =/2 is higher (so its binding power weaker) than that of + and
*.
As another example of the use of infix operators let us introduce a binary relation
<> with the intention of writing X<>Y instead of diff(X, Y). To this end we need
to declare it by, say,
:- op(600, xfy, <>).
and define it by the rule
X<>Y diff(X, Y).
Once diff is the relation defined in Exercise 52 of Section 5.3 we can now write
queries like neighbour(X, guatemala), neighbour(Y, guatemala), X<>Y.
Let us return now to the comparison relations. The comparison relations work
on gaes and produce the outcome expected to anyone familiar with the basics of
arithmetic. So for instance, > compares the values of two gaes and succeeds if the
value of the first argument is larger than the value of the second and fails otherwise.
Thus, for example
| ?- 5*2 > 3+4.
yes
| ?- 7 > 3+4.
no
When one of the arguments of the comparison relations is not a gae, the computation ends in an error . For example, we have
| ?- [] < 5.
! Error in arithmetic expression: [] is not a number
no
Such type of errors are called run-time errors, because they happen during the
program execution. The outcome of this form of an error is that the computation
terminates abnormally, without producing any result. Here the error took place
immediately, but it is easy to imagine a situation when it happens only after several
computation steps. So when the built-in comparison relations are used, one has to
be careful when using specific queries.
As a first example of the use of the comparison relations consider the following
program that computes the maximum of two gaes:
9.3
Complex Domains
Recall from Section 5.6 that by a complex domain we mean a domain built from
some constants by means of function symbols. We now discuss an example of a
complex domain that involves integers.
To this end let us return here to the problem of representing information about
countries (see Section 5.3). Suppose that additionally to the information about
the neighbouring countries we also wish to store for each country such items as
its capital with its population, surface, population, language(s) used, etc. One
possibility is to use a relation, say country of the arity corresponding to the number
of items of interest. The neighbours could be stored in the form of a list which
would fill one argument of the country relation and similarly with the languages
used. Then to ask a question like
list the countries whose capital has less than 100000 inhabitants
we could use the query
country(Name, , Cap Population, , , . . .), Cap Population < 100000.
There are two disadvantages of such a representation. First, each time a query
is formulated, the whole atom country(. . .) needs to be used. Secondly, when
the number of items to be considered grows, it is better to arrange them in a
hierarchical way which naturally reflects their logical structure. Such a structuring
can easily be done using function symbols. In our case we can envisage the following
term structure:
country(Name,
capital(Cap Name, Cap Population),
Surface,
people(Population, Languages),
Neighbours
)
Then each entry will be a fact of the form entry(country( . . .)). To make the
interaction with the so stored information more natural, it is useful to define selector relations which select appropriate fields. In particular, using the anonymous
variables we can define
Complex Domains
239
name(country(Name, , , , ), Name).
surface(country( , , Surface, , ), Surface).
cap population(country( , capital( , Cap Population), , , ),
Cap Population).
neighbours(country( , , , , Neighbours), Neighbours).
etc. We can now formalize various questions in a natural way as queries. For
example,
list the countries whose capital has less than 100000 inhabitants
| ?- entry(Country), cap population(Country, Cap Population),
Cap Population < 100000,
list the neighbours of Honduras whose surface is larger than 50000 km2
| ?- entry(Country), name(Country, honduras),
neighbours(Country, Neighbours),
member(Name, Neighbours), name(Neighbour, Name),
surface(Neighbour, Surface), Surface > 50000.
where member refers to the program MEMBER, etc.
Exercise 113 A disadvantage of this representation is that each time a query is posed,
the values of all local variables, like Country, Neighbours, Surface, in the last example, are printed. Find a way to avoid it.
2
The above representation of the countries could be made more readable using
SICStus Prologs infix operator : and writing each country in the following form:
country(name : Name,
capital : (Cap Name, Cap Population),
surface : Surface,
people : (Population, Languages),
neighbours : Neighbours
)
Here name, capital, surface, people and neighbours are constants which
play a role analogous to the field identifiers in Pascal records.
The operator : is defined internally in SICStus Prolog as
:- op(550, xfy, :).
The precedence of : implies that its binding power is stronger than , which
ensures its correct usage in the above representation.
9.4
Lists of Integers
Lists of integers form an often used domain for which a number of important
programs exist. In all of them we use the built-in comparison relations on gaes. In
fact, all these programs work equally well on lists of gaes.
Ordered List
The following program checks whether a list is an ordered one.
% ordered(Xs) Xs is an -ordered list of integers.
ordered([]).
ordered([X]).
ordered([X, Y | Xs]) X Y, ordered([Y | Xs]).
Program: ORDERED
Note that according to this program any one element list is ordered. To enforce
a more limited interpretation we could use in the second clause Prologs built-in
integer which tests whether a term is an integer. For example, we have
| ?- integer(-5).
yes
Slowsort
One of the most fundamental operations on the lists is sorting. The task is to
sort a list of integers. We begin our presentation with the following naive way of
sorting.
% ss(Xs, Ys) Ys is an ordered permutation of the list Xs.
ss(Xs, Ys) permutation(Xs, Ys), ordered(Ys).
augmented by the PERMUTATION program.
augmented by the ORDERED program.
Program: SLOWSORT
Obviously, this program is hopelessly inefficient and, as already explained in Section
1.4, it is often used as a benchmark.
Quicksort
A more efficient way of sorting, called quicksort was proposed by Hoare [Hoa62].
According to this sorting procedure, a list is first partitioned into two sublists
using an element X of it, one consisting of elements smaller than X and the other
consisting of elements larger or equal than X. Then each sublist is quicksorted and
the resulting sorted sublists are appended with the element X put in the middle.
This can be expressed in Prolog as follows where X is chosen to be the first element
of the given list:
Lists of Integers
241
Difference Lists
243
The fair split of a list is achieved here in an elegant way by means of the reversed
order of parameters in the recursive call of split.
Exercise 115 Write a program which formalizes the following sorting procedure (called
insertion sort): to sort a list, sort its tail and insert the head in the sorted tail so that
the order is preserved.
2
9.5
Difference Lists
When discussing the programs REVERSE and QUICKSORT ACC we noted that they
rely on the use of accumulators. From accumulators there is only one step to an
important alternative representation of lists, called difference lists.
One of the drawbacks of the concatenation of lists performed by the APPEND
program is that for lists s,t the execution of the query app(s, t, Z) takes the
number of steps that is proportional to the length of the first list. Difference list is
a generalization of the concept of a list that allows us to perform concatenation in
constant time. The fact that many programs rely explicitly on list concatenation
explains importance of difference lists.
In what follows we use the subtraction operator - written in the infix form. Its
use has nothing to do with arithmetic, though intuitively one should read it as the
difference. Formally, a difference list is a construct of the form [a1 , ..., am |x] x,
where x is a variable and where we used the notation introduced in Section 5.5. It
represents the list [a1 , ..., am ] in a form amenable to a different definition of concatenation. Namely, consider two difference lists [a1 , ..., am |x] x and [b1 , ..., bn |y] y.
Then their concatenation is the difference list [a1 , ..., am , b1 , ..., bn |y] y.
This concatenation process is achieved by the program that consists of a single
clause.
% append(Xs, Ys, Zs) the difference list Zs is the result of concatenating
the difference lists Xs and Ys.
append(X-Y, Y-Z, X-Z).
Program: APPEND DL
For example, we have:
| ?- append([a,b|X]-X, [c,d|Y]-Y, U).
U = [a,b,c,d|Y]-Y,
X = [c,d|Y]
which shows that U became instantiated to the difference list representing the list
[a,b,c,d]. By instantiating appropriately the output argument we can actually
obtain the outcome list directly, as an instance of a specific variable:
Difference Lists
245
=
=
=
=
=
=
=
a,
b,
[b|_A]-_A,
_A-_A,
[a,b|_A]-[a,b|_A],
[a,b|_A]-[b|_A],
[a,b|_A]-_A
A
B
U
V
X
Y
Z
=
=
=
=
=
=
=
9.6
a,
b,
[b|_A]-[a,b|_A],
_A-[a,b|_A],
[a,b|_A]-[a,b|_A],
_A-[b|_A],
_A-_A
247
if the right subtree is empty, then the root is the maximum, otherwise
the maximum of a search tree equals the maximum of its right subtree.
This leads to the following program where the minima and maxima are maintained only for non-empty trees:
% is search tree(Tree) Tree is a search tree.
is search tree(void).
is search tree(T) is search tree(T, Min, Max).
% is search tree(Tree, Min, Max) Tree is a search tree with a
minimum element Min and a
maximum element Max.
is search tree(tree(X, void, void), X, X).
is search tree(tree(X, void, Right), X, Max)
is search tree(Right, Min, Max), X < Min.
is search tree(tree(X, Left, void), Min, X)
is search tree(Left, Min, Max), Max < X.
is search tree(tree(X, Left, Right), Min1, Max2)
is search tree(Left, Min1, Max1), Max1 < X,
is search tree(Right, Min2, Max2), X < Min2.
Program: SEARCH TREE
Exercise 117 Analyze the behaviour of this program for non-ground terms.
Various operations can be easily defined and efficiently performed when a search
tree is used to represent the data. For example, to sort the elements stored in the
search tree it suffices to use the in-order traversal.
Exercise 118 Prove the above statement.
Tree)
Left,
Left,
Left,
Exercise 120 Write a program which tests whether a ground term is a search tree
by translating directly the definition of a search tree into clauses, using the programs
TREE MINIMUM and TREE MAXIMUM. Why is this program less efficient than the program
SEARCH TREE?
2
Insertion
Next, let us consider operations which change the search tree. In each case one
needs to take care that after the operation the tree remains a search tree. It is
easy to insert an element x in a search tree t in an appropriate way it suffices
to insert it as a leaf at the right position. The following algorithm does the job.
If t is empty, then create a tree with the root x and empty left and right subtrees.
Otherwise compare x with the root y of the search tree t.
If x =:= y, then output the present search tree; otherwise
if x < y, then insert x in the left subtree of t,
if x > y, then insert x in the right subtree of t.
The following program formalizes this algorithm.
249
xP
ooo PPPPP
o
o
PPP
o
PPP
ooo
ooo
P
o
===
===
=
==
==
r ===
l
=
=
___________
y ___________
Exercise 124 Write a program which sorts a list of integers by first repeatedly inserting
them into a search tree and then listing them by means of the in-order traversal.
2
Exercise 125 In general, arbitrary items are stored in a tree, not necessarily integers.
In this case it is customary to associate with each item a natural number key which
is used for all comparison purposes. Propose an appropriate data representation and
modify the proposed algorithms on search trees to this case.
2
As in the case of trees, insertions and deletions in a search tree can also degenerate it into a list. This problem can be avoided if trees are used which remain
balanced in presence of insertions and deletions. Several proposals were made in
the literature see, e.g. Cormen et al. [CLR90]. Some of them were elegantly
coded in Prolog see, e.g. Bratko [Bra86].
9.7
So far we have presented programs that use ground arithmetic expressions, but have
not yet presented any means of evaluating them. The advantages of evaluating gaes
will become clear in a moment. For example, no facilities have been introduced so
far to evaluate 3+4. All we can do at this stage is to check that the outcome is
7 by using the comparison relation =:= and the query 7 =:= 3+4. But using the
comparison relations it is not possible to assign the value of 3+4, that is 7, to a
variable, say X. Note that the query X =:= 3+4 ends in an error.
251
Between
The following program has already been defined in Section 1.4.
% between(X, Y, Z) X, Y are gaes and Z is an integer between X and Y,
inclusive.
between(X, Y, Z) X Y, Z is X.
between(X, Y, Z) X < Y, X1 is X+1, between(X1, Y, Z).
Program: BETWEEN
It allows us to generate all the integer values in a given range. For example:
| ?- between(10, 14, Z).
Z = 10 ;
Z = 11 ;
Z = 12 ;
Z = 13 ;
Z = 14 ;
no
Exercise 128 Usually the following, slightly simpler program is used to compute the
above relation:
between(X, Y, X) X Y.
between(X, Y, Z) X < Y, X1 is X+1, between(X1, Y, Z).
Find a query for which these two programs yield different results.
Concluding Remarks
253
Another Length
The LENGTH program of Section 5.5 is hardly useful for computing the length of
a list, since it computes it in terms of numerals. Here is Prologs version which
yields the arithmetic output:
% length(Xs, N) N is the length of the list Xs.
length([], 0).
length([ | Ts], N) length(Ts, M), N is M+1.
Program: LENGTH1
Exercise 129 Complete the program below to an alternative definition of the length
relation:
length(Ts, M) length1(Ts, N), . . ..
length1([], 0).
length1([ | Ts], N+1) length1(Ts, N).
2
Exercise 130 The use of the sequence relation in the program SEQUENCE is rather
awkward. Define a binary relation length1 such that for a natural number n the query
length1(Ss,n) generates a list Ss of different variables of length n and use it in an
alternative version of the program SEQUENCE. Can the relation length of the program
LENGTH1 be used for this purpose?
2
Exercise 131 Write a program that computes the number of nodes in a given binary
tree.
9.8
Concluding Remarks
In this chapter we introduced Prolog facilities dealing with arithmetic. To this end
we introduced the comparison relations on ground arithmetic expressions and the
arithmetic evaluator is. Let us now try to assess these Prolog features.
9.8.1
Comparison Relations
9.8.2
Arithmetic Evaluator
Ground arithmetic expressions can be evaluated only using the arithmetic evaluator is. However, its use can also easily cause a run-time error. Moreover, the
appropriate use of is in specific programs, like FACTORIAL, is quite subtle because
it relies on the introduction of fresh variables for holding intermediate results. This
proliferation of local variables makes an understanding of such programs more difficult. In imperative programming languages the reuse of the same variables in
computation can be seen in such circumstances as an advantage. In functional
programming the corresponding functions can be programmed in a much more
natural way.
As an example of these complications reconsider from Section 5.4 the task of
producing all pairs X, Y such that X + Y = s3 (0). In Section 5.4 we allowed X and Y
to be numerals and the corresponding query was simply sum(X, Y, s(s(s(0)))).
If we wish to produce all pairs of natural numbers X, Y such that X + Y = 3, then
the corresponding query X + Y = 3 is incorrect and we actually need to use a more
complicated and artificial query between(0,3,X), Y is 3-X.
We conclude that arithmetic facilities in Prolog are quite subtle and require good
insights to be properly used.
9.9
Bibliographic Remarks
As in Chapter 5, most of the programs we discussed here were taken from other
sources. In particular, Section 9.5 is influenced by the corresponding presentation
of difference lists in Sterling and Shapiro [SS86].
The above mentioned complications and shortcomings of Prologs arithmetic facilities have motivated work on the modifications of Prolog. In several language
proposals this problem was addressed by attempting to combine logic and functional programming. A number of first approaches to this subject are presented
in De Groot and Lindstrom [GL86] whereas more recent proposals are gathered
in Apt et al. [ABR93]. The two most recent programming languages based on
the logic programming paradigm which address Prologs problems with arithmetic
are LIFE (see At-Kaci [Ait93]) and Godel (see Hill and Lloyd [HL94]). For a
Summary
255
9.10
Summary
In this chapter we introduced a subset of Prolog that extends the one introduced
in Chapter 5 by Prologs facilities that allow us to deal with arithmetic. These
involve
use of operators to determine the binding power,
arithmetic comparison relations,
arithmetic evaluator is.
We followed here the presentation style used in Chapter 5 and presented programs according to the domain over which they computed. These were here
complex domains that involve integers,
lists of integers,
binary search trees.
9.11
References
[Ait93]
H. At-Kaci. An introduction to LIFE programming with logic, inheritance, functions, and equations. In D. Miller, editor, Proceedings of the
International Symposium on Logic Programming, pages 5268. MIT Press,
Cambridge, MA, 1993.
[ABR93]
K.R. Apt, de Bakker J.W., and J.J.M.M. Rutten, editors. Current Trends
in Logic Programming Languages. The MIT Press, Cambridge, MA, 1993.
[Bra86]
[Caj28]
[GL86]
[Han94]
[HL94]
[Hoa62]
[Ifr85]
[JM94]
[SS86]
L. Sterling and E. Shapiro. The Art of Prolog. MIT Press, Cambridge, MA,
1986.
Chapter 10
The aim of this chapter is to study verification of pure Prolog programs with
arithmetic. In Chapters 68 we developed methods that allowed us to deal with the
correctness of pure Prolog programs. Here we show that by a simple modification
these methods can also apply to pure Prolog programs with arithmetic. One new
aspect of correctness that we shall have to take into account is the possibility of
run-time errors due to the presence of arithmetic relations.
In the next section we begin our exposition by explaining what changes are
needed to the syntax assumed in Chapters 68 in order to deal with programs
written in pure Prolog with arithmetic (from now on called arithmetic programs).
In Section 10.2 we explain why the procedural interpretation of logic programs
cannot be used to model the possibility that an LD-derivation ends in an error.
In the subsequent two sections, that is 10.3 and 10.4, we show that both termination and the occur-check freedom of arithmetic programs can be established by
the methods originally developed in Chapters 6 and 7.
Then in Section 10.5 we introduce the notions of well-typed queries and programs
and use them in Section 10.6 to consider a new aspect of correctness, namely the
absence of errors in the presence of arithmetic relations.
Finally, in Section 10.7 we consider partial correctness. We explain there how
the methods developed in Chapter 8 naturally extend from the case of pure Prolog
programs to arithmetic programs.
10.1
Syntax
In the case of pure Prolog we first dealt with its foundations, that is logic programming, then introduced pure Prolog and subsequently, in Chapters 68, adjusted the
syntax of logic programs to be able to deal formally with pure Prolog programs.
In the case of pure Prolog with arithmetic we reversed the presentation and
first presented programs in pure Prolog with arithmetic. To deal formally with
257
10.2
Procedural Interpretation
When studying pure Prolog programs with arithmetic in a formal way we have to
decide about the status of the arithmetic built-ins, that is the comparison relations
<, , =:=, 6=, , > and the arithmetic evaluator s is t. Are they some further
unspecified relation symbols the definitions of which we can ignore? With this
choice we face the following problem.
One of the properties of these built-ins is that in some situations the evaluation
of atoms containing them results in an error. Now, the procedural interpretation
of logic programs does not provide any facilities to deal with such errors. However,
one could consider trading them for failure, that is to model their occurrence by
means of failed LD-derivations. Unfortunately, this is not possible.
Indeed, the query 1>0 succeeds, so by the Lifting Corollary 3.23 the query X>Y
succeeds, as well, whereas it was supposed to fail. Hence, it is not possible to model
the fact that the query X>Y ends in an error. We conclude that the procedural
interpretation of logic programming discussed in Chapter 3 cannot be used to
capture the behaviour of the arithmetic relations properly.
To model Prologs interpretation of arithmetic relations within logic programming we introduce the following notions.
Definition 10.1
We call an atom arithmetic if its relation symbol is either a comparison
relation <, , =:=, 6=, , >, or the arithmetic evaluator is.
We say that an arithmetic atom is correctly instantiated if either its relation
is a comparison relation and both of its arguments are gae or it is of the form
s is t and t is a gae.
We call an arithmetic atom incorrectly instantiated if it is not correctly instantiated.
We say that an LD-derivation ends in an error if in some of its query an
incorrectly instantiated arithmetic atom is selected.
2
Termination
259
In what follows we assume that an LD-derivation that ends in an error abnormally terminates with the first query in which an incorrectly instantiated arithmetic
atom is selected. We could enforce this by redefining the notion of an LD-resolvent
in the presence of arithmetic atoms. Thus in the presence of arithmetic a finite
LD-derivation can be successful, failed or abnormally terminating. In our considerations the abnormal termination is not studied separately. In fact, we try to
identify queries which both terminate in the sense studied in previous chapters
and are such that no LD-derivation of them ends in an error.
We now add to each program infinitely many clauses which define the ground
instances of the arithmetic relations. Given a gae n we denote by val(n) its value.
For example, val(3+4) equals 7. So for < we add the following set of unit clauses:
{m < n | m, n are gaes and val(m)<val(n)},
for is we add the set
{val(n) is n | n is a gae},
etc. We denote this infinite set of ground unit clauses by P (Ar). (Actually we
identify here atoms with unit clauses, but no confusion will result.)
For example, both the clauses 7 is 7 and 7 is 3+4 are in P (Ar), while 3+4
is 7 is not. We also assume that, conforming to the status of built-ins, in the
original program arithmetical relations are not used in clauses heads, that is they
are defined only by the above ground unit clauses.
From now on, when discussing the behaviour of pure Prolog programs with arithmetic we shall always assume that each such program is automatically augmented
by the program P (Ar).
These added clauses allow us to compute resolvents when the selected atom is
an arithmetic one. For example, using the leftmost selection rule, the query X is
3+4, X < 2+3 resolves to only one query, namely 7 < 2+3 (using the clause 7 is
3+4) and the query 7 < 2+3 fails. Thus all LD-derivations of the query X is 3+4,
X < 2+3 fail, which agrees with Prologs interpretation.
Now, that the programs consist of infinitely many clauses some properties of
them could be affected. For example, in Chapter 6 to study the LD-trees we
used the Konigs Lemma 6.2, which assumes that the considered tree is finitely
branching. Note, however, that thanks to the ending in an error provision every
query with a selected arithmetic atom has at most one descendant in every LD-tree.
Consequently, the resulting LD-trees remain finitely branching.
10.3
Termination
Termination
261
The clauses defining the part relation are recurrent w.r.t. level mapping
|part(x, xs, ls, bs)| = |xs|, |s > t| = 0 and |s t| = 0.
Extend now the above level mapping with
|qs(xs, ys)| = |xs|,
|app(xs, ys, zs)| = |xs|.
Recall from Section 6.2 that APPEND is recurrent w.r.t. | |. Next, define a
Herbrand interpretation of QUICKSORT by putting
I =
{1 + |xs| |ys|}
qs([x|xs], ys)
X > Y,
part(x, xs, ls, bs).
{|xs| |ls| + |bs|}
X Y,
part(x, xs, ls, bs).
{|xs| |ls| + |bs|}
{1 + |xs|}
{|xs|}
{|littles|}
{|bigs|}
{|ls|}
We conclude that the methods developed in Chapter 6 apply to arithmetic programs, as well. In fact, the base for our approach to termination, the Finiteness 1
Corollary 6.9 and Finiteness 2 Corollary 6.24, remain valid for arithmetic programs,
as the same proof carries through.
However, some caution has to be exercised when interpreting the results of Chapter 6 in the presence of arithmetic. Namely, the Acceptability Theorem 6.28 does
not hold any more. Indeed, consider the program with only one clause:
p X < Y, p.
Because the LD-derivations which end in an error are finite, the above program is
left terminating. However, it is easy to see that it is not acceptable just consider
the ground instance p 1<2, p and recall from Section 10.2 that the clause
1 < 2 is added to the program, so it is true in every model of it. (In contrast, the
program consisting of the clause
p X < X, p.
is acceptable.) This shows that the proposed method of proving termination is
somewhat less general in the case of programs with arithmetic.
Exercise 133 Call a program P error-free if for all ground non-arithmetic atoms A
the LD-derivations of P {A} do not end in an error.
Let P be a left terminating, error-free program. Prove that for some level mapping | |
and an interpretation I of P
Occur-check Freedom
263
10.4
Occur-check Freedom
Next, we deal with the issue of the occur-check. The approach of Chapter 7 is
applicable to pure Prolog programs with arithmetic without any modification. The
reason is that the unit clauses which define the arithmetic relations are all ground,
so they automatically satisfy the conditions of the Occur-check 1 Corollary 7.18 and
the Occur-check 2 Corollary 7.25. To see how these results apply here reconsider
the three programs considered in the previous section. The first two examples
confirm the usefulness of the methods provided in Chapter 7 while the third one
indicates their limitations.
Length1
First, consider the LENGTH1 program with the moding length(+,-), is(-,+).
Then LENGTH1 is well-moded and the heads of all clauses are output linear. By
the Occur-check 1 Corollary 7.18 for s ground, LENGTH1 {length(s, t)} is
occur-check free.
Moreover, in this moding LENGTH1 is also nicely moded and the heads of all
clauses are input linear. Thus, the Occur-check 2 Corollary 7.25 applies here, as
well and yields that when t is linear and s is an arbitrary term such that Var (s)
Var (t) = , LENGTH1 { length(s, t)} is occur-check free. In particular, this
conclusion holds for any list s and a variable t not appearing in s.
Quicksort
Next, consider QUICKSORT with the moding reflecting its use, that is
qs(+,-),
partition(+,+,-,-),
app(+,+,-),
>(+, +),
(+, +).
It is easy to check that QUICKSORT is then well-moded and the heads of all clauses
are output linear. The Occur-check 1 Corollary 7.18 applies and yields that for s
ground, QUICKSORT {qs(s, t)} is occur-check free.
Moreover, in this moding QUICKSORT is also nicely moded and the head of every
clause is input linear. Thus, the Occur-check 2 Corollary 7.25 applies, as well and
yields that when t is linear and Var (s) Var (t) = , QUICKSORT { qs(s, t)}
We conclude that the proof of the occur-check freedom of the QUICKSORT ACC
program is beyond the scope of the methods introduced in Chapter 7. In Apt and
Pellegrini [AP94] a refinement of the methods given in Chapter 7 is proposed which
can be used to deal with this program. Finally, note that if we reverse the order of
the recursive calls to qs acc, then the resulting program becomes well-moded and
we can prove its occur-check freedom using the Occur-check 1 Corollary 7.18.
10.5
To deal with the absence of run-time errors we now introduce types. They allow
us to ensure that the input positions of the selected atoms remain correctly typed
during the program execution.
The following very general definition of a type is sufficient for our purposes.
Definition 10.2 A type is a set of terms closed under substitution.
265
Definition 10.3 A type for an n-ary relation symbol p is a function tp from [1, n]
to the set Types. If tp (i) = T , we call T the type associated with the position i of p.
Assuming a type tp for the relation p, we say that an atom p(s1 , . . ., sn ) is correctly
typed in position i if si tp (i).
2
In the remainder of this section we adopt the following
Assumption Every considered relation has a fixed mode and a fixed type associated with it.
This assumption will allow us to talk about modes and types of input positions
and of output positions of an atom. An n-ary relation p with a mode mp and type
tp will be denoted by
p(mp (1) : tp (1), . . ., mp (n) : tp (n)).
We can talk about types of input positions and of output positions of an atom.
For example, part(+ : Gae, + : ListGae, : ListGae, : ListGae) denotes a
relation part with four arguments: the first position is moded as input and typed
Gae, the second position is moded as input and typed ListGae and the third and
fourth positions are moded as output and typed ListGae.
Intuitively, the modes and types indicate how the arguments of a relation should
be used: the given, known arguments should be put in the input positions and
these arguments should belong to the types of the corresponding input positions.
The terms in which the values should be computed should be put in the output
positions. The idea is that the computed values should belong to the types of the
corresponding output positions. This intuition is not precise because the computation can also instantiate the input positions and the output positions can be filled
in by terms which are not instantiated by the computation (for example, ground
terms of the appropriate types).
To prove that the modes and types are used in a way conforming to the above
intuition we need to impose certain conditions on a program and a query. This
brings us to the notion of a well-typed query and a well-typed program. The notion
of well-typed queries and programs relies on the concept of a type judgement.
By a typed term we mean a construct of the form s : S where s is a term and
S is a type. Given a sequence s : S = s1 : S1 , . . ., sn : Sn of typed terms we write
s S if for i [1, n] we have si Si .
Definition 10.4
A type judgement is a statement of the form s : S t : T.
We say that a type judgement s : S t : T is true and write
|= s : S t : T,
if for all substitutions , s S implies t T.
267
(j = n + 1) the types of the terms filling in the output positions of the head
can be deduced from the types of the terms filling in the input positions of
the head and the types of the terms filling in the output positions of the body
atoms.
Note that a query with only one atom is well-typed iff this atom is correctly
typed in its input positions and a unit clause p(s : S, t : T) is well-typed if
|= s : S t : T.
We now prove that the notion of well-typedness is a special case of the notion
of well-assertedness introduced in Chapter 8 in Definition 8.5. Below we use the
assumption made on page 265 which states that every considered relation has a
fixed mode and a fixed type associated with it.
Theorem 10.6 (Reduction) For every relation symbol there exists a specification in the sense of Definition 8.2 such that for every clause c and every query
Q
(i) c is well-typed iff c is well-asserted,
(ii) Q is well-typed iff Q is well-asserted.
Proof. For every relation symbol p let
prep = {p(s : S, t : T) | s S},
postp = {p(s : S, t : T) | t T}.
That is, prep consists of all the p-atoms that are correctly typed in their input
positions while postp consists of all the p-atoms that are correctly typed in their
output positions.
This implies the claim.
2
Exercise 136 Prove that the notion of a well-moded clause (respectively query) is a
special case of the notion of a well-typed clause (respectively query).
The result just proved allows us to draw conclusions analogous to those made
in Section 8.2, now reformulated for the notion of well-typedness. We list them
below.
Lemma 10.7 (Well-typedness) An SLD-resolvent of a well-typed query and a
well-typed clause is well-typed.
2
Corollary 10.8 (Well-typedness) Let P and Q be well-typed. Then all queries
in all SLD-derivations of P {Q} are well-typed.
2
Corollary 10.9 (Well-typed Computed Answer) Let P and Q be well-typed.
Then for every computed answer substitution for P {Q}, each atom in Q is
well-typed in its output positions.
2
10.5.1
Examples
The above results allow us to draw some conclusions about the program behaviour.
Let us see now to what extent these results can be applied to specific programs. We
concentrate here on our three running examples and analyze the use of Well-typed
Computed Answer Corollary 10.9. Note that this corollary refers to an arbitrary
selection rule. In the next section we shall analyze the use of the Correct Typing
Corollary 10.10.
Length1
We use the following modes and types:
length(+ : U, : Gae),
is( : Gae, + : Gae).
It is easy to check that LENGTH1 is then well-typed. Indeed, its first clause is
well-typed because 0 is a gae and the second clause is well-typed because if m is a
gae, then m+1 is a gae. Note that the above moding and typing imposes no type
restrictions on the input position of the length relation and consequently every
length-atom is well-typed.
By the Well-typed Computed Answer Corollary 10.9 we conclude that for arbitrary terms s,t every computed answer substitution of length(s,t) is such
that t is a gae.
Quicksort
The following moding and typing of the qs relation reflects the natural use of
QUICKSORT:
qs(+ : ListGae, : ListGae).
We now complete the moding and typing in such a way that QUICKSORT is welltyped:
> (+ : Gae, + : Gae),
(+ : Gae, + : Gae),
part(+ : Gae, + : ListGae, : ListGae, : ListGae),
app(+ : ListGae, + : ListGae, : ListGae).
By way of example, notice that the clauses of the APPEND program are well-typed
since that following implications clearly hold:
|= Ys : ListGae Ys : ListGae,
269
Exercise 139 Prove that for a list s all c.a.s.s for PERMUTATION {perm(s,t)} are
such that t is a list.
Hint. Use the following moding and typing:
perm(+ : List, : List),
app( : List, : List, + : List) for the first call to APPEND,
app(+ : List, + : List, : List) for the second call to APPEND.
2
Quicksort with Accumulator
To deal with the QUICKSORT ACC program we would like to use the same moding
and typing for the qs relation as in QUICKSORT, that is
qs(+ : ListGae, : ListGae).
Unfortunately, this moding and typing cannot be completed so that QUICKSORT ACC
is well-typed. Suppose otherwise. Then on account of the clause defining the qs
relation, we have to mode and type the last position of qs acc as : T1 , where
T1 ListGae. This in turn, on account of the first clause defining the qs acc
relation, forces us to mode the second position qs acc as + : T2 , where T2 T1 .
But then
|= [X|Xs] : ListGae, Zs : ListGae, o : O [X|Y1s] : T 2,
where o : O is the sequence of typed terms filling in the output positions of the
atom part(X, Xs, Littles, Bigs), is a part of the statement that the second
clause defining qs acc is well-typed. Hence,
|= [X|Xs] : ListGae, Zs : ListGae, o : O [X|Y1s] : ListGae.
Contradiction, since this type judgement is not true.
We conclude that, given a query qs(s,t) with s a list of gaes, we can prove
using the Well-typed Computed Answer Corollary 10.9 that for QUICKSORT every
computed answer substitution is such that t is a list of gaes, but that the same
conclusion cannot be established for QUICKSORT ACC using only this corollary. This
shows the limitations of the notion of well-typedness.
By the Well-typed Computed Answer Corollary 10.9 we now conclude that for a
query qs(s,t) with s a list of gaes, every computed answer substitution for the
QUICKSORT ACC1 program is such that t is also a list of gaes. Now by the Independence Theorem 3.33 the same conclusion can be drawn for the QUICKSORT ACC
program.
Exercise 141 Show that the moding qs(+, ) can be extended so that QUICKSORT ACC1
is well-moded.
10.6
We now show how the notion of well-typedness allows us to establish the absence
of run-time errors. Due to the introduced syntax limitations these errors can arise
only due to the use of arithmetic operations. To prove the absence of errors we
thus need to show that for a program and a query of interest, no LD-derivation
271
ends in an error. This requires an analysis of the form of the arguments of the
selected atoms. Now, the Correct Typing Corollary 10.10 does allow us to carry
out such an analysis. More precisely, the following conclusion of it will now be of
use for us.
Corollary 10.11 (Absence of Errors) Consider a program with arithmetic P
and a query Q such that
P and Q are well-typed,
each arithmetic comparison relation p is moded and typed p(+ : Gae, + :
Gae),
the arithmetic evaluator is is moded and typed is( : Gae, + : Gae) or
is(+ : Gae, + : Gae).
Then the LD-derivations of P {Q} do not end in an error.
Proof. It suffices to note that by virtue of the Correct Typing Corollary 10.10 all
selected arithmetic atoms in all LD-derivations of P {Q} are correctly instantiated.
2
This result can be used to prove the absence of errors, both for the arithmetic
comparison relations and for the arithmetic evaluator is. To see its usefulness we
now show how it can be applied to the three running examples of this chapter.
Length1
We use the modes and types used in the previous section, that is
length(+ : U, : Gae),
is( : Gae, + : Gae).
We noticed that LENGTH1 is then well-typed. By the Absence of Errors Corollary
10.11 we conclude that for arbitrary terms s,t the LD-derivations of LENGTH1
{length(s, t)} do not end in an error.
Quicksort
Again, we use the modes and types used in the previous section, that is
qs(+ : ListGae, : ListGae),
> (+ : Gae, + : Gae),
(+ : Gae, + : Gae),
part(+ : Gae, + : ListGae, : ListGae, : ListGae),
app(+ : ListGae, + : ListGae, : ListGae).
We noted that QUICKSORT is then well-typed. Assume now that s is a list of gaes
and t an arbitrary term. The query qs(s,t) is then well-typed and by the Absence
of Errors Corollary 10.11 we conclude that the LD-derivations of QUICKSORT
{qs(s, t)} do not end in an error.
Exercise 144 Let s be a list of gaes and t an arbitrary term. Prove that the LDderivations of MERGESORT {ms(s, t)} do not end in an error.
2
Quicksort with Accumulator
To deal with the QUICKSORT ACC program we use the following moding and typing:
qs(+ : ListGae, : U ),
qs acc(+ : ListGae, : U, : U ),
with the same moding and typing for the >, and part relations as those used
for QUICKSORT.
Exercise 145 Prove that QUICKSORT ACC is then well-typed.
Again by the Absence of Errors Corollary 10.11 we conclude that, for a list of
gaes s and an arbitrary term t, the LD-derivations of QUICKSORT ACC {qs(s, t)}
do not end in an error.
Note that we used here quite a weak typing in the sense that no type information about the output positions of the qs and qs acc relations was used. Yet
it was sufficient to establish the desired conclusion.
Exercise 146 Let s be a list of gaes and t an arbitrary term. Prove that all the
selected qs acc-atoms in all LD-derivations of QUICKSORT ACC {qs(s, t)} are of the
form qs acc(s,t,u) where s is a list of gaes and t is not a variable.
2
Exercise 147 Prove that for a binary tree t built from integers, the LD-derivations
of SEARCH TREE {search tree(t, min, max)} do not end in an error irrespectively of
the choice of the terms min and max.
2
10.7
Partial Correctness
10.7.1
The first notion of partial correctness was dealt with using the well-asserted queries
and well-asserted programs. When dealing with programs with arithmetic we need
to remember (see Section 10.2) that to each such program we added infinitely
Partial Correctness
273
many clauses which define the arithmetic relations. It is natural to assume a fixed
specification for all these relations. The specifications given below reflect the use of
these relations and closely correspond with the information contained in the unit
clauses that were added to each program with arithmetic. For < we define
pre< = {s < t | s, t are gaes},
post< = {s < t | s, t are gaes and val(m) < val(n)},
and similarly for other comparison relations. For the arithmetic evaluator is we
define
preis = {s is t | t is a gae},
postis = {s is t | t is a gae and s = val(t)}.
The use of the above pre-declared specifications forms the only difference between the notion of well-assertedness considered here and in Section 10.2.
The following observation shows that these pre-declared specifications are correct.
Note 10.12 (Arithmetic Relations) The program P (Ar) is well-asserted w.r.t.
to the above specifications.
Proof. It is sufficient to note that for each A P (Ar) we have |= post(A).
By the Partial Correctness Corollary 8.10 we conclude that for a ground list s
{length(s, N)} LENGTH1 inst(length(s, N)) postlength .
But the set inst(length(s, N)) postlength has just one element, length(s, |s|), so
every successful LD-derivation of LENGTH1 {length(s, N)} yields the computed
instance length(s, |s|).
Partial Correctness
275
acc
postqs
acc
10.7.2
To use these corollaries we use the following modifications of the Unique Fixed
Point Theorems 1 8.13 and 2 8.18.
Theorem 10.15 (Unique Fixed Point 3) Let P be an acceptable arithmetic
2
program. Then M(P ) is a unique fixpoint of TP .
Theorem 10.16 (Unique Fixed Point 4) Let P be an acceptable arithmetic
program. Then M(pre,post) (P ) is a unique fixpoint of Tpre(P ) .
2
Exercise 150 Prove the above two theorems.
Note that in both theorems we have now replaced left termination by acceptability. The reason is that, as noticed in Section 10.3, for arithmetic programs these
notions do not coincide. The result indicated in Exercise 133 is hardly of use here
because many natural programs, for example QUICKSORT, are not error-free (just
consider the query qs([a,b],c)).
We now illustrate the use of these results on our three running examples.
Length1
In the case of the LENGTH1 program we can use the Unique Fixed Point Theorem
3 10.15 to check that
M(LENGTH1) =
P (Ar)
{length(s, |s|) | s is a ground list}.
Partial Correctness
277
This theorem is applicable here, since we proved in Section 10.3 that the program
LENGTH1 is recurrent, that is acceptable.
So for a ground list s the set ground(length(s, N)) M(LENGTH1) consists of
just one element: length(s,|s|). In Section 10.6 we proved that for all terms
s,t the LD-derivations of length(s,t) do not end in an error. So the Intersection
3 Corollary 10.13 applies here and yields
sp(length(s, N), LENGTH1) = {length(s, |s|)}.
Quicksort
To deal with the QUICKSORT program we rather use the M(pre,post) (QUICKSORT)
interpretation, where we refer to the specifications for QUICKSORT given in the
previous subsection. We proved in Section 10.3 that QUICKSORT is acceptable and
in the previous subsection that it is well-asserted, so we can use the Unique Fixed
Point 4 Theorem 10.16 to determine the interpretation M(pre,post) (QUICKSORT).
Exercise 151 Prove that
M(pre,post) (QUICKSORT) =
P (Ar)
M(pre,post) (APPEND)
{part(a, s, ls, bs) | s, ls, bs are lists of gaes and
a splits s into ls and bs}
{qs(s, t) | s, t are lists of gaes and
t is a sorted permutation of s}.
by checking that the set on the right-hand side is indeed a fixpoint of the program
Tpre(QUICKSORT) .
2
So for a list of gaes s the set ground(qs(s, Ys)) M(pre,post) (QUICKSORT) consists
of just one element: qs(s,t), where t is a sorted permutation of s. In Section
10.6 we proved that the LD-derivations of QUICKSORT {qs(s, Ys)} do not end in
an error. Consequently, by the Intersection 4 Corollary 10.14 we have
sp(qs(s, Ys), QUICKSORT) = {qs(s, t)}.
Quicksort with Accumulator
Finally, we consider the QUICKSORT ACC program with the specifications given in
the previous subsection.
Exercise 152 Prove that
M(pre,post) (QUICKSORT ACC) =
P (Ar)
{part(a, s, ls, bs) | s, ls, bs are lists of gaes and
a splits s into ls and bs}
{qs(s, t) | s, t are lists of gaes and
t is a sorted permutation of s}
{qs acc(s, t, u) | s is a list of gaes and if t is
a list of gaes then sort(s)*t = u},
By the same reasoning as above we thus conclude that for a list of gaes s
sp(qs(s, Ys), QUICKSORT ACC) = {qs(s, t)}.
10.7.3
Absence of Failures
Finally, we consider the absence of failures. For pure Prolog programs we used the
Absence of Failures Corollary 8.19. For programs with arithmetic the following
modification of it can be used.
Corollary 10.17 (Absence of Failures 1) Assume that the language L has infinitely many constants. Consider a program P and an atom A. Suppose that the
LD-derivations of P {A} do not end in an error and that the set ground(A)M(P )
is non-empty. Then there exists a successful LD-derivation of P {A}.
2
Exercise 153 Prove the above corollary.
As a simple application of this corollary note that for a list s0 there exists a
successful LD-derivation of LENGTH1 {length(s0, N)} because the set
ground(length(s0 , N)) {length(s, |s|) | s is a ground list}
is non-empty.
As for a non-ground list s0 this set is infinite, this result cannot be established
using the Intersection 3 Corollary 10.13.
10.8
Concluding Remarks
Bibliographic Remarks
279
QUICKSORT ACC is of course just an example of a program for which more elaborate techniques are needed to deal with its correctness. In general, programs
that use accumulators and difference lists often (but not always see, e.g. the
PALINDROME program) fall into this category.
In this chapter we also dealt with a new problem, namely the absence of runtime errors due to the presence of arithmetic relations. To this end we modified the
notion of an LD-derivation and introduced the notion of well-typedness. In Section
10.5 we showed that this notion is a special case of the notion of well-assertedness.
So we could have carried out the considerations of Section 10.6 without introducing
the notion of well-typedness.
However, this notion is notationally simpler in that the modes and types can be
defined in a more compact and intuitive form. Moreover, Aiken and Lakshman
[AL93] showed that the problem of whether a program or query is well-typed w.r.t.
a given moding and typing is decidable for a large class of types which includes the
ones studied here. So, for this class of types, the modes and types can be declared
and checked at the compile time. This makes it possible to turn pure Prolog with
arithmetic into a typed language.
The remark at the end of Section 10.5 shows that the notions of well-modedness
and well-typedness depend in a crucial way on the ordering of the atoms in the
clause bodies. The same holds for the notion of well-assertedness. On the other
hand, as already observed in Section 8.1, the notion of the computed instance
does not depend on this ordering. So when studying the properties of computed
instances it would be natural to introduce more flexible versions of these notions
which abstract from the ordering of the atoms in the clause bodies. Exercise 142
offers one such a possibility.
10.9
Bibliographic Remarks
The modelling of Prologs interpretation of arithmetic relations within logic programming given in Section 10.2 is due to Kunen [Kun88].
The notions of well-typed query and well-typed program are due to Bronsard et
al. [BLR92]. (Unfortunately, a well-typed query (respectively program) is called
there a well-moded query (respectively program).) In this paper a language allowing us to define in a concise way recursive (i.e., inductively defined) and polymorphic (i.e., parametric) is introduced.
The definition of these notions given here follows Apt and Etalle [AE93] in that it
abstracts from the concrete syntax for types and from the way the type judgements
are proved. In this paper modes and types were used to identify a large class of
pure Prolog programs for which unification can be replaced by (iterated) matching,
a computing mechanism used in functional programming.
The Well-typedness Lemma 10.7 is from Apt and Luitjes [AL95]. It strengthens the original lemma of Bronsard et al. [BLR92] from LD-resolvents to SLD-
10.10
Summary
In this chapter we discussed the verification of pure Prolog programs with arithmetic. We noted that all the correctness aspects studied for pure Prolog programs,
that is
termination,
occur-check freedom,
partial correctness and
absence of failures
10.11
[AE93]
References
K. R. Apt and S. Etalle. On the unification free Prolog programs. In
A. Borzyszkowski and S. Sokolowski, editors, Proceedings of the Conference
on Mathematical Foundations of Computer Science (MFCS 93), Lecture Notes
in Computer Science, pages 119. Springer-Verlag, Berlin, 1993. Invited Lecture.
[AGP96] K.R. Apt, M. Gabbrielli, and D. Pedreschi. A closer look at declarative interpretations. Journal of Logic Programming, 28(2): 147180, 1996.
References
281
[AL93]
[AL95]
[AM94]
[AP94]
Chapter 11
So far we have dealt only with a small fragment of Prolog which is hardly adequate
for programming. In this chapter we discuss various other features of Prolog and
explain their meaning and use.
Some of these features have also been studied from the program correctness point
of view. This study has always taken place by considering appropriate extensions
of logic programming. The exposition of these developments and of the application of the resulting theories to verification of Prolog programs would require
another hundred or so pages and thus would make this book inappropriate for a
one semester course. Moreover, the correctness aspects of these Prolog programs
have not been studied in a systematic fashion. Therefore we limit ourselves to
providing necessary pointers to the literature.
We begin our presentation by discussing in the next section the cut operator. In
Section 11.2 we explain its use by presenting programs that deal with sets. Next,
in Section 11.3 we discuss Prolog facilities that allow us to collect all solutions to
a query. In Section 11.4, we introduce an interesting feature of Prolog metavariables and explain their use. We also explain there how these two features
cut and meta-variables allow us to define some other control facilities in Prolog.
In Section 11.5 we introduce negation in Prolog and illustrate its use by means
of simple programs. Negation is an important facility because it allows us to
write simpler and shorter programs and because it provides a readily available
computational interpretation of non-monotonic reasoning, an important branch
of common-sense reasoning, an area of artificial intelligence. We illustrate these
uses of negation in Section 11.6, where we present a number of programs that
deal with directed graphs and in Section 11.7, where we explain the modelling of
non-monotonic reasoning.
In the subsequent two sections we study various meta-level built-ins. These are
built-ins that allow us to access, compare or modify the syntactic entities out of
which the programs are built. In particular, in Section 11.8 we deal with the builtins that allow us to inspect, compare and decompose terms and in Section 11.9 we
282
Cut
283
consider the built-ins that allow us to inspect and modify the programs.
Then, in Section 11.10, we discuss Prologs input/output facilities. We conclude
the chapter by discussing in Section 11.11 the issue of program verification in the
presence of the features here discussed and the literature on related extensions of
the theory of logic programming.
11.1
Cut
One of the sources of inefficiency in Prolog is the generation of a too large search
space. This problem has been addressed in the language by providing a built-in
nullary relation, denoted as ! and called cut, which allows us to prune the
search space during the program execution. In this section, we define the meaning
of cut.
Informally, cut is defined as follows. Consider the following definition of a relation p:
p(s1 ) A1 .
...
p(si ) B,!,C.
...
p(sk ) Ak .
Here, the i-th clause contains a cut atom (there could be others, either in the same
clause or in other clauses). Now, suppose that during the execution of a query,
some atom p(t) is resolved using the i-th clause and that later on, the cut atom
thus introduced becomes the leftmost atom. Then the indicated occurrence of !
succeeds immediately, but additionally
1. all other ways of resolving B are discarded and
2. all derivations of p(t) using the i + 1-th to k-th clause for p are discarded.
In other words, let Q be a node in T with the cut atom as the selected atom
and let Q0 be the node that introduced this cut atom. Then, the execution of this
cut atom succeeds immediately and additionally results in pruning all the branches
that originate in Q0 and are to the right of Q. This effect of pruning is illustrated
in Figure 11.1.
Note that this operational definition of the behaviour of the cut operator depends
on the leftmost selection rule and on viewing the program as a sequence of clauses,
instead of a set of clauses.
The following example illustrates Prolog computation in presence of cut.
Example 11.1 Consider the following Prolog program:
p r,!,t.
p.
Q0
...
...
...
Q =!, . . . . . .
...
fail
...
cut
...
...
Q0
...
...
Q =!, . . .
...
fail
...
fail
...
fail
r,!,t
1
s,!,t
!,t
!,t
t
fail
Figure 11.2 A computation tree for the query p
r s.
r.
s.
An LD-tree for the query p and the program augmented by the fact !. is shown
in Figure 11.2. This tree is augmented with a dashed arrow. In the node at the
source of this arrow the selected atom is the cut atom. This cut atom is introduced
by resolving the query p. We say that this query p is the origin of the considered
cut atom. We use here a dashed arrow to point from the selected cut atom to
its origin. Execution of this cut atom leads to pruning: the middle branch is
pruned according to rule 1 and the rightmost branch is pruned following rule 2.
Additionally, the only direct descendant, t, is generated.
In the figure, the pruned branches are marked using a cross. The label on the
cross refers to the rule that was responsible for the pruning of this branch. Here,
Cut
the computation ends in a failure.
285
2
p
exp.
exp.
exp.
s,r
s,r
q,!,t,r
s,r
r
q,!,t,r
!,t,r
p
cut
exp.
s,r
p
exp.
s,r
s,r
q,!,t,r
q,!,t,r
!,t,r
!,t,r
t,r
t,r
fail
q,!,t,r
!,t,r
Cut
287
Now the Expansion Corollary 11.6 justifies Definition 11.3. More formally, this
definition is justified by the fact that every countable partial ordering with the
least element (here the relation v, with the one node tree as the least element) can
be canonically extended to a cpo (see e.g. Gierz et al. [GHK+ 80]), so the limits of
growing countable chains always exist.
Member1
We have already observed that the definition of the meaning of the cut operator
depends on the leftmost selection rule and on the clause ordering. This excludes
any declarative interpretation of it and forces one to rely solely on the procedural
interpretation when arguing about the correctness of programs that use cut. This
is an obvious drawback and in fact the cut operator is the main source of errors in
Prolog programs. In general, it is preferable to use constructs that are defined by
means of cut internally, like the ones we shall discuss in Sections 11.4 and 11.5.
In the subsequent presentation of programs that use cut we shall indicate some
of the subtleties involved in its use. We begin with a simple example. Let us
modify the MEMBER program of Section 5.5 so that it generates only one solution:
% member1(Element, List) Element is an element of the list List.
member1(X, [X | ]) !.
member1(X, [ | Xs]) member1(X, Xs).
Program: MEMBER1
Here the cut takes place as soon as an element is found and its execution prevents
backtracking. The behaviour of the query below should now be compared with that
of member(X, [tom, dick, harry]) on page 126:
| ?- member1(X,
X = tom ;
no
Note also that we still have
| ?- member1(harry, [tom, dick, harry]).
yes
To illustrate further uses of cut, we now retake the thread of Chapters 5 and 9
and discuss programs computing over a specific domain.
11.2
Sets
Finite sets form a fundamental data structure. Prolog programs that implement
operations on sets typically rely on the use of cut. Sets are not supported in Prolog
by any built-in facilities though, as we shall see in the next section, Prolog provides
some features that allow us to construct the set of all solutions to a query. In what
follows we represent finite sets as ground lists without repetition, so the set {1, 3, 7}
is simply represented by any of the following lists: [1,3,7], [1,7,3], [3,1,7], [3,7,1],
[7,1,3], [7,3,1]. In the sequel by a set we mean a list without a repetition. To avoid
various complications we consider here only sets of ground terms. We now present
a couple of programs dealing with sets.
Set
First, let us transform a ground list into a set by removing the duplicate elements.
The following program performs this task:
% set(Xs, Ys) the ground list Ys is the result of removing duplicates
from the ground list Xs.
set([], []).
set([X | Xs], Ys) member(X, Xs), !, set(Xs, Ys).
set([X | Xs], [X | Ys]) set(Xs, Ys).
augmented by the MEMBER program.
Program: SET
Here, the purpose of the cut is to ensure that the third clause is used only when
the test member(X, Xs) of the second clause fails. We now have
| ?- set([1,2,1,2,3], Ys).
Ys = [1,2,3] ;
no
| ?- set([1,2,1,2,3], [1,2,3]).
yes
Sets
289
but also
yes
| ?-
set([1,2,1,2,3], [1,3,2]).
no
Exercise 156 Modify the above program so that it can also be used to test whether
the second argument is a set representation of the first one. In particular the last query
should then succeed.
2
member(X, Xs), !.
would restrict the use of the program to the queries of the form add(s,t, Z),
where s,t are sets. Indeed, we would then have
| ?- add(a, [a], [a,a]).
yes
In general, the atoms appearing before the cut, should be viewed as the only
tests to be performed before the execution of the atoms appearing after the cut. If
these tests are not satisfied, the next clause is attempted. Now, the above clause
introduces an additional, implicit test that the second and third arguments of add
are the same. This test is absent in the original formulation of the program ADD
for which we obtain, as desired,
| ?- add(a, [a], [a,a])
no
Intersection
Finally, we deal with the intersection.
% inter(Xs, Ys, Zs) the set Zs is the intersection of the sets Xs and Ys.
inter([], , []).
inter([X | Xs], Ys, Zs)
member(X,Ys), !,
Zs = [X | Z1s], inter(Xs,Ys,Z1s).
inter([X | Xs], Ys, Zs) inter(Xs, Ys, Zs).
augmented by the MEMBER program.
Program: INTERSECTION
Exercise 158 Investigate the behaviour of the above program with the second clause
replaced by
inter([X | Xs], Ys, [X | Zs]) member(X,Ys), !, inter(Xs,Ys,Zs).
for the query inter([a], [a], [a]).
Exercise 159
(i) Write a program that computes the set difference.
(ii) The symmetric difference of two sets A and B is defined as (A B) (B A),
where A B denotes the set difference. Write a program that computes the symmetric
difference of two finite sets.
2
291
Other natural relations on sets include the set equality, membership and the
subset relation. To define them, respectively, the programs PERMUTATION, MEMBER
and SUBSET can be used.
Exercise 160 Write a program that defines a binary relation subset1 such that for a
set ys the query subset1(Xs, ys) generates all subsets of ys.
Exercise 161 Write a program that computes the number of different elements in a
list without the use of the program SET.
2
Exercise 162 Modify the programs QUICKSORT, QUICKSORT ACC and MERGESORT using
cut in such a way that the size of the Prolog trees is reduced for the usual queries.
Provide examples that illustrate the achieved reduction in size.
2
11.3
When discussing in Section 5.1 the interaction with a Prolog system we explained
that all solutions to a query can be obtained by the repeated typing of ;. This
can be both time consuming and unsatisfactory for certain purposes. In some
applications it is natural to collect all solutions to a query in a list or a set. Prolog
offers some built-ins that make such constructions possible. These built-ins heavily
rely on the fact that Prolog assumes ambivalent syntax, so that a query can be
used as a term. In particular, SICStus Prolog provides the following built-ins that
allow us to collect all solutions to a query.
11.3.1
findall/3
Consider a call findall(term, query, bag). Then query has to be a nonvariable term. It is treated as a query and the call findall(term, query, bag)
finds values of term as instantiated by all the c.a.s. of query, collects them in a
list in the order of generated solutions and unifies this list with bag.
The call findall(term, query, bag) does not instantiate any variables that
appear in query (assuming that bag and query do not share a variable).
In a typical use of findall both term and bag are different variables. Then the
call of findall(term, query, bag) instantiates bag to the list of all instances of
term which are found in all successive successful Prolog derivations of the query
query.
For example, assuming that the MEMBER program is part of the considered program, we have
| ?- findall(X, member(X, [tom, dick, tom, harry]), Ls).
Ls = [tom,dick,tom,harry] ;
no
and, assuming that the program used on page 3 is part of the considered program
we have
| ?- findall(X, direct(X,Y), Ls).
Ls = [amsterdam,amsterdam,seattle,anchorage] ;
no
11.3.2
bagof/3
Meta-variables
11.3.3
293
setof/3
The only difference between this built-in and bagof/3 is that the last argument now
becomes a set and not a list of all solutions, so duplicates are removed; additionally
the elements are sorted according to some standard ordering. In particular, we now
have
| ?- setof(X, member(X, [tom, dick, tom, harry]), Ls).
Ls = [dick,harry,tom] ;
no
To prevent the binding of local variables and the resulting case distinction in
the successive calls of the setof/3, SICStus Prolog provides a syntactic means to
bind the (local) variables of the query by means of an existential quantifier. For
a query Q with a variable X, the query X^Q denotes Q preceded by an existential
quantification over X. In the call setof(term, X^query, set) the variable X does
not get bound and no backtracking takes place in case different values for X exist
in successful computations of the query query.
In such a way a call of setof/3 can be used to generate a single set of all
solutions to a query. For example, we now have
| ?- setof(X, Y^direct(X,Y), Ls).
Ls = [amsterdam,anchorage,seattle] ;
no
Formally, ^ is an infix operator declared in SICStus Prolog by
:- op(200, xfy, ^).
The construct X^Q on its own is a legal query; during its execution the quantification X^ is ignored.
Exercise 163 The findall/3 built-in is similar to bagof/3 in that the computed
output list does not have to be a set. Suggest an implementation of a variant of the
findall/3 built-in that makes it similar to the setof/3 built-in.
2
11.4
Meta-variables
One of the unusual features of Prolog is that it permits the use of variables in
the positions of atoms, both in the queries and in the clause bodies. Such a use
of a variable is called a meta-variable. Computation in the presence of the metavariables is defined as for pure Prolog programs with the exception that the mgus
employed can now also bind the meta-variables. So, for example, for the program
Negation
295
As the precedence of , is lower than that of ;, the atom of the form A,B ;
C,D stands for ;((A,B), (C,D)).
Exercise 164 Rewrite the program FRONTIER using disjunction, without a reference
to the relation nel tree.
11.4.1
Control Facilities
11.5
Negation
The if then else construct can in turn be used to define negation by the single
clause
(X) (X fail;true).
Recall from Section 5.2 that the query fail always fails and the query true always
succeeds. So, procedurally, (Q) is executed as follows. If Q succeeds, then (Q)
fails and if Q fails, then (Q) succeeds.
By unfolding the if then else relation we obtain the alternative, equivalent,
but less intuitive definition
Negation
297
Remove
As an example of the use of 6= consider now the problem of deleting from a list
all occurrences of a given element. The following program does the job.
% remove(Xs, X, Ys) Ys is the result of removing all occurrences of the
element X from the ground list Xs.
remove([ ], , [ ]).
remove([X | Xs], X, Ys) remove(Xs, X, Ys).
remove([Y | Xs], X, [Y | Ys]) X 6= Y, remove(Xs, X, Ys).
augmented by the NOT EQUAL program.
Program: REMOVE
This program is meant to be used for the queries of the form remove(xs, x,
s), where xs and x are ground. For example, we have
| ?- remove([1,3,3,5], 3, Ys).
Ys = [1,5]
Exercise 165 Write a program that computes the result of removing the first occurrence of an element from the list.
Set/1
The following program tests whether a ground list represents a set. This is tantamount to checking that the list has no duplicates.
% set(Xs) Xs is a ground list without duplicates.
set([]).
set([X | Xs]) member(X, Xs), set(Xs).
augmented by the MEMBER program.
Program: SET/1
In the already presented programs that dealt with sets we could enforce checking that the arguments are indeed sets by using the above relation set/1.
Disjoint
We call two ground lists disjoint if no element is a member of both of them. This
statement, when translated into first-order logic, yields
disjoint(Xs, Ys) Z(member(Z, Xs), member(Z, Ys)).
Unfortunately, due to the presence of the existential quantifier this is not a legal
clause. Defining an auxiliary relation overlap solves the problem and yields the
following program which allows us to test whether two ground lists are disjoint:
11.6
Directed Graphs
As further illustration of the use of negation we now consider pure Prolog programs
with negation which deal with finite directed graphs. Formally, a directed graph G
is a pair (N , E), where N is a set and E is a relation on N , that is E N N .
The elements of N are called the nodes of G and the pairs which belong to E are
called the edges of G. If the set N is finite, the directed graph is called finite.
Prolog does not have any built-in facilities that deal with graphs. We represent
here a finite directed graph (in short: a graph) by a (ground) list of its edges.
In turn, we represent an edge from node a to node b by the term e(a, b). In
this representation the isolated nodes of the graph are omitted. However, we
consider here only algorithms dealing with paths in graphs and, consequently, such
a (mis)representation is adequate for our purposes.
Formally, given a graph g, by a path in g from a to b we mean a sequence
a1 , . . ., an (n > 1) such that
e(ai , ai+1 ) g for i [1, n 1],
a1 = a,
an = b.
A path is called acyclic if its elements are distinct and is called cyclic (or a cycle)
otherwise. A path a1 , . . ., an is called a simple cycle if a1 = an and a1 , . . ., an1 are
distinct. In what follows we present paths as lists.
Finally, recall that a graph is called acyclic if no cyclic path exists in it. We use
the customary abbreviation dag for directed acyclic graph.
Exercise 167 Write a program which tests whether a path is a simple cycle.
Directed Graphs
299
Directed Graphs
301
| ?- path(c,b,[e(a,b),e(b,c),e(c,a),e(c,d),e(d,a),e(d,b)], Path),
set(Path).
Path = [c,a,b]
To generate all acyclic paths in g which start in a given node, say b we pose the
query
| ?- path(b,_,[e(a,b),e(b,c),e(c,a),e(c,d),e(d,a),e(d,b)], Path),
set(Path).
Path = [b,c] ;
Path = [b,c,a] ;
Path = [b,c,d] ;
Path = [b,c,d,a] ;
no
And to generate all simple cycles in g of length 5 we use the query
| ?- path(_, _,[e(a,b),e(b,c),e(c,a),e(c,d),e(d,a),e(d,b)], Path),
simple_cycle(Path), length(Path,5).
Path = [a,b,c,d,a] ;
Path = [b,c,d,a,b] ;
Path = [c,d,a,b,c] ;
Path = [d,a,b,c,d] ;
no
Exercise 168 Write a program which tests whether a graph is a dag.
Win
Finally, consider the problem of determining a winner in a two-person finite game
in which winning and losing are the only possible outcomes. We represent the
game by a graph the nodes of which are positions in the game and the edges of
which are the moves in the game. This graph is acyclic, since the game is assumed
to be finite. The program to solve the above problem is remarkably concise. Its
only clause just defines when a position is a winning one, namely when a move
exists which leads to a losing, that is non-winning, position:
% win(X, Graph) X is a winning position in the game
represented by the graph Graph.
win(X, Graph) member(e(X,Y), Graph), win(Y, Graph).
augmented by the MEMBER program.
Program: WIN
This program assumes that the graph is acyclic, so for a graph g and a node a
representing the beginning position in the game, the query acyclic(g), win(a,
g), where acyclic is the relation defined in Exercise 168 ensures its proper use.
Exercise 169 Use this program to write a program for playing a simple game, like tic
tac toe.
11.7
Non-monotonic Reasoning
Non-monotonic Reasoning
303
One of the striking features of Prolog is that it can naturally support nonmonotonic reasoning by means of negation. In this section we show solutions to
two well-known problems in the non-monotonic reasoning by means of pure Prolog
programs with negation.
The Birds Program
As the first example consider the proverbial problem concerning the birds. The
problem is to reason in the presence of default assumptions. In the natural language
they are often expressed by means of the qualification usually. In what follows
the usual situations are identified with those which are not abnormal.
We stipulate the following assumptions.
The birds which are not abnormal fly (i.e., birds usually fly).
The penguins are abnormal.
Penguins and eagles are birds.
Tweety is a penguin and Toto is an eagle.
The problem is to deduce which of these two birds flies. The solution in Prolog
is immediate. We simply translate the above statements into the following rules.
fly(X) ab(X), bird(X).
ab(X) penguin(X).
bird(X) penguin(X).
bird(X) eagle(X).
penguin(tweety).
eagle(toto).
Program: BIRDS
We now obtain the desired conclusions:
| ?- fly(toto).
yes
| ?- fly(tweety).
no
The Yale Shooting Problem
In this example we assume from the reader an elementary knowledge of first-order
logic. In Hanks and McDermott [HM87] a simple problem in temporal reasoning,
a branch of non-monotonic reasoning, was discussed. It became known in the literature as the Yale Shooting Problem. Hanks and McDermotts interest in this
problem arose from the fact that apparently all known theories of non-monotonic
reasoning, when used to formalize this problem, led to too weak conclusions. The
load
alive
wait
shoot
dead?
305
holds(alive, []).
holds(loaded, [load | Xs]).
holds(dead, [shoot | Xs]) holds(loaded, Xs).
ab(alive, shoot, Xs) holds(loaded, Xs).
holds(Xf, [Xe | Xs]) ab(Xf, Xe, Xs), holds(Xf, Xs).
Program: YSP
Note that this program is an almost literal translation of the above formulas to
Prolog syntax. To enhance readability we used here the list notation [e | s]
instead of result(e, s) and denoted the initial situation by the empty list [ ].
In contrast to the solutions in other formalisms, Prolog solution can be used not
only to model the problem but also to compute answers to the relevant queries.
For example, we have
| ?- holds(dead, [shoot, wait, load]).
yes
| ?- holds(dead, [wait, load]).
no
11.8
Prolog offers a number of built-in relations that allow us to inspect, compare and
decompose terms. In particular, the following built-ins belong to the first category:
As a simple example of the use of the nonvar/1 built-in consider the following
modification of the LIST program:
11.8.1
functor/3
functor/3 either extracts from a term the leading symbol and its arity or constructs a most general term of the given arity with the given function symbol as
the leading symbol.
More precisely, consider a call functor(t, f, n). There are two cases.
t is a non-variable term.
Let f be the leading symbol of t and n the arity of t. Then the call of
functor(t, f, n) unifies the pair (f,n) with (f,n). For example,
307
| ?- functor(f(a,b), F, N).
F = f,
N = 2
In the presence of arithmetic operators functor/3 deals correctly with the
infix notation:
| ?- functor(3*4+5, F, N).
F = +,
N = 2
t is a variable.
Then f has to be a non-numeric constant (atom in Prologs terminology)
and n a natural number. Then the call of functor(t, f, n) instantiates t
to a pure variable term of arity n the leading symbol of which is f.
For example
| ?- functor(T, f, 3).
T = f(_C,_B,_A)
Any other uses of functor/3 lead to a run-time error.
11.8.2
arg/3
11.8.3
../2
=.. (pronounced univ) either creates a list which consists of the leading symbol
of the term followed by its arguments or constructs a term from a list that starts
with a function symbol and the tail of which is a list of term arguments.
It is internally defined as an infix operator with the following declaration in the
case of SICStus Prolog:
309
11.9
Another unusual feature of Prolog is that it allows us to access and modify the
program during its execution. In this section we consider Prolog built-ins that
support these operations.
11.9.1
clause/2
To access the definitions of the relations present in the considered program, Prolog
provides the clause/2 built-in. This built-in assumes that true is the body of a
unit clause. In its call the first argument has to be a non-variable. This determines the relation to which the call refers to. Given a call clause(head, body),
first the term head is unified with a head of a clause present in the considered
program. If no such clause exists, the call of clause(head, body) fails. Otherwise, the first such clause is picked and the term head body is unified with
this clause. Upon backtracking successive choices for head are considered and the
corresponding alternative solutions are generated.
If at the moment of the call the first argument is a variable, a run-time error
arises. So, assuming that MEMBER is part of the considered program we have
| ?- clause(member(X,Y), Z).
Y = [X|_A],
Z = true ;
Y = [_A|_B],
Z = member(X,_B) ;
no
Once the program clauses can be accessed, by means of the clause/2 built-in,
we can construct programs that take other programs as data. Such programs are
usually called meta-programs. As a typical example consider the problem of writing in Prolog an interpreter for pure Prolog. The required program is remarkably
concise and intuitive.
% solve(X) the query X succeeds for the
program accessible by clause/2.
solve(true) !.
solve((A,B)) !, solve(A), solve(B).
solve(A) clause(A, B), solve(B).
Program: META INTERPRETER
311
The first clause states that the built-in true succeeds immediately. The second
clause states that a query of the form A, B is provable if A is provable and B is
provable. Finally, the last clause states that an atomic query A is provable if there
exists a clause of the form A B such that the query B is provable.
The cuts are used here to enforce the distinction by cases: either the argument
of solve is true or a non-atomic query or else an atomic one. The cuts also prevent
that upon backtracking queries of the form clause(true, B) are considered.
To illustrate the behaviour of the program META INTERPRETER assume again that
MEMBER is a part of the considered program. We then have
| ?- solve(member(X, [tom,dick,harry])).
X = tom ;
X = dick ;
X = harry ;
no
This program forms a basis for building various types of interpreters for larger
fragments of Prolog or for its extensions. For example, using meta-variables the
program META INTERPRETER can be easily extended to the case of pure Prolog with
arithmetic, by adding to it the following clauses:
arithmetic(
arithmetic(
arithmetic(
arithmetic(
arithmetic(
arithmetic(
arithmetic(
solve(A) :-
< ).
=< ).
=:= ).
=\= ).
>= ).
> ).
is ).
arithmetic(A), !, A.
The last clause shifts the calls of arithmetic atoms to the system level. In
other words, these calls are executed directly by the underlying Prolog system.
Exercise 171 The last clause has to be inserted at the right place in the program
META INTERPRETER in order to prevent that upon backtracking queries clause(A, B),
where A is an arithmetic atom, are considered. Determine this place.
2
For example, assuming now that the program QUICKSORT is a part of the program
considered we now have
| ?- solve(qs([7,9,8,1,5], Ys)).
Ys = [1,5,7,8,9]
assert/1
Additionally, Prolog provides facilities for adding and removing clauses from the
underlying program.
The assert/1 built-in adds its argument as the clause to the program at some
place in the program. Its argument has to be a syntactically correct clause. The
actions of adding the clauses to the program through the calls of assert/1 are not
undone during backtracking.
There are two more specific versions of assert/1; asserta/1 adds its argument
at the beginning of the program and assertz/1 adds its argument at the end of
the program.
11.9.3
retract/1
11.10
Input/Output Facilities
Finally, we discuss the input/output facilities of Prolog. We only mention here the
most important built-ins provided by the Prolog systems. They can be divided into
two categories those concerned with reading and writing and those concerned
with file manipulation.
Input/Output Facilities
313
Below we refer to an input stream and output stream. These are two files,
which by default are the terminal screen. Both input stream and output stream
can be temporarily changed by commands which we shall explain shortly.
In particular, the following built-ins belong to the first category:
get0/1, which unifies its argument with the ASCII code of the next character
of the output stream,
get/1, which unifies its argument with the ASCII code of the next printable
character of the output stream,
put/1, which prints the character with the ASCII code equal to the value of
its argument,
read/1, which unifies its argument with the next term of the current input
stream. The period . marks the end of this term. If the sequence of
characters until the period is not a term, a run-time error arises,
write/1, which writes its argument on the current output stream,
the already mentioned in Section 5.2 nl/0, which produces a new line.
For example, we have
| ?- get(X), put(X), nl, put(X+1).
|: a
a
b
X = 97
Here |: is the SICStus Prolog prompt for the input. Recall that 97 is the ASCII
code of the character a.
Strings are written in Prolog by surrounding them with double quotes. A string
is internally identified with the list of the ASCII codes of its characters. name/2 is
a built-in that provides conversion between these two forms of representing strings,
as the following two representative examples show:
| ?- name(X, [97, 98]).
X = ab
| ?- name(ab, Y).
Y = [97,98]
Using name/2 and the built-in list facilities we can easily manipulate strings. For
example, we have
| ?- "Alma" = [X, Y | Z], name(New, [Y, X | Z]).
New = lAma,
11.11
Concluding Remarks
Concluding Remarks
315
As another example of the difficulties in the presence of cut note that by the
Unfolding 1 Theorem 7.28 and the Independence Theorem 3.33, a pure Prolog
non-recursive program and its unfolding yield for every query the same computed
answer substitutions. This property, however, does not hold any more in the
presence of cut. Indeed, take the program
p !, q.
p.
and the query p. Then the Prolog computation ends in a failure. However, by
unfolding q the first clause gets deleted, so the computation for the query p and
the resulting program ends in a success.
It is interesting to note that the meta-variables do admit a declarative interpretation. In fact, Apt and Ben-Eliyahu [ABE96] showed that there exists a
well-defined declarative interpretation of logic programs with meta-variables and
that the soundness and completeness of SLD-resolution can be extended to logic
programs with meta-variables. The declarative interpretation can be used to reason
about the correctness of pure Prolog programs with meta-variables.
We have already noted in Section 11.5 that in general negation in Prolog does
not admit a declarative interpretation and mentioned that when it is applied to
ground queries a declarative interpretation of it is possible. In fact, there is a whole
area of the theory of logic programming that is concerned with the procedural and
declarative interpretation of logic programs augmented with negation. For further
discussion we introduce the following terminology. By a literal we mean an atom
or a negation of an atom. By a general query we mean a finite sequence of literals
and by a general clause a construct of the form H L, where H is an atom and
L a general query. Finally, a general program is a finite set of general queries.
The procedural interpretation of general programs is an extension of the SLDresolution that allows us to deal with negative literals. It is called SLDNFresolution and was proposed by Clark [Cla78]. Negation is interpreted in it using
the negation as finite failure rule. Intuitively, this rule works as follows: for a
ground atom A,
A succeeds iff A finitely fails,
A finitely fails iff A succeeds,
where finitely fails means that the corresponding evaluation tree is finite and all
its leaves are marked with fail.
The correct formal definition is more subtle than it seems. In particular, the
original definition does not properly capture the computational process involved
and is not adequate for reasoning about termination. The reader is referred to
Martelli and Tricomi [MT92] and Apt and Doets [AD94] for a precise definition
of the SLDNF-resolution.
There are several competing declarative interpretations of general programs.
They can be divided into two categories. The first one involves restriction to all
11.12
Bibliographic Remarks
The problem of formalizing the meaning of cut has been studied in a number of
publications starting from Jones and Mycroft [JM84], where various semantics for
Prolog with cut were defined. This work was pursued by Arbab and Berry [AB87],
Debray and Mishra [DM88] and, more recently, by Lilly and Bryant [LB92]. The
approach presented here is due to Apt and Teusink [AT95].
The WIN program appeared first in Gelfond and Lifschitz [GL88]. Termination
of the programs TRANS and WIN is considered in Apt and Pedreschi [AP93] and
various other aspects of their correctness are dealt with in Apt [Apt95].
The Yale Shooting Problem was extensively discussed in the literature and its
formalizations in various formalisms for non-monotonic reasoning were given. The
solution in Prolog presented here was found independently by Apt and Bezem
[AB91], Elkan [Elk89] and Evans [Eva89] where its declarative and procedural
interpretation were also studied.
Summary
317
The formal aspects of the term inspection facilities have been considered in
Apt et al. [AMP94], where procedural and declarative interpretation of logic programs with arithmetic and the term inspection facilities has been proposed. This
declarative interpretation has been used there to prove universal termination of
the programs LIST1 and UNIFICATION for all queries.
Finally, the program (scheme) META INTERPRETER appeared first in Pereira et
al. [PPW78]. Sterling and Shapiro [SS86] discussed in detail various extensions
of it. There is by now an extensive body of work concerning the procedural and
declarative interpretation of the logic programming counterpart of this program,
that is the program without cuts. The early references include Kowalski [Kow79],
Bowen and Kowalski [BK82] and Hill and Lloyd [HL88]. The more recent references are Levi and Ramundo [LR93], Martens and de Schreye [MS95b, MS95a]
and Kalsbeek [Kal95]. Pedreschi and Ruggieri [PR96] studied various correctness
aspects of the META INTERPRETER program and of its extensions.
Most of the other programs presented here are taken from the books of Bratko
[Bra86], Clocksin and Mellish [CM84] and Sterling and Shapiro [SS86].
11.13
Summary
In this chapter we discussed various features of Prolog so far left out of consideration. These included
We illustrated the use of these features by presenting a number of Prolog programs which dealt with
sets,
directed graphs,
game trees
and showed how these features can be used to implement
non-monotonic reasoning,
logical operations, like unification,
meta-interpreters, that is interpreters of (fragments of) Prolog written in
Prolog.
11.14
References
[AB87]
[AB91]
[AB94]
[ABE96]
[ABW88]
[AD94]
[AMP94]
K. R. Apt, E. Marchiori, and C. Palamidessi. A declarative approach for firstorder built-ins of Prolog. Applicable Algebra in Engineering, Communication
and Computation, 5(3/4):159191, 1994.
[AP93]
[Apt95]
References
319
[AT95]
[Bau88]
M. Baudinet. Proving termination properties of Prolog programs. In Proceedings of the 3rd Annual Symposium on Logic in Computer Science (LICS),
pages 336347. IEEE Computer Society, Edinburgh, 1988.
[BK82]
[BR94]
E. B
orger and D. Rosenzweig. A mathematical definition of full Prolog.
Science of Computer Programming, 24:249286, 1994.
[Bra86]
[BS94]
[Cla78]
[CM84]
[DEC96]
P. Deransart, A. Ed-Dbali, and L. Cervoni. Prolog: The Standard. SpringerVerlag, Berlin, 1996.
[DF87]
[DM88]
[Elk89]
[Eva89]
[Fit85]
[GHK+ 80] G. Gierz, K.H. Hofmann, K. Keimel, J.D. Lawson, M.W. Mislove, and D.S.
Scott. A Compendium of Continuous Lattices. Springer-Verlag, Berlin, 1980.
M. Gelfond and V. Lifschitz. The stable model semantics for logic programming. In R.A. Kowalski and K.A. Bowen, editors, Proceedings of the Fifth
International Conference on Logic Programming, pages 10701080. The MIT
Press, Cambridge, MA, 1988.
[HL88]
[HM87]
[ISO95]
[JM84]
N. D. Jones and A. Mycroft. Stepwise development of operational and denotational semantics for Prolog. In Sten-
Ake T
arnlund, editor, Proceedings of
the Second International Conference on Logic Programming, pages 281288,
Uppsala, 1984.
[Kal95]
M. Kalsbeek. Correctness of the vanilla meta-interpreter and ambivalent syntax. In K.R. Apt and F. Turini, editors, Meta-logics and Logic Programming,
pages 326. The MIT Press, Cambridge, MA, 1995.
[Kow79]
R.A. Kowalski. Logic for Problem Solving. North-Holland, New York, 1979.
[Kun89]
[LB92]
A. Lilly and B.R. Bryant. A prescribed cut for Prolog that ensures soundness.
Journal of Logic Programming, 14(4):287339, 1992.
[LR93]
[MH69]
J. McCarthy and P.J. Hayes. Some philosophical problems from the standpoint of artificial intelligence. In B. Meltzer and D. Mitchie, editors, Machine
Intelligence 4, pages 463502. Edinburgh University Press, Edinburgh, 1969.
[MS95a]
[MS95b]
[MT92]
References
321
[PPW78]
[PR96]
[SS86]
L. Sterling and E. Shapiro. The Art of Prolog. MIT Press, Cambridge, MA,
1986.
[vG88]
A. van Gelder. Negation as failure using tight derivations for general logic
programs. In J. Minker, editor, Foundations of Deductive Databases and Logic
Programming, pages 149176. Morgan Kaufmann, Los Altos, CA, 1988.
[vGRS88] A. van Gelder, K. Ross, and J.S. Schlipf. Unfounded sets and well-founded
semantics for general logic programs. In Proceedings of the Seventh Symposium on Principles of Database Systems, ACM-SIGACT-SIGCOM, pages
221230. ACM Press, New York, NY, 1988.
Index
atomic/1, 305
bagof/3, 292
call/1, 295
clause/2, 310
compound/1, 305
fail/0, 114
findall/3, 291
functor/3, 306
get/1, 313
get0/1, 313
ground/1, 305
integer, 240
is/2, 251
name/2, 313
nl/0, 114, 313
nonvar/1, 305
put/1, 313
read/1, 313
repeat/0, 114
retract/1, 312
see/1, 314
seeing/1, 314
seen/0, 314
setof/3, 293
tell/1, 314
telling/1, 314
told/0, 314
true/0, 114
var/1, 305
write/1, 313
322
Index
absence
of failures, 226
of run-time errors, 270
access path, 27
accumulator, 134, 242
Aiken, A., 279
At-Kaci, H., 119, 254
Albert, L., 41
algebra, 76
domain of, 76
alphabet, 19
ambivalent syntax, 105
Apt, K.R., 12, 63, 72, 101, 102,
159, 174, 175, 194, 200,
228, 229, 254, 264, 279,
315317
Arbab, B., 316
arithmetic
comparison relation, 236
evaluator, 251
arity, 19
assertion, 209
for a relation, 209
holds for an atom, 209
atom, 45
arithmetic, 258
bounded, 150
correctly instantiated, 258
correctly typed in position i,
has an implication tree, 83
incorrectly instantiated, 258
input linear, 190
inputoutput disjoint, 190
level of, 150
output linear, 190
rigid, 152
Baader, F., 41
backtracking, 107
bag, see multiset
Bal Wang, 174, 316
Baudinet, M., 174, 316
Ben-Eliyahu, R., 315
Berry, D.M., 316
323
265
324 Index
correct answer substitution, 80
correct instance, 80
Cotta, J.C., 135, 144
cpo, see partial ordering
cut, 283
origin of, 284, 285
cycle, see acyclic path
simple, 298
dag (directed acyclic graph), 298
de Groot, D., 254
de Schreye, D., 317
Debray, S., 316
declaration of an operator, 234
declarative interpretation, 2, 75
declarative programming, 2
Decorte, S., 175
Dembi
nski, P., 190, 203
depth-first search, 107
Deransart, P., 16, 102, 181, 200, 203,
204, 229, 314
Dershowitz, N., 174
Deville, Y., 16, 204
difference list, 243
directed graph, 298
finite, 298
disagreement pair, 27
disjunction, 294
Doets, H.C., 16, 72, 102, 315
Drabent, W., 203, 229
Dumant, B., 204
Eder, E., 41
Elkan, C., 316
equivalent
sets of equations, 32
substitutions, 37
error
computation ends in, 237
run-time, 254, 294
Etalle, S., 279
Evans, C., 316
existential closure, 78
expression, 77
arithmetic, 235
true in an interpretation, 77
fact, 105
Falaschi, M., 88, 99, 102
Ferrand, G., 102, 314
Fitting, M., 26, 316
fixpoint, 90
Floyd, R., 12
Francez, N., 40
function symbol, 19
Gabbrielli, M., 72
gae
value of, 236
gae (ground arithmetic expression), 235
Gallier, J., 102
Gelfond, M., 316
Gierz, G., 287
graph, 298
acyclic, 298
transitive closure of, 298
Hanks, S., 304
Hanus, M., 255
Harriot, T., 255
Hayes, P.J., 303, 304
Herbrand
algebra, 93
base, 93
interpretation, 93
model, 93
universe, 93
Herbrand, J., 41
Hill, P.M., 254, 317
Hill, R., 102
Hoare, C.A.R., 12, 51, 240
Hogger, C.J., 229
Ifrah, G., 255
immediate consequence operator
TP , 96
UP , 88
implication tree, 83
ground, 83
Index
infix form, 233
input clause, see clause
instance, 21, 23
ground, 21
interpretation, 77
based on an algebra, 77
invariant, 29
Jaffar, J., 255
Jones, N.D., 316
Kalsbeek, M., 317
Kanamori, T., 199
Kawamura, T., 199
Ko, H.-P., 71, 101
Komara, J., 102
Komorowski, J., 198
Konig, D., 147
Kowalski, R.A., 18, 72, 96, 102, 317
Kuehner, D., 72
Kunen, K., 149, 279, 316
Lakshman, T.K., 279
language
determined by a program, 46
of programs, 45
of terms, 19
Lassez, J.-L., 41
LD-derivation, 106
data driven, 191
ends in an error, 258
output driven, 195
LD-resolution, 106
LD-resolvent, 106
LD-tree, 106
least Herbrand model, 93
least term model, 87
least upper bound, 89
leftmost selection rule, 64
level mapping, 150
Levi, G., 317
Lifschitz, V., 316
lift, 57
of an SLD-derivation step, 58
Lilly, A., 316
325
326 Index
OKeefe, R.A., 144
Olderog, E.-R., 12
operator, 233
expand , 108
arithmetic, 233
continuous, 90
finitary, 89
monotonic, 89
powers of, 90
ordering
lexicographic, 33
multiset, 147
Palamidessi, C., 41
parentheses, 19
partial ordering, 33
complete (cpo), 89
irreflexive, 33
well-founded, 33
Paterson, M.S., 41
path
acyclic, 298
in a graph, 298
semi-acyclic, 299
Pedreschi, D., 159, 174, 175, 229, 280,
316, 317
Pellegrini, A., 194, 200, 203, 264
Pereira, L.M., 317
Pettorossi, A., 204
Pieramico, C., 175
Plaisted, D.A., 197, 204
Pl
umer, L., 175
post-assertion, 209
Potter, J., 174
pre-assertion, 209
pre-fixpoint, 89
pre-interpretation, 76
predicate, 105
prefix form, 233
priority, 233
procedural interpretation, 2, 44
program, 45
acceptable, 159
arithmetic, 257
general, 315
left terminating, 158
nicely moded, 193
occur-check free, 180
recurrent, 150
recursive, 119
semi-acceptable, 171
semi-recurrent, 170
terminating, 149
unfolding of, 198
well-asserted, 211
well-moded, 184
well-typed, 266
Proietti, M., 204
proof outline, 164
propositional symbol, 45
query, 45
n-deep, 84
bounded, 150, 160
diverges, 111
fails, 112
general, 315
level of, 150
nicely moded, 193
potentially diverges, 111
produces infinitely many answers,
111
universally terminates, 111
well-asserted, 210
well-moded, 184
well-typed, 266
queue, 245
Rahn, J.H., 255
Ramundo, D., 317
Rao, K., 175, 203
reasoning method
monotonic, 302
non-monotonic, 302
Recorde, R., 255
Reddy, U., 203
relation, 32
antisymmetric, 33
Index
comparison, 236
irreflexive, 32
more general than, 21, 24
reflexive, 32
transitive, 33
transitive closure of, 33
transitive, reflexive closure of, 33
relation symbol, 45
definition of, 105
depends on, 170
refers to, 169
renaming, 20
resultant, 45
associated with an SLD-derivation
step, 52
of level i, 53
reversed implication, 45
Robinson, J.A., 18, 26, 41, 72
Rosenblueth, D.A., 203
Rosenzweig, D., 314
Ruggieri, S., 229, 317
rule, 3, 105
S-tree, 155
Sato, T., 198
selected atom, 48, 52
selection rule, 63
variant independent, 69
semantic consequence, 78
semantically equivalent, 78
semantically implies, 78
set (in Prolog), 288
set of equations
available, 180
left linear, 181
NSTO, 180
solved, 32
Shapiro, E., 16, 144, 317
Shepherdson, J., 101
Shepherdson, J.C., 72, 101
Shoenfield, J.R., 119, 121
Shostak, R.E., 175
Shyamasundar, R.K., 174, 316
SICStus Prolog, 106
327
Siekmann, J.H., 41
Sigal, R., 102
similar SLD-derivations, 58
simple pair of terms, 27
situation calculus, 304
SLD-derivation, 49
failed, 50
length of, 49
lift of, see lift
successful, 50
trivially successful, 92
SLD-derivation step, 48
SLD-resolvent, 48, 52
SLD-resultant step, 52
SLD-tree, 68
finitely failed, 68
successful, 68
Smullyan, R., 148
Snyder, W., 101
Sndergaard, H., 204
specification, 209
for a relation, 209
state, 76
Stark, R., 102
Steiner, J., 102
Sterling, L., 16, 144, 317
substitution, 20
composition of, 21
determined by a simple pair of terms,
27
empty, 20
ground, 20
idempotent, 37
pure variable, 20
subterm, 19
proper, 19
subtree
left, 138
right, 138
success set, 98
Tamaki, H., 198
Tarnlund, S-
A., 229
term, 19
328 Index
algebra, 81
base, 81
ground, 19
interpretation, 81
closed under substitution, 82
model, 81
typed, 265
universe, 81
termination
existential, 174
universal, see query universally terminates
Teusink, F., 316
transitive, see relation
tree, 138
augmented, 27
binary, 138
binary search, 246
empty, 138
ordered, 107
Prolog, 108
search, 246
Tricomi, C., 315
Turing, A.M., 12
type, 264
associated with position i, 265
for a relation symbol, 265
type judgement, 265
true, 265
Ullman, J.D., 174, 175
unfolding, see program
unifiable terms, 25
unification problem, 26
unifier, 25, 31
determined by a set of equations,
32
most general, see mgu
relevant, 38
unit clause, see clause
universal closure, 78
van Emden, M.H., 72, 73, 96, 102
van Gelder, A., 174, 175, 316
variable, 19
anonymous, 106
local, 117
solved, 34
unsolved, 34
variant, 21
Vasak, T., 174
Wegman, M.N., 41
well-founded ordering, see partial ordering
Widman, R., 255
Yale Shooting Problem, 303