100% found this document useful (1 vote)
189 views

The Art of Prolog

This document provides the table of contents for the book "The Art of Prolog: Advanced Programming Techniques" by Leon Sterling and Ehud Shapiro. The book covers advanced Prolog programming techniques across 20 chapters organized into three sections: Logic Programs, The Prolog Language, and Advanced Prolog Programming Techniques. The table of contents lists the chapter titles and section headings to provide an overview of the topics and organization of the book.

Uploaded by

paolo
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
100% found this document useful (1 vote)
189 views

The Art of Prolog

This document provides the table of contents for the book "The Art of Prolog: Advanced Programming Techniques" by Leon Sterling and Ehud Shapiro. The book covers advanced Prolog programming techniques across 20 chapters organized into three sections: Logic Programs, The Prolog Language, and Advanced Prolog Programming Techniques. The table of contents lists the chapter titles and section headings to provide an overview of the topics and organization of the book.

Uploaded by

paolo
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 277

- -

Leon Sterling
Ehud Shapiro
with a foreword by David H. D. Warren

The Art of Prolog


Advanced Programming Techniques
Second Edition

The MIT Press


Cambridge, Massachusetts
London, England
Third printing. 1999
O 1986, 1994 Massachusetts Institute of Technology To Ruth, Miriam, Michal, Danya, and Sara
All rights reserved. No part of this book may be reproduced in any form by
any electronic or mechanical means (including photocopying, recording, or
information storage and retrieval) without permission in writing from the
publisher.
Thls book was composed and typeset by Paul C. Anagnostopoulos and Joe
Snowden using ZZTEX.The typeface is Lucida Bright and Lucida New Math
created by Charles Bigelow and Kris Holmes specifically for scientific and
electronic publishing. The Lucida letterforms have the large x-heights and
open interiors that aid legibility in modern printing technology, but also echo
some of the rhythms and calligraphc details of lively Renaissance handwrit-
ing. Developed in the 1980s and 1990s, the extensive Lucida typeface family
includes a wide variety of mathematical and techmcal symbols designed to
harmonize with the text faces.
This book was printed and bound in the United States of America.
Library of Congress Cataloging-in-Publication Data
Sterling, Leon
The art of Prolog : advanced programming techniques / Leon
Sterling, Ehud Shapiro ; with a foreword by David H. D. Warren.
p. cm. - (MIT Press series in logic programming)
Includes bibliographical references and index.
ISBN 0-262-19338-8
1.Prolog (Computer program language) I. Shapiro, Ehud Y.
11. Title. 111. Series.
QA76.73.P76S74 1994
005.13'3-dc20 93-49494

CIP
Contents

Figures xiii
Programs xvii
Series Foreword xxv
Foreword xxvii
Preface xxxi
Preface to First Edition xxxv
-.

Introduction 1

I Logic Programs 9

1 Basic Constructs 11
1.1 Facts 11
1.2 Queries 1 2
1.3 The Logical Variable, Substitutions, and Instances 13
1.4 Existential Queries 14
1.5 Universal Facts 1 5
1.6 Conjunctive Queries and Shared Variables 16
1.7 Rules 18
Contents ix Contents

1.8 A Simple Abstract Interpreter 22


1.9 The Meaning of a Logic Program 25 II The Prolog Language 11 7
1.10 Summary 27
6 Pure Prolog 119
6.1 The Execution Model of Prolog 119
2 Database Programming 29
6.2 Comparison to Conventional Programming Languages 124
2.1 Simple Databases 29
6.3 Background 127
2.2 Structured Data and Data Abstraction 35
2.3 Recursive Rules 39 7 Programming in Pure Prolog 129
2.4 Logic Programs and the Relational Database Model 42 7.1 Rule Order 129
2.5 Background 44 7.2 Termination 131
7.3 Goal Order 133
3 Recursive Programming 45 7.4 Redundant Solutions 136
3.1 Arithmetic 45 7.5 Recursive Programming in Pure Prolog 139
3.2 Lists 56 7.6 Background 147
3.3 Composing Recursive Programs 65
3.4 Binary Trees 72 8 Arithmetic 149
3.5 Manipulating Symbolic Expressions 78 8.1 System Predicates for Arithmetic 149
3.6 Background 84 8.2 Arithmetic Logic Programs Revisited 152
8.3 Transforming Recursion into Iteration 154
4 The Computation Model of Logic Programs 87 8.4 Background 162
4.1 Unification 87
9 Structure Inspection 163
4.2 An Abstract Interpreter for Logic Programs 91
9.1 Type Predicates 163
4.3 Background 98
9.2 Accessing Compound Terms 167
9.3 Background 174
5 Theory of Logic Programs 101
5.1 Semantics 101 10 Meta-Logical Predicates 175
5.2 Program Correctness 105 10.1 Meta-Logical Type Predicates 176
5.3 Complexity 108 10.2 Comparing Nonground Terms 180
5.4 Search Trees 110 10.3 Variables as Objects 182
5.5 Negation in Logic Programming 113 10.4 The Meta-Variable Facility 185
5.6 Background 115 10.5 Background 186
Contents Contents

11 Cuts and Negation 189 15.2 Difference-Structures 291


11.1 Green Cuts: Expressing Determinism 189 1 5.3 Dictionaries 293
11.2 Tail Recursion Optimization 195 15.4 Queues 297
11.3 Negation 198 15.5 Background 300
11.4 Red Cuts: Omitting Explicit Conditions 202
16 Second-Order Programming 301
1 1.5 Default Rules 206
16.1 All-Solutions Predicates 301
11.6 Cuts for Efficiency 208
16.2 Applications of Set Predicates 305
11.7 Background 21 2
16.3 Other Second-Order Predicates 3 14
12 Extra-Logical Predicates 2 15 16.4 Background 317
12.1 Input/Output 2 15
12.2 Program Access and Manipulation 2 19 17 Interpreters 319
12.3 Memo-Functions 22 1 17.1 Interpreters for Finite State Machines 319
12.4 Interactive Programs 223 17.2 Meta-Interpreters 323
12.5 Failure-Dri\>enLoops 2 29 17.3 Enhanced Meta-Interpreters for Debugging 33 1
12.6 Background 231 17.4 An Explanation Shell for Rule-Based Systems 341
17.5 Background 354
13 Program Development 233
1 3.1 Programming Style and Layout 233 18 Program Transformation 3 57
13.2 Reflections on Program Development 23 5 18.1 Unfold/Fold Transformations 3 57
13.3 Systematizing Program Construction 238 18.2 Partial Reduction 360
13.3 Background 244 18.3 Code Wallung 366
18.4 Background 373

19 Logic Grammars 375


III Advanced Prolog Programming Techniques 24 7 19.1 Definite Clause Grammars 375
19.2 A Grammar Interpreter 380
14 Nondeterministic Programming 249
19.3 Application to Natural Language Understanding 382
14.1 Generate-and-Test 249
19.4 Background 388
13.2 Don't-Care and Don't-Know Nondeterminism 263
14.3 Artificial Intelligence Classics: ANALOGY, ELIZA, and 20 Search Techniques 389
McSAM 270 20.1 Searchng State-Space Graphs 389
14.4 Background 280 20.2 Searching Game Trees 401
15 Incomplete Data Structures 283 20.3 Background 407
15.1 Difference-Lists 283
xii Contents

ZV Applications 409 Figures


21 Game-Playing Programs 4 11
21.1 Mastermind 411
21.2 Nim 415
21.3 Kalah 420
21.4 Background 423
22 A Credit Evaluation Expert System 429
22.1 Developing the System 429
22.2 Background 438 An abstract interpreter to answer ground queries with respect
to logic programs 22
23 An Equation Solver 439
Tracing the interpreter 23
2 3.1 An Overview of Equation Solving 439
A simple proof tree 25
2 3.2 Factorization 448
Defining inequality 31
23.3 Isolation 449
A logical circuit 32
23.4 Polynomial 452
Still-life objects 34
23.5 Homogenization 454
A simple graph 41
23.6 Background 457 47
Proof trees establishing completeness of programs
24 A Compiler 459 Equivalent forms of lists 57
24.1 Overview of the Compiler 459 Proof tree verifying a list 58
24.2 The Parser 466 Proof tree for appending two lists 61
24.3 The Code Generator 470 Proof trees for reversing a list 63
24.4 The Assembler 475 Comparing trees for isomorphsm 74
24.5 Background 478 A binary tree and a heap that preserves the tree's shape 77
A unification algorithm 90
A Operators 479 An abstract interpreter for logic programs 93
References 483 Tracing the appending of two lists 94
Different traces of the same solution 95
Index 497
Solving the Towers of Hanoi 97
A nonterminating computation 97
xiv Figures Figures

A nonterrninating computation 107 Tracing the meta-interpreter 325


Two search trees 111 Fragment of a table of builtin predicates 327
Search tree with multiple success nodes 112 Explaining a computation 3 5 1
Search tree with an infinite branch 113 A context-free grammar for the language a*b*c* 371
Tracing a simple Prolog computation 121 The water jugs problem 393
Multiple solutions for splitting a list 122 A simple game tree 405
Tracing a quicksort computation 123 A starting position for Nim 41 5
A nonterminating computation 132 Computing nim-sums 419
Variant search trees 139 Board positions for Kalah 42 1
Tracing a reverse computation 146 Test equations 440
Computing factorials iteratively 155 Position of subterms in terms 449
Basic system type predicates 164 A PL program for computing factorials 460
Tracing the substitute predicate 171 Target language instructions 460
The effect of cut 191 Assembly code version of a factorial program 361
Template for a specification 243 The stages of compilation 461
A solution to the 4 queens problem 253 Output from parsing 470
A map requiring four colors 255 The generated code 475
Directed graphs 265 The compiled object code 477
Initial and final states of a blocks world problem 267
A geometric analogy problem 2 71
Sample conversation with ELIZA 273
A story filled in by McSAM 276
Three analogy problems 2 79
Concatenating difference-lists 285
Tracing a computation using difference-lists 287
Unnormalized and normalized sums 292
Power of Prolog for various searching tasks 307
The problem of Lee routing for VLSI circuits 308
Input and output for keyword in context (KWIC)problem 3 12
Second-order predicates 3 15
A simple automaton 321
Programs

A biblical family database 12


Biblical family relationshps 2 3
Defining family relationships 3 1
A circuit for a logical and-gate 33
The circuit database with names 36
Course rules 37
The a n c e s t o r relationship 39
A directed graph 41
The transitive closure of the edge relation 41
Defining the natural numbers 46
The less than or equal relation 48
Addition 49
Multiplication as repeated addition 51
Exponentiation as repeated multiplication 51
Computing factorials 52
The minimum of two numbers 52
A nonrecursive definition of modulus 53
A recursive definition of modulus 53
Ackermann's function 54
The Euclidean algorithm 54
Defining a list 57
Programs xix Programs

Membershp of a list 58 Reversing with no duplicates 146


Prefixes and suffutes of a list 59 Computing the greatest common divisor of two integers 152
Determining sublists of lists 60 Computing the factorial of a number 153
Appending two lists 60 An iterative factorial 155
Reversing a list 62 Another iterative factorial 156
Determining the length of a list 64 Generating a range of integers 157
Deleting all occurrences of an element from a list 67 Summing a list of integers 157
Selecting an element from a list 67 Iterative version of summing a list of integers using an accumu-
Permutation sort 69 lator 157
Insertion sort 70 Computing inner products of vectors 158
Quicksort 70 Computing inner products of vectors iteratively 158
Defining binary trees 73 Computing the area of polygons 159

Testing tree membershlp 73 Finding the maximum of a list of integers 160


Determining when trees are isomorphic Checking the length of a list 160
74
Substituting for a term in a tree Finding the length of a list 161
75
Generating a list of integers in a given range 161
Traversals of a binary tree 76
Adjusting a binary tree to satisfy the heap property Flattening a list with double recursion 165
77
Recognizing polynomials 79 Flattening a list using a stack 166
Derivative rules 80 Finding subterms of a term 168
A program for substituting in a term 170
Towers of Hanoi 82
Satisfiability of Boolean formulae 83 Subterm defined using univ 172
Constructing a list corresponding to a term 173
Yet another family example 102
Constructing a term corresponding to a list 174
Yet another family example 130
Merging ordered lists 138 Multiple uses for p l u s 176
Checking for list membershlp 139 A multipurpose length program 177
A more efficient version of grandparent 178
Selecting the first occurrence of an element from a list 140
Nonmembershp of a list 141 Testing if a term is ground 178
Testing for a subset 142 Unification algorithm 180
Testing for a subset 142 Unification with the occurs check 181
Translating word for word 143 Occurs in 182
Removing duplicates from a list Numbering the variables in a term 185
145
Programs xxi Programs

Logical disjunction 186 A puzzle solver 259


Merging ordered lists 190 A description of a puzzle 260
Merging with cuts 192 Connectivity in a finite DAG 265
minimum with cuts 193 Finding a path by depth-first search 266
Recognizing polynomials 193 Connectivity in a graph 266
Interchange sort 195 A depth-first planner 268
Negation as failure 198 Testing the depth-first planner 269
Testing if terms are variants 200 A program solving geometric analogies 272
Implementing f 201 Testing ANALOGY 2 73
Deleting elements from a list 204 ELIZA 275
Deleting elements from a list 204 McSAM 277
If-then-else statement 205 Testing McSAM 278
Determining welfare payments 207 Concatenating diff erence-lists 28 5
Determining welfare payments 207 Flattening a list of lists using difference-lists 286
Writing a list of terms 2 16 Reverse with difference-lists 288
Reading in a list of words 217 Quicksort using difference-lists 289
Towers of Hanoi using a memo-function 222 A solution to the Dutch flag problem 290
Basic interactive loop 223 Dutch flag with difference-lists 291
Alineeditor 224 Normalizing plus expressions 292
An interactive shell 226 Dictionary lookup from a list of tuples 294
Logging a session 228 Dictionary lookup in a binary tree 295
Basic interactive repeat loop 230 Melting a term 296
Consulting a file 230 A queue process 297
Finding the union of two lists 241 Flattening a list using a queue 298
Finding the intersection of two lists 241 Sample data 302
Finding the union and intersection of two lists 241 Applying set predicates 303
Finding parts of speech in a sentence 251 Implementing an all-solutions predicate using difference-lists,
Naive generate-and-test program solving N queens a s s e r t , and r e t r a c t 304
253
Placing one queen at a time 2 55 Testing connectivity breadth-first in a DAG 306
Map coloring 2 56 Testing connectivity breadth-first in a graph 307
Test data for map coloring 257 Lee routing 3 10
Producing a keyword in context (KWIC)index 313
xxii Programs xxiii Programs

Second-order predicates in Prolog 3 16 Testing program composition 370


An interpreter for a nondeterministic finite automaton (NDFA) A Prolog program parsing the language a*b*c* 371
320 Translating grammar rules to Prolog clauses 372
An NDFA that accepts the language ( a b )* 321 Enhancing the language a*b*c* 377
An interpreter for a nondeterministic pushdown automaton Recognizing the language aNI?NcN 377
(NPDA) 322 Parsing the declarative part of a Pascal block 378
An NPDA for palindromes over a finite alphabet 322 A definite clause grammar (DCG)interpreter 381
A meta-interpreter for pure Prolog 324 A DCG interpreter that counts words 382
A meta-interpreter for pure Prolog in continuation style 326 A DCG context-free grammar 383
A tracer for Prolog 328 A DCG computing a parse tree 384
A meta-interpreter for building a proof tree 329 A DCG with subject/object number agreement 38 5
A meta-interpreter for reasoning with uncertainty 330 A DCG for recognizing numbers 387
Reasoning with uncertainty with threshold cutoff 33 1 A depth-first state-transition framework for problem solving
A meta-interpreter detecting a stack overflonl 333 3 90
A nonterminating insertion sort 334 Solving the wolf, goat, and cabbage problem 392
An incorrect and incomplete insertion sort 335 Solving the water jugs problem 394
Bottom-up diagnosis of a false solution 336 Hill climbing framework for problem solving 397
Top-down diagnosis of a false solution 338 Test data 398
Diagnosing missing solution 340 Best-first framework for problem solving 399
Oven placement rule-based system 342 Concise best-first framework for problem solving 400
A skeleton two-level rule interpreter 343 Framework for playing games 402
An interactive rule interpreter 345 Choosing the best move 403
A two-level rule interpreter carrying rules 347 Choosing the best move with the minimax algorithm 406
A two-level rule interpreter with proof trees 348 Choosing a move using minimax with alpha-beta pruning 407
Explaining a proof 3 50 Playing mastermind 4 13
An explanation shell 3 52 A program for playing a winning game of Nim 4 17
A program accepting palindromes 3 59 A complete program for playing Kalah 424
a
A meta-interpreter for determining residue 361 A credit evaluation system 432
A simple partial reduction system 362 Test data for the credit evaluation system 437
Specializing an NPDA 363 A program for solving equations 442
Specializing a rule interpreter 364 A compiler from PL to machine language 462
Composing two enhancements of a skeleton 368 Test data 465
Series Foreword

The logic programming approach to computing investigates the use of


logic as a programming language and explores computational models
based on controlled deduction.
The field of logic programming has seen a tremendous growth in the
last several years, both in depth and in scope. Thls growth is reflected in
the number of articles, journals, theses, books, workshops, and confer-
ences devoted to the subject. The MIT Press series in logic programming
was created to accommodate t h s development and to nurture it. It is
dedicated to the publication of hgh-quality textbooks, monographs, col-
lections, and proceedings in logic programming.

Ehud Shapiro
The Weizmann Institute of Science
Rehovot, Israel
Foreword

Programming in Prolog opens the mind to a new way of loolung at com-


puting. There is a change of perspective which every Prolog programmer
experiences when first getting to know the language.
I shall never forget my first Prolog program. The time was early 1974.
I had learned about the abstract idea of logic programming from Bob
Kowalski at Edinburgh, although the name "logic programming" had not
yet been coined. The main idea was that deduction could be viewed as a
form of computation, and that a declarative statement of the form
P if Q and R and S .
could also be interpreted procedurally as
To solve P, solve Q and R and S.
Now I had been invited to Marseilles. Here, Alain Colmerauer and his col-
leagues had devised the language Prolog based on the logic programming
concept. Somehow, this realization of the concept seemed to me, at first
sight, too simpleminded. However, Gerard Battani and Henri Meloni had
implemented a Prolog interpreter in Fortran (their first major exercise in
programming, incidentally). Why not give Prolog a try?
I sat at a clattering teletype connected down an ordinary telephone line
to an IBM machine far away in Grenoble. I typed in some rules defining
how plans could be constructed as sequences of actions. There was one
important rule, modeled on the SRI planner Strips, whlch described how
a plan could be elaborated by adding an action at the end. Another rule,
necessary for completeness, described how to elaborate a plan by insert-
ing an action in the middle of the plan. As an example for the planner to
xxviii Foreword Foreword

work on, I typed in facts about some simple actions in a "blocks world" After more than a decade of growth of interest in Prolog, it is a great
and an initial state of t h s world. I entered a description of a goal state to pleasure to see the appearance of t h s book. Hitherto, knowledge of how
be acheved. Prolog spat back at me: to use Prolog for serious programming has largely been communicated
by word of mouth. T h s textbook sets down and explains for the first
time in an accessible form the deeper principles and techniques of Prolog
meaning it couldn't find a solution. Could it be that a solution was not programming.
deducible from the axioms I had supplied? Ah, yes, I had forgotten to The book is excellent for not only conveying what Prolog is but also ex-
enter some crucial facts. I tried again. Prolog was quiet for a long time plaining how it should be used. The key to understanding how to use
and then responded: Prolog is to properly understand the relationship between Prolog and
logic programming. This book takes great care to elucidate the relation-
DEBORDEMENT DE PILE
ship.
Stack overflow! I had run into a loop. Now a loop was conceivable since Above all, the book conveys the excitement of using Prolog-the thrill
the space of potential plans to be considered was infinite. However, I had of declarative programming. As the authors put it, "Declarative program-
taken advantage of Prolog's procedural semantics to organize the axioms ming clears the mind." Declarative programming enables one to concen-
so that shorter plans ought to be generated first. Could somethng else trate on the essentials of a problem without getting bogged down in
be wrong? After a lot of head scratching, I finally realized that I had too much operational detail. Programming should be an intellectually
rnistyped the names of some variables. I corrected the mistakes, and rewarding activity. Prolog helps to make it so. Prolog is indeed, as the
tried again. authors contend, a tool for thinking.
Lo and behold, Prolog responded almost instantly with a correct plan
to achieve the goal state. Magic! Declaratively correct axioms had assured David H. D. Warren
a correct result. Deduction was being harnessed before my very eyes Manchester, England, September 1986
to produce effective computation. Declarative programming was truly
programming on a higher plane! I had dimly seen the advantages in
theory. Now Prolog had made them vividly real in practice. Never had I
experienced such ease in getting a complex program coded and running.
Of course, I had taken care to formulate the axioms and organize them
in such a way that Prolog could use them effectively. I had a general
idea of how the axioms would be used. Nevertheless it was a surprise
to see how the axioms got used in practice on particular examples. It
was a delightful experience over the next few days to explore how Prolog
actually created these plans, to correct one or two more bugs in my facts
and rules, and to further refine the program.
Since that time, Prolog systems have improved significantly in terms of
debugging environments, speed, and general robustness. The techniques
of using Prolog have been more fully explored and are now better un-
derstood. And logic programming has blossomed, not least because of
its adoption by the Japanese as the central focus of the Fifth Generation
project.
Preface

Seven years have passed since the first edition of The A r t of Prolog was
published. In that time, the perception of Prolog has changed markedly.
While not as widely used as the language C, Prolog is no longer regarded
as an exotic language. An abundance of books on Prolog have appeared.
Prolog is now accepted by many as interesting and useful for certain
applications. Articles on Prolog regularly appear in popular magazines.
Prolog and logic programming are part of most computer science and
engineering programs, although perhaps in a minor role in an artificial
intelligence or programming languages class. The first conference on
Practical Applications of Prolog was held in London in April 1992. A
standard for the language is likely to be in place in 1994. A future for
Prolog among the programming languages of the world seems assured.
In preparing for a second edition, we had to address the question of
how much to change. I decided to listen to a request not to make the new
edition into a new book. This second edition is much like the first, al-
though a number of changes are to be expected in a second edition. The
typography of the book has been improved: Program code is now in a dis-
tinctive font rather than in italics. Figures such as proof trees and search
trees are drawn more consistently. We have taken the opportunity to be
more precise with language usage and to remove minor inconsistencies
with hyphenation of words and similar details. All known typograph-
cal errors have been fmed. The background sections at the end of most
chapters have been updated to take into account recent, important re-
search results. The list of references has been expanded considerably.
Extra, more advanced exercises, whch have been used successfully in my
Prolog classes, have been added.
Let us take an overview of the specific changes to each part in turn. Louis Lassez, Charlie Linville, Per Ljung, David Maier, Fred Mailey, Martin
Part IV, Applications, is unchanged apart from minor corrections and Marshall, Andre Mesarovic, Dan Oldham, Scott Pierce, Lynn Pierce, David
tidylng. Part I, Logic Programs, is essentially unchanged. New programs Pedder, S. S. Ramakrishnan, Chet Ramey, Marry Silverstein, Bill Sloan, Ron
have been added to Chapter 3 on tree manipulation, including heapifying Taylor, Rodney Topor, R. J. Wengert, Ted Wright, and Nan Yang. For the
a binary tree. Extra exercises are also present. former students of CMPS411, I hope the extra marks were sufficient re-
Part 11, The Prolog Langauge, is primarily affected by the imminence of ward.
a Prolog standard. We have removed all references to Wisdom Prolog in Thanks to Sarah Fliegelmann and Venkatesh Srinivasan for help with
the text in preparation for Standard Prolog. It has proved impossible to entering changes to the second edition and TeXing numerous drafts.
guarantee that this book is consistent with the standard. Reaching a stan- Thanks to Phil Gannon and Zoe Sterling for helpful discussions about the
dard has been a long, difficult process for the members of the committee. figures, and to Joe Gelles for drawing the new figures. For proofreading
Certain predicates come into favor and then disappear, making it difficult the second edition, thanks to Kathy Kovacic, David Schwartz, Ashish Jain,
for the authors of a text to know what to write. Furthermore, some of the and Venkatesh Srinivasan. Finally, a warm thanks to my editor, Terry
proposed I/O predicates are not available in current Prologs, so it is im- Ehling, who has always been very helpful and very responsive to queries.
possible to run all the code! Most of the difficulties in reaching a Prolog Needless to say, the support of my family and friends is the most
standard agreeable to all interested parties have been with builtin or sys- important and most appreciated.
tem predicates. This book raises some of the issues involved in adding
builtins to Prolog but largely avoids the concerns by using pure Prolog as Leon Sterling
much as possible. We tend not to give detailed explanations of the con- Cleveland, January 1993
troversial nonlogical behaviors of some of the system predicates, and we
certainly do not use odd features in our code.
Part 111, Advanced Programming Techniques, is the most altered in this
second edition, whlch perhaps should be expected. A new chapter has
been added on program transformation, and many of the other chapters
have been reordered. The chapters on Interpreters and Logic Grammars
have extensive additions.
Many people provided us feedback on the first edition, almost all of
it very positive. I thank you all. Three people deserve special thanks
for talung the trouble to provide long lists of suggestions for improve-
ments and to point out embarrassingly long lists of typos in the first
edition: Norbert Fuchs, Harald Sclndergaard, and Stanley Selkow. The
following deserve mention for pointing out mistakes and typos in the
various printings of the first edition or making constructive comments
about the book that led to improvements in later printings of the first
edition and for t h s second edition. The list is long, my memory some-
times short, so please forgive me if I forget to mention anyone. Thanks
to Hani Assiryani, Tim Boemker, Jim Brand, Bill Braun, Pu Chen, Yves
Deville, George Ernst, Claudia Giinther, Ann Halloran, Sundar Iyengar,
Gary Kacmarcik, Mansoor Khan, Sundeep Kumar, Arun Lakhotia, Jean-
Preface to First Edition

The origins of this book lie in graduate student courses aimed at teach-
ing advanced Prolog programming. A wealth of techniques has emerged
in the fifteen years since the inception of Prolog as a programming lan-
guage. Our intention in this book has been to make accessible the pro-
gramming techniques that kindled our okvn excitement, imagination, and
involvement in this area.
The book fills a general need. Prolog, and more generally logic pro-
gramming, has received wide publicity in recent years. Currently avail-
able books and accounts, however, typically describe only the basics. All
but the simplest examples of the use of Prolog have remained essentially
inaccessible to people outside the Prolog community.
We emphasize throughout the book the distinction between logic pro-
gramming and Prolog programming. Logic programs can be understood
and studied, using two abstract, machine-independent concepts: truth
and logical deduction. One can ask whether an axiom in a program is
true, under some interpretation of the program symbols; or whether a
logical statement is a consequence of the program. These questions can
be answered independently of any concrete execution mechanism.
On the contrary, Prolog is a programming language, borrowing its basic
constructs from logic. Prolog programs have precise operational mean-
ing: they are instructions for execution on a computer-a Prolog ma-
chine. Prolog programs in good style can almost always be read as log-
ical statements, thus inheriting some of the abstract properties of logic
programs. Most important, the result of a computation of such a Pro-
log program is a logical consequence of the axioms in it. Effective Prolog
Preface to First Edition Preface to First Edition

programming requires an understanding of the theory of logic program- The main part of the book is Part 111. We describe advanced Prolog
ming. programming techniques that have evolved in the Prolog programming
The book consists of four parts: logic programming, the Prolog lan- community, illustrating each with small yet powerful example programs.
guage, advanced techniques, and applications. The first part is a self- The examples typify the applications for which the technique is useful.
contained introduction to logic programming. It consists of five chapters. The six chapters cover nondeterministic programming, incomplete data
The first chapter introduces the basic constructs of logic programs. Our structures, parsing with DCGs, second-order programming, search tech-.
account differs from other introductions to logic programming by ex- niques, and the use of meta-interpreters.
plaining the basics in terms of logical deduction. Other accounts explain The final part consists of four chapters that show how the material in
the basics from the background of resolution from which logic program- the rest of the book can be combined to build application programs. A
ming originated. we have found the former to be a more effective means common request of Prolog n e ~ ~ o m eisr sto see larger applications. They
of teaching the material, which students find intuitive and easy to under- understand how to write elegant short programs but have difficulty in
stand. building a major program. The applications covered are game-playing
The second and thlrd chapters of Part I introduce the two basic styles programs, a prototype expert system for evaluating requests for credit, a
of logic programming: database programming and recursive program- symbolic equation solver, and a compiler.
ming. The fourth chapter discusses the computation model of logic pro- During the development of the book, it has been necessary to reorga-
gramming, introducing unification, while the fifth chapter presents some nize the foundations and basic examples existing in the folklore of the
theoretical results hithout proofs. In developing t h ~ spart to enable the logic programming community. Our structure constitutes a novel frame-
clear explanation of advanced techniques, we have introduced new con- work for the teaching of Prolog.
cepts and reorganized others, in particular, in the discussion of types Material from this book has been used successfully for several courses
and termination. Other issues such as complexity and correctness are on logic programming and Prolog: in Israel, the United States, and Scot-
concepts whose consequences ha\re not yet been fullj. del~elopedin the land. The material more than suffices for a one-semester course to first-
logic programming research communitj.. year graduate students or advanced undergraduates. There is consider-
The second part is an introduction to Prolog. It consists of Chapters 6 able scope for instructors to particularize a course to suit a special area
through 13. Chapter 6 discusses the computation model of Prolog in of interest.
contrast to logic programming, and gi\.es a comparison between Prolog A recommended division of the book for a 13-week course to senior un-
and conventional programming languages such as Pascal. Chapter 7 dis- dergraduates or first-year graduates is as follows: 4 weeks on logic pro-
cusses the differences between composing Prolog programs and logic gramming, encouraging students to develop a declarative style of writing
programs. Examples are gi\,en of basic programming techniques. programs, 4 weeks on basic Prolog programming, 3 weeks on advanced
The next fi\re chapters introduce system-provided predicates that are techniques, and 2 weeks on applications. The advanced techniques
essential to make Prolog a practical programming language. We clas- should include some discussion of nondeterminism, incomplete data
sify Prolog system predicates into four categories: those concerned structures, basic second-order predicates, and basic meta-interpreters.
with efficient arithmetic, structure inspection, meta-logical predicates Other sections can be covered instead of applications. Application areas
that discuss the state of the computation, and extra-logical predicates that can be stressed are search techniques in artificial intelligence, build-
that achieve side effects outside the computation model of logic pro- ing expert systems, writing compilers and parsers, symbol manipulation,
gramming. One chapter is devoted to the most notorious of Prolog and natural language processing.
extra-logical predicates, the cut. Basic techniques using these system There is considerable flexibility in the order of presentation. The ma-
predicates are explained. The final chapter of the section gives assorted terial from Part I should be covered first. The material in Parts I11 and IV
pragmatic programming tips. can be interspersed with the material in Part I1 to show the student how
Preface to First Edition
xxxviii Preface to First Edition

prepared the appendix. The publishers, MIT Press, were helpful and sup-
larger Prolog programs using more advanced techniques are composed
portive.
in the same style as smaller examples.
Finally, we acknowledge the support of family and friends, without
Our assessment of students has usually been 50 percent by homework
which nothmg would get done.
assignments throughout the course, and 50 percent by project. Our expe-
rience has been that students are capable of a significant programming
Leon Sterling
task for their project. Examples of projects are prototype expert systems,
1986
assemblers, game-playing programs, partial evaluators, and implementa-
tions of graph theory algorithms.
For the student who is studying the material on her own, we strongly
advise reading through the more abstract material in Part I. A good Pro-
log programming style develops from thinking declaratively about the
logic of a situation. The theory in Chapter 5, however, can be skipped
until a later reading.
The exercises in the book range from very easy and well defined to
difficult and open-ended. Most of them are suitable for homework exer-
cises. Some of the more open-ended exercises were submitted as course
projects.
The code in this book is essentially in Edinburgh Prolog. The course has
been given where students used several different variants of Edinburgh
Prolog, and no problems were encountered. All the examples run on
Wisdom Prolog, whlch is discussed in the appendixes.
We acknowledge and thank the people who contributed directly to the
book. We also thank, collectively and anonymously, all those who indi-
rectly contributed by influencing our programming styles in Prolog. Im-
provements were suggested by Lawrence Byrd, Oded Maler, Jack Minker,
Richard O'Keefe, Fernando Pereira, and several anonymous referees.
We appreciate the contribution of the students who sat through
courses as material from the book was being debugged. The first author
acknowledges students at the University of Edinburgh, the Weizmann
Institute of Science, Tel Aviv University, and Case Western Reserve Uni-
versity. The second author taught courses at the Weizmann Institute and
Hebrew University of Jerusalem, and in industry.
We are grateful to many people for assisting in the technical aspects
of producing a book. We especially thank Sarah Fliegelmann, who pro-
duced the various drafts and camera-ready copy, above and beyond the
call of duty. Thls book might not have appeared without her tremendous
efforts. Arvind Bansal prepared the index and helped with the references.
Yehuda Barbut drew most of the figures. Max Goldberg and Shmuel Safra
Introduction

The inception of logic is tied with that of scientific thinking. Logic pro-
vides a precise language for the explicit expression of one's goals, knowl-
edge, and assumptions. Logic provides the foundation for deducing
consequences from premises; for studying the truth or falsity of state-
ments given the truth or falsity of other statements; for establishing the
consistency of one's claims; and for \,erif>.ingthe validity of one's argu-
ments.
Computers are relati\rely new in our intellectual history. Similar to
logic, they are the object of scientific study and a powerful tool for
the advancement of scientific endeavor. Like logic, computers require
a precise and explicit statement of one's goals and assumptions. Un-
like logic, which has developed with the power of human thinking as the
only external consideration, the development of computers has been gov-
erned from the start by severe technological and engineering constraints.
Although computers were intended for use by humans, the difficul-
ties in constructing them were so dominant that the language for
expressing problems to the computer and instructing it how to solve
them was designed from the perspective of the engineering of the com-
puter alone.
Almost all modern computers are based on the early concepts of von
Neumann and his colleagues, which emerged during the 1940s. The von
Neumann machine is characterized by a large uniform store of memory
cells and a processing unit with some local cells, called registers. The
processing unit can load data from memory to registers, perform arith-
metic or logical operations on registers, and store values of registers
back into memory. A program for a von Neumann machine consists of
Introduction lnrroduction

a sequence of instructions to perform such operations, and an additional "coding," the last, mundane, intellectually trivial, time-consuming, and
set of control instructions, which can affect the next instruction to be tedious phase of solving a problem using a computer system, is perhaps
executed, possibly depending on the content of some register. at the very root of what has been known as the "software crisis."
As the problems of building computers were gradually understood and Rather, we think that programming can be, and should be, part of
solved, the problems of using them mounted. The bottleneck ceased to the problem-solving process itself; that thoughts should be organized as
be the inability of the computer to perform the human's instructions but programs, so that consequences of a complex set of assumptions can be
rather the inability of the human to instruct, or program, the computer. investigated by "running1'the assumptions; that a conceptual solution to
A search for programming languages convenient for humans to use be- a problem should be developed hand-in-hand with a working program
gan. Starting from the language understood directly by the computer, that demonstrates it and exposes its different aspects. Suggestions in
the machine language, better notations and formalisms were developed. this direction have been made under the title "rapid prototyping."
The main outcome of these efforts was languages that were easier for To achieve this goal in its fullest-to become true mates of the human
humans to express themselves in but that still mapped rather directly thinking process-computers have still a long way to go. However, we
to the underlying machine language. Although increasingly abstract, the find it both appropriate and gratifying from a historical perspective that
languages in the mainstream of development, starting from assembly logic, a companion to the human thinking process since the early days of
language through Fortran, Algol, Pascal, and Ada, all carried the mark human intellectual history, has been discovered as a suitable stepping-
of the underlying machine-the von Neumann architecture. stone in this long journey.
To the uninitiated intelligent person who is not familiar with the en- Although logic has been used as a tool for designing computers and for
gineering constraints that led to its design, the von Neumann machine reasoning about computers and computer programs since almost their
seems an arbitrary, even bizarre, device. Thinking in terms of its con- beginning, the use of logic directly as a programming language, termed
strained set of operations is a nontrivial problem, which sometimes logic programming, is quite recent.
stretches the adaptiveness of the human mind to its limits. Logic programming, as well as its sister approach, functional program-
These characteristic aspects of programming von Neumann computers ming, departs radically from the mainstream of computer languages.
led to a separation of work: there were those who thought how to solve Rather then being derived, by a series of abstractions and reorganiza-
the problem, and designed the methods for its solution, and there were tions, from the von Neumann machine model and instruction set, it is
the coders, who performed the mundane and tedious task of translating derived from an abstract model, which has no direct relation to 'or de-
the instructions of the designers to instructions a computer can use. pendence on to one machine model or another. It is based on the belief
Both logic and programming require the explicit expression of one's that instead of the human learning to think in terms of the operations
knowledge and methods in an acceptable formalism. The task of making of a computer that which some scientists and engineers at some point
one's knowledge explicit is tedious. However, formalizing one's knowl- in history happened to find easy and cost-effective to build, the com-
edge in logic is often an intellectually rewarding activity and usually puter should perform instructions that are easy for humans to provide.
reflects back on or adds insight to the problem under consideration. In In its ultimate and purest form, logic programming suggests that even
contrast, formalizing one's problem and method of solution using the explicit instructions for operation not be given but rather that the knowl-
von Neumann instruction set rarely has these beneficial effects. edge about the problem and assumptions sufficient to solve it be stated
We believe that programming can be, and should be, an intellectu- explicitly, as logical axioms. Such a set of axioms constitutes an alterna-
ally rewarding activity; that a good programming language is a powerful tive to the conventional program. The program can be executed by pro-
conceptual tool-a tool for organizing, expressing, experimenting with, viding it with a problem, formalized as a logical statement to be proved,
and even communicating one's thoughts; that treating programming as called a goal statement. The execution is an attempt to solve the prob-
lntroduction lntroduction

lem, that is, to prove the goal statement, given the assumptions in the addition to the declarative reading of the clause, A is true if the B, are
logic program. true, it can be read as follows: To solve (execute).4, solve (execute) B1 and
A distinguishing aspect of the logic used in logic programming is that B, and . . . and B,,. In this reading, the proof procedure of Horn clause
a goal statement typically is existentially quantified: it states that there logic is the interpreter of the language, and the unification algorithm,
exist some individuals with some property. An example of a goal state- which is at the heart of the resolution proof procedure, performs the
ment is, "there exists a list X such that sorting the list [3,1,21gives X." basic data manipulation operations of variable assignment, parameter
The mechanism used to prove the goal statement is constructive. If suc- passing, data selection, and data construction.
cessful, it provides the identity of the unknown individuals mentioned in At the same time, in the early 1970s, Colmerauer and his group at
the goal statement, which constitutes the output of the computation. In the University of Marseilles-Aix developed a specialized theorem prover,
the preceding example, assuming that the logic program contains appro- written in Fortran, which they used to implement natural language pro-
priate axioms defining the sort relation, the output of the computation cessing systems. The theorem pro\,er, called Prolog (for Programmation
would be X = [ l ,2,3]. en Logique), embodied Kowalski's procedural interpretation. Later, van
These ideas can be summarized in the following two metaphorical Emden and Kowalski de\.eloped a formal semantics for the language of
equations: logic programs, showing that its operational, model-theoretic, and fix-
point semantics are the same.
program = set of axioms. In spite of all the theoretical work and the exciting ideas, the logic pro-
computation = constructive proof of a goal statement from the progrum. gramming approach seemed unrealistic. At the time of its inception, re-
searchers in the United States began to recognize the failure of the "next-
The ideas behind these equations can be traced back as far as intuition- generation .41 languages," such as Micro-Planner and Conniver, which de-
istic mathematics and proof theory of the early twentieth century. They veloped as a substitute for Lisp. T'he main claim against these languages
are related to Hilbert's program, to base the entire body of mathemati- was that they were hopelessl~,inefficient, and very difficult to control.
cal knowledge on logical foundations and to provide mechanical proofs Given their bitter experience with logic-based high-level languages, it is
for its theories, starting from the axioms of logic and set theory alone. no great surprise that IJ.S. artificial intelligence scientists, when hearing
It is interesting to note that the failure of this program, from which en- about Prolog, thought that the Europeans were o\.er-excited o\er what
sued the incompleteness and undecidability results of Godel and Turing, they, the Americans, had already suggested, tried, and disco~~ered not to
marks the beginning of the modern age of computers. work.
The first use of this approach in practical computing is a sequel to In that atmosphere the Prolog-10 compiler was almost an imaginary
Robinson's unification algorithm and resolution principle, published in being. Developed in the mid to late 1!370s by D a ~ i dH. Ll. Warren and
1965. Se\-era1hesitant attempts were made to use this principle as a basis his colleagues, this efhcient implementation of Prolog dispelled all the
of a computation mechanism, but they did not gain any momentum. myths about the impracticality of logic programming. That compiler, still
The beginning of logic programming can be attributed to Kowalski and one of the finest implementations of Prolog around, delivered on pure
Colmerauer. Kowalski formulated the procedural interpretation of Horn list-processing programs a performance comparable to the best Lisp sys-
clause logic. He showed that an axiom tems available at the time. Furthermore, the compiler itself was written
A if BI and B2 and . . . and B, almost entirely in Prolog, suggesting that classic programming tasks, not
just sophisticated A1 applications, could benefit from the power of logic
can be read and executed as a procedure of a recursive programming programming.
language, where A is the procedure head and the B, are its body. In
Introduction Introduction

The impact of this implementation cannot be overemphasized. Without Programming as well as in the general computer science journals and
it, the accumulated experience that has led to this book would not have conferences.
existed. Clearly, one of the dominant areas of interest is the relation between
In spite of the promise of the ideas, and the practicality of their im- logic programming, Prolog, and parallelism. The promise of parallel com-
plementation, most of the Western computer science and A1 research puters, combined with the parallelism that seems to be available in the
community was ignorant, openly hostile, or, at best, indifferent to logic logic programming model, have led to numerous attempts, still ongoing,
programming. By 1980 the number of researchers actively engaged in to execute Prolog in parallel and to devise novel concurrent program-
logic programming were only a few dozen in the United States and about ming languages based on the logic programming computation model.
one hundred around the world. This, however, is a subject for another book.
No doubt, logic programming would have remained a fringe activity
in computer science for quite a while longer hadit not been for the an-
nouncement of the Japanese Fifth Generation Project, which took place
in October 1981. Although the research program the Japanese presented
was rather baggy, faithful to their tradition of achieving consensus at
almost any cost, the important role of logic programming in the next
generation of computer systems was made clear.
Since that time the Prolog language has undergone a rapid transition
from adolescence to maturity. There are numerous commercially avail-
able Prolog implementations on most computers. A large number of Pro-
log programming books are directed to different audiences and empha-
size different aspects of the language. And the language itself has more
or less stabilized, having a de facto standard, the Edinburgh Prolog fam-
ily.
The maturity of the language means that it is no longer a concept for
scientists yet to shape and define but rather a given object, with vices
and virtues. It is time to recognize that, on the one hand, Prolog falls
short of the high goals of logic programming but, on the other hand, is a
powerful, productive, and practical programming formalism. Given the
standard life cycle of computer programming languages, the next few
years will reveal whether these properties show their merit only in the
classroom or prove useful also in the field, where people pa)- money to
solve problems the)- care about.
What are the current active subjects of research in logic programming
and Prolog? Answers to this question can be found in the regular sci-
entific journals and conferences of the field; the Logic Programming
Journal, the Journal of New Generation Computing, the International
Conference on Logic Programming, and the IEEE Symposium on Logic
I Logic Programs

A logic program is a set of axioms, or rules, defining relations between


objects. A computation of a logic program is a deduction of conse-
quences of the program. A program defines a set of consequences, whlch
is its meaning. The art of logic programming is constructing concise and
elegant programs that have the desired meaning.

Leonardo Da Vinci. Old Man thinking. Pen and ink (slightly enlarged). About
1 5 10. Windsor Castle, Royal Library.
Basic Constructs

The basic constructs of logic programming, terms and statements, are


inherited from logic. There are three basic statements: facts, rules, and
queries. There is a single data structure: the logical term.

-- - - - -- - --
- --

1.1 Facts

The simplest kind of statement is called a fact. Facts are a means of


stating that a relation holds between objects. An example is

father (abraham,isaac)

This fact says that Abraham is the father of Isaac, or that the relation fa-
ther holds between the individuals named abraham and isaac. Another
name for a relation is a predicate. Names of individuals are known as
atoms. Similarly, plus ( 2 , 3 , 5 ) expresses the relation that 2 plus 3 is 5.
The familiar plus relation can be realized via a set of facts that defines
the addition table. An initial segment of the table is

plus (O,O,0) . plus (0,I,I) . plus (0,2,2). plus (0,3,3).


plus(I,O,l). plus(l,l,2). plus(1,2,3). plus(1,3,4).

A sufficiently large segment of this table, which happens to be also a


legal logic program, will be assumed as the definition of the plus relation
throughout this chapter.
The syntactic conventions used throughout the book are introduced as
needed. The first is the case convention. It is significant that the names
Chapter 1 Basic Constructs

father(terach,abraham) male (terach) . consequence incrementally through this chapter. Logical consequences
father(terach,nachor). male (abraham) . are obtained by applying deduction rules. The simplest rule of deduction
father(terach,haran) . male (nachor) . is identity: from P deduce P. A query is a logical consequence of an
father(abraham, isaac) . male (haran) .
identical fact.
father(haran,lot) . male(isaac).
father(haran,milcah) . male(1ot). Operationally, answering simple queries using a program containing
father(haran, yiscah) . facts like Program 1.1is straightforward. Search for a fact in the program
female (sarah) . that implies the query. If a fact identical to the query is found, the answer
mother (sarah,isaac) . female (milcah) . is yes.
female(yiscah). The answer no is given if a fact identical to the query is not found,
Program 1.1 A biblical family database because the fact is not a logical consequence of the program. This answer
does not reflect on the truth of the query; it merely says that we failed to
prove the query from the program. Both the queries female (abraham) ?
of both predicates and atoms in facts begin \vith a lowercase letter rather and plus ( I , I,2 ) ? will be answered no with respect to Program 1.1.
than an uppercase letter.
A finite set of facts constitutes a program. This is the simplest form
of logic program. A set of facts is also a description of a situation. This -- -- - -- -- - -

insight is the basis of database programming, to be discussed in the next 1.3 The Logical Variable, Substitutions, and Instances
chapter. ,4n example database of famil) relationships from the Bible is
given as Program 1.1. The predicates f a t h e r , mother, male, and female A logical variable stands for an unspecified individual and is used ac-
express the obvious relationships. cordingly. Consider its use in queries. Suppose we want to know of
whom abraham is the father. One way is to ask a series of queries,
--
f ather(abraham, l o t ) ? , f ather(abraham,milcah)?, . . . , father
Queries (abraham, i s a a c ) ? , . . . until an answer yes is given. A variable allows
a better way of expressing the query as f a t h e r (abraham, X) ?, to which
The second form of statement in a logic program is a query. Queries are the answer is X=isaac. Used in this way, variables are a means o f sum-
a means of retrieving information from a logic program. A query asks marizing many queries. A query containing a variable asks whether there
whether a certain relation holds between objects. For example, the query is a value for the variable that makes the query a logical consequence of
father(abraham, i s a a c ) ? asks whether the f a t h e r relationship holds the program, as explained later.
between abraham and isaac. Given the facts of Program 1.1, the answer Variables in logic programs behave differently from variables in con-
to this query is yes. ventional programming languages. They stand for an unspecified but sin-
Syntactically, queries and facts look the same, but they can be distin- gle entity rather than for a store location in memory.
guished by the context. When there is a possibility of confusion, a termi- Having introduced variables, we can define a term, the single data
nating period will indicate a fact, while a terminating question mark will structure in logic programs. The definition is inductive. Constants and
indicate a query. We call the entity without the period or question mark variables are terms. Also compound terms, or structures, are terms.
a goal. A fact P. states that the goal P is true. A query P? asks whether A compound term comprises a functor (called the principal functor
the goal P is true. A simple query consists of a single goal. of the term) and a sequence of one or more arguments, which are
Answering a query with respect to a program is determining whether terms. A functor is characterized by its name, which is an atom, and
the query is a logical consequence of the program. We define logical its arity, or number of arguments. Syntactically, compound terms have
Chapter 1 Basic Constructs

the form f(tl,tz,.. .,t,), where the functor has name f and is of arity
Operationally, to answer a nonground query using a program of facts,
n, and the t, are the arguments. Examples of compound terms include
search for a fact that is an instance of the query. If found, the answer,
s(O), hot(milk), name(john,doe), list(a,list(b,nil)), foo(X), and
or solution, is that instance. A solution is represented in this chapter by
tree(tree(nil,3,nil), 5 , R ) .
the substitution that, if applied to the query, results in the solution. The
Queries, goals, and more generally terms where variables do not occur
answer is no if there is no suitable fact in the program.
are called ground. Where variables do occur, they are called nonground.
In general, an existential query may have several solutions. Program
For example, foo (a,b) is ground, whereas bar (XI is nonground.
1.1 shows that Haran is the father of three children. Thus the query
Definition father (haran,)()? has the solutions {X=lot}, {X=milcah), {X=yiscah}.
A substitution is a finite set (possibly empty) of pairs of the form XI = ti, Another query with multiple solutions is plus (X,Y,4) ? for finding num-
where XI is a variable and t, is a term, and X, # X, for every i f j, and XI bers that add up to 3. Solutions are, for example, {X=O, Y=4} and {X=l,
does not occur in t,, for any i and j. Y=3}. Note that the different variables X and Y correspond to (possibly)
different objects.
An example of a substitution consisting of a single pair is {X=isaac}.
An interesting variant of the last query is plus (X ,X ,4)?, which insists
Substitutions can be applied to terms. The result of applying a substi-
that the two numbers that add up to 4 be the same. It has a unique
tution 19 to a term A, denoted by A 8 , is the term obtained by replacing
answer {X=2}.
every occurrence of X by t in A, for every pair X = t in 8 .
The result of applying {X=isaac} to the term father (abraham,X) is
the term father (abraham,isaac).
Definition 1.5 Universal Facts
A is an instance of B if there is a substitution 8 such that A = Be. w
Variables are also useful in facts. Suppose that all the biblical characters
The goal father (abraham,isaac) is an instance of father (abraham, like pomegranates. Instead of including in the program an appropriate
X) by this definition. Similarly, mother (sarah,isaac) is an instance of fact for every individual,
mother (X,Y) under the substitution {X=sarah,Y=isaac}.

- -
- -

1.4 Existential Queries

Logically speaking, variables in queries are existentially quantified, which a fact 1ikes (X,pomegranates) can say it all. Used in this way, variables
means, intuitively, that the query father (abraham,X) ? reads: "Does
are a means of summarizing many facts. The fact times (0,X , 0) summa-
there exist an X such that abraham is the father of X?" More generally,
rizes all the facts stating that 0 times some number is 0.
a query p(T,,Tl,. . .,T,)?, which contains the variables XI,&,. . .,Xk reads: Variables in facts are implicitly universally quantified, which means,
"Are there XI,XZ,.. .,Xk such that p(Tl,T2,.. .,T,)?" For convenience, exis- intuitively, that the fact likes(X,pomegranates) states that for all X,
tential quantification is usually omitted. X likes pomegranates. In general, a fact p(Tl,. . .,T,) reads that for all
The next deduction rule we introduce is generalization. An existential XI,. . .,Xk, where the X, are variables occurring in the fact, p(Tl,.. .,T,)
query P is a logical consequence of an instance of it, PO, for any substi- is true. Logically, from a universally quantified fact one can deduce
tution 8 . The fact father (abraham,isaac) implies that there exists an X any instance of it. For example, from likes (X,pomegranates), deduce
such that father (abraham,X) is true, namely, X=isaac.
likes(abraham,pomegranates).
Chupter 1

This is the third deduction rule, called instantiation. From a universally single goal. Logically, it asks whether a conjunction is deducible from the
quantified statement P, deduce an instance of it, P Q , for any substitution program. We use "," throughout to denote logical and. Do not confuse
0. the comma that separates the arguments in a goal with commas used to
As for queries, two unspecified objects, denoted by variables, can be separate goals, denoting conjunction.
constrained to be the same by using the same variable name. The fact In the simplest conjunctive queries all the goals are ground, for exam-
p l u s ( 0 , X, X) expresses that 0 is a left identity for addition. It reads that ple, f a t h e r (abraham, i s a a c ) ,male ( l o t ) ?. The answer to this query us-
for all values of X, 0 plus X is X. A similar use occurs when translating the ing Program 1. I is clearly yes because both goals in the query are facts in
English statement "Everybody likes himself" to l i k e s (X, X). the program. In general, the query a,. . .,&?, where each a is a ground
Answering a ground query with a universally quantified fact is straight- goal, is answered yes with respect to a program P if each is implied by
forward. Search for a fact for which the query is an instance. For example, P. Hence ground conjunctive queries are not very interesting.
the answer to p l u s ( 0 , 2 , 2 ) ? is yes, based on the fact p l u s (0 ,X, X I . An- Conjunctive queries are interesting when there are one or more shared
swering a nonground quer)? using a nonground fact involves a new defi- variables, variables that occur in two different goals of the query. An ex-
nition: a common instance of two terms. ample is the query f a t h e r ( h a r a n , X) ,male (XI ?. The scope of a variable
in a conjunctive query, as in a simple query, is the whole conjunction.
Definition Thus the quer)?p(X),q(X)Preads: "Is there an X such that both p(X) and
C is a common instance of '4 and B if it is an instance of A and an instance q(X)?"
of B, in other words, if there are substitutions 0 , and 0, such that C=AOI Sharcd variables arc used a s a means of constraining a simple query
is syntactically identical to B O l . by restricting the range of a variable. We have already seen an example
cvith the query p l u s (X ,X ,4)?, where the solution of numbers adding
For example, the goals p l u s (O,3, Y) and p l u s ( 0 , X , X) have a com-
up to 4 was restricted to the numbers being the same. Consider the
mon instance p l u s ( 0 , 3 , 3 ) . When the substitution {Y=31 is applied to
querl, f a t h e r ( h a r a n , X) ,male (XI ?. Here solutions to the query f a -
p l u s ( 0 , 3 , Y) and the substitution {X=3) is applied to p l u s ( 0 , X, X I , both
t h e r ( h a r a n , X) ? are restricted t o children that are male. Program 1.1
yield p l u s ( 0 , 3 , 3 ) .
shows there is only one solution, i X = l o t l . Alternatively, this query can
In general, to ansn,er a query using a fact, search for a common in-
be viewed as restricting solutions to the query male (XI ? to individuals
stance of the querj. and fact. The anslver is the common instance, if one
n7ho have Haran for a father.
exists. Otherwise the answer is no.
A slightly different use of a shared variable can be seen in the query
Answering an existential querJr~vitha universal fact using a common
f a t h e r ( t e r a c h , X ) , f a t h e r (X,Y)?. On the one hand, it restricts the sons
instance invol~~es two logical deductions. The instance is deduced from
of t e r a c h to those who are themselves fathers. On the other hand, it con-
the fact by the rule of instantiation, and the query is deduced from the
siders individuals Y, whose fathers are sons of t e r a c h . There are several ,
instance b ~the . rule of generalization.
solutions, for example, 1 X=abraham, Y=isaac} and {X=haran,Y=lot 1. j
A conjunctive query is a logical consequence of a program P if all the !
- -- pp - - -- -- -
goals in the conjunction are consequences of P, where shared variables
1.6 Conjunctive Queries and Shared Variables are instantiated to the same values in different goals. A sufficient condi-
tion is that there be a ground instance of the query that is a consequence
An important extension to the queries discussed so far is conjunctive of P. This instance then deduces the conjuncts in the query via general-
queries. Conjunctive queries are a conjunction of goals posed as a query, ization.
a,..,a?.
for example, f a t h e r ( t e r a c h , X ) , f a t h e r (X ,Y)? or in general, . The restriction to ground instances is unnecessary and will be lifted in
Simple queries are a special case of conjunctive queries when there is a Chapter 4 when we discuss the computation model of logic programs.
Chapter 1 Basic Constructs

We employ this restriction in the meantime to simplify the discussion in A rule for the g r a n d f a t h e r relationship is
the coming sections.
Operationally, to solve the conjunctive query A1,A2,.. .,A,? using a pro-
gram P, find a substitution B such that AIB and . . . and A,B are ground Rules can be viewed in two ways. First, they are a means of ex-
instances of facts in P. The same substitution applied to all the goals en- pressing new or complex queries in terms of simple queries. A query
sures that instances of variables are common throughout the query. For son (X ,haran) ? to the program that contains the preceding rule for son
example, consider the query f a t h e r ( h a r a n , X) ,male (XI ? with respect is translated to the query f a t h e r ( h a r a n , X) ,male (X)? according to the
to Program 1.1. Applying the substitution {X=lot) to the query gives rule, and solved as before. A new query about the son relationship has
the ground instance f a t h e r ( h a r a n , l o t ) , m a l e ( l o t ) ? , which is a conse- been built from simple queries involving f a t h e r and male relationships.
quence of the program. Interpreting rules in this way is their procedural reading. The procedural
reading for the g r a n d f a t h e r rule is: "To answer a query Is X the grand-
father o f Y?, answer the conjunctive query Is X the father o f Z and Z the
father o f Y?."
The second view of rules comes from interpreting the rule as a logical
Interesting conjunctive queries are defining relationships in their own axiom. The backward arrow -is used to denote logical implication. The
right. The query f a t h e r (haran,X) ,male(X)? is asking for a son of Ha- son rule reads: "X is a son of Y if Y is the father of X and X is male."
ran. The query f a t h e r ( t e r a c h , X) ,f a t h e r (X ,Y) ? is asking about grand- In this view, rules are a means of defining new or complex relationships
children of Terach. This brings us to the third and most important state- using other, simpler relationships. The predicate son has been defined in
ment in logic programming, a rule, which enables us to define new rela- terms of the predicates f a t h e r and male. The associated reading of the
tionships in terms of existing relationships. rule is known as the declarative reading. The declarative reading of the
Rules are statements of the form: g r a n d f a t h e r rule is: "For all X, Y , and Z , X is the grandfather of Y if X
is the father of Z and Z is the father of Y."
Although formally all variables in a clause are universally quantified,
we will sometimes refer to variables that occur in the body of the clause,
where n 2 0. The goal A is the head of the rule, and the conjunction of but not in its head, as if they are existentially quantified inside the body.
goals B,,. . .,B, is the body of the rule. Rules, facts, and queries are also For example, the g r a n d f a t h e r rule can be read: "For all X and Y, X is the
called Horn clauses, or clauses for short. Note that a fact is just a special grandfather of Y if there exists a Z such that X is the father of Z and Z
case of a rule when n = 0. Facts are also called unit clauses. We also is the father of Y." The formal justification of this verbal transformation
have a special name for clauses with one goal in the body, namely, when will not be given, and we treat it just as a convenience. Whenever it is a
n = 1. Such a clause is called an iterative clause. As for facts, variables source of confusion, the reader can resort back to the formal reading of a
appearing in rules are universally quantified, and their scope is the whole clause, in which all variables are universally quantified from the outside.
rule. To incorporate rules into our framework of logical deduction, we need
A rule expressing the son relationship is the law of modus ponens. Modus ponens states that from B and A B -
SOII(X,
Y) - f a t h e r (Y ,X) , male (X) . we can deduce A.
Definition
Similarly one can define a rule for the daughter relationship:
The law of universal modus ponens says that from the rule
daughter ( x , Y ) - f a t h e r ( Y ,X ), female (X) R = (A - B1,B2,. . .,B,)
Chapter 1
Basic Constructs

and the facts B,O is ground for 1 I i I n. Then recursively solve each B,8. This pro-
cedure can int~olvearbitrail). long chains of reasoning. It is difficult in
general to guess the correct ground instance and to choose the right rule.
We show in Chapter 4 how the guessing of an instance can be removed.
The rule given for son is correct but is an incomplete specification of
the relationship. For example, we cannot conclude that Isaac is the son
of Sarah. What is missing is that a child can be the son of a mother as
well as the son of a father. A new rule expressing this relationship can be
A' can be deduced if
added, namely,

is an instance of R. 'To define the relationship g r a n d p a r e n t correctly would take four rules
to include both cases of f a t h e r and mother:
[Jniversal modus ponens includes identity and instantiation as special
cases. grandparent(X,Y) - father(X,Z), father(Z,Y).
We are now in a position to give a complete definition of the concept g r a n d p a r e n t (X, Y) - f a t h e r (X, Z) , mother ( 2 ,Y) .
of a logic program and of its associated concept of logical consequence. grandparent ( X ,Y) - mother(X, Z) , f a t h e r ( Z ,Y) .
Definition
g r a n d p a r e n t (X ,Y) - mother (X, Z) , mother (Z ,Y) .
A logic program is a finite set of rules. There is a better, more compact, n-a),of expressing these rules. \Ve need
to define the auxiliary relationship p a r e n t as being a father or a mother.
Definition Part of the art of logic programming is deciding on what intermediate
An existentially quantified goal G is a logical consequence of a program P predicates to define to achie\,e a complete, elegant axiomatization of a
-
if there is a clause in P with a ground instance A B 1 , .. . , B n , n 2 0 such
that B,,. . .,B, are logical consequences of P, and A is an instance of G.
relationship. The rules defining p a r e n t are straightforward, capturing
the definition of a parent being a father or a mother. Logic programs
can incorporate a1ternatik.e definitions, or more technically disjunction,
Note that the goal G is a logical consequence of a program P if and only
by haking alternative rules, as for p a r e n t :
if G can be deduced from P by a finite number of applications of the rule
of universal modus ponens. p a r e n t (X,Y) f ather(X,Y) .
- mother (X,Y) .
--

Consider the query s o n ( S , h a r a n ) ? with respect to Program 1.1 aug- p a r e n t (X, Y)


mented by the rule for son. The substitution {X=lot ,Y=haran} applied
Rules for son and g r a n d p a r e n t are non, respectively,
to the rule gives the instance s o n ( 1 o t , h a r a n ) - f a t h e r ( h a r a n , l o t ) ,
m a l e ( 1 o t ) . Both the goals in the body of this rule are facts in Pro- son(X,Y) -
p a r e n t (Y ,X) , male(X) .
gram 1.1. Thus universal modus ponens implies the quer)? with answer grandparent (X ,Y) -
p a r e n t (X, Z) , p a r e n t (Z,Y) .
{S=lot}.
A collection of rules with the same predicate in the head, such as
Operationally, answering queries reflects the definition of logical con-
the pair of parent rules, is called a procedure. We shall see later that
seauence. Guess a ground
- instance of a goal, and a ground instance of
under the operational interpretation of these rules by Prolog, such a
a rule, and recursively answer the conjunctive query corresponding to
collection of rules is indeed the analogue of procedures or subroutines
the body of that rule. To solve a goal A with program P, choose a rule
in conventional programming languages.
A, -B1,B2,. . .,Bn in P, and guess substitution t) such that A = AlO, and
Basic Constructs
Chapter 1

- - --
- -- p- -- --- -- - Input: son (lot,haran) ? and Program 1.2
1.8 A Simple Abstract Interpreter Resolvent is son (lot,haran)
Resolvent is not empty
choose son(1ot ,haran) (the only choice)
An operational procedure for answering queries has been informally de-
scribed and progressively developed in the previous sections. In this
chooseson(lot,haran) - father(haran,lot), male(1ot)
new resolvent is father(haran,lot) , male(1ot)
section, the details are fleshed out into an abstract interpreter for logic Resolvent is not empty
programs. In keeping with the restriction of universal modus ponens to choose father (haran,lot)
ground goals, the interpreter only answers ground queries. choose father (haran,lot) .
The abstract interpreter performs yes/no computations. It takes as new resolvent is male (lot)
input a program and a goal, and answers yes if the goal is a logi- Resolvent is not empty
cal consequence of the program and no otherwise. The interpreter is choosemale(lot)
given in Figure 1.1. Note that the interpreter may fail to terminate if choose male (lot) .
the goal is not deducible from the program, in which case no answer is new resolvent is empty
given. Output: yes
The current, usually conjunctive, goal at any stage of the computation Figure 1.2 Tracing the interpreter
is called the resolvent. A trace of the interpreter is the sequence of resol-
vents produced during the computation. Figure 1.2 is a trace of answer-
ing the query son(lot ,har+n)? with respect to Program 1.2, a subset of father(abraham,isaac). malecisaac) .
the facts of Program 1.1 together with rules defining son and daughter. father(haran,lot). male(1ot).
For clarit)., Figure 1.2 also explicitly-states the choice of goal and clause father(haran,milcah). female (milcah) .
made at each iteration of the abstract interpreter. father(haran,yiscah). female(yiscah).
Each iteration of the while loop of the abstract interpreter corresponds
to a single application of modus ponens. This is called a reduction.
Program 1.2 Biblical family relationships

Input: ;Z ground goal (; and a program P


Output: ,v~,ri fis a log~calconsequence of P.
(; Definition
no othenz~se A reduction of a goal G by a program P is the replacement of G by the
body of an instance of a clause in P , whose head is identical to the chosen
Algorithm: Initialize the resolvent to G.
while the resolvent is not empt) do goal.
choose a goal A from the resolvent
A reduction is the basic computational step in logic programming. The
choose a ground instance of a clause .A' -B,,. . .,B,, from P
such that A and A' are identical
goal replaced in a reduction is reduced, and the new goals are derived.
(ifno such goal and clause exist,exit the while loop) In this chapter, we restrict ourselves to ground reductions, where the
replace A by B,,.. .,B,, in the resolvent goal and the instance of the clause are ground. Later, in Chapter 4, we
If' the resolLent is ernptJ.,thcn output yes, else output no. consider more general reductions where unification is used to choose the
instance of the clause and make the goal to be reduced and the head of
Figure 1.1 An abstract interpreter to answer ground queries with respect to the clause identical.
logic programs
Chapter 1 Basic Constructs

The trace in Figure 1.2 contains three reductions. The first reduces the
goal s o n ( 1 o t , h a r a n ) and produces two derived goals, f a t h e r ( h a r a n ,
l o t ) and male ( l o t ) . The second reduction is of f a t h e r ( h a r a n , l o t and
produces no derived goals. The third reduction also produces no derived
goals in reducing male ( l o t ) .
There are two unspecified choices in the interpreter in Figure 1.1. The Figure 1.3 A simple proof tree
first is the goal to reduce from the resolvent. The second choice is the
clause (and an appropriate ground instance) to reduce the goal. These
two choices have very different natures. A trace of a query implicitly contains a proof that the query follows
The selection of the goal to be reduced is arbitrary. In any given resol- from the program. A more convenient representation of the proof is with
vent, all the goals must be reduced. It can be shown that the order of a proof tree. A proof tree consists of nodes and edges that represent the
reductions is immaterial for answering the query. goals reduced during the computation. The root of the proof tree for a
In contrast, the choice of the clause and a suitable instance is criti- simple query is the query itself. The nodes of the tree are goals that are
cal. In general, there are several choices of a clause, and infinitely many reduced during the computation. There is a directed edge from a node
ground instances. The choice is made nondeterministically. The concept to each node corresponding to a derived goal of the reduced goal. The
of nondeterministic choice is used in the definition of many computa- proof tree for a conjunctive query is just the collection of proof trees for
tion models, e.g., finite automata and Turing machines, and has proven the individual goals in the conjunction. Figure 1.3 gives a proof tree for
to be a powerful theoretic concept. A nondeterministic choice is an un- the program trace in Figure 1.2.
specified choice from a number of alternatives, which is supposed to be An important measure provided by proof trees is the number of nodes
made in a "clairvoyant" way. If only some of the alternatives lead to a in the tree. It indicates how many reduction steps are performed in a
successful computation, then one of them is chosen. Formally, the con- computation. This measure is used as a basis of comparison between
cept is defined as follows. A computation that contains nondeterministic different programs in Chapter 3.
choices succeeds if there is a sequence of choices that leads to success.
Of course, no real machine can directly implement this definition. How- - -- -- -

ever, it can be approximated in a useful way, as done in Prolog. This is 1.9 The Meaning of a Logic Program
explained in Chapter 6.
The interpreter given in Figure 1.1 can be extended to answer non- How can we know if a logic program says what we wanted it to say? If
ground existential queries by an initial additional step. Guess a ground it is correct, or incorrect? In order to answer such questions, we have
instance of the query. This is identical to the step in the interpreter of to define what is the meaning of a logic program. Once defined, we can
guessing ground instances of the rules. It is difficult in general to guess examine if the program means what we have intended it to mean.
the correct ground instance, since that means knowing the result of the
computation before performing it. Definition
A new concept is needed to lift the restriction to ground instances and The meaning of a logic program P, M(P), is the set of ground goals
remove the burden of guessing them. In Chapter 4, we show how the deducible from P.
guess of ground instances can be eliminated, and we introduce the com-
putational model of logic programs more fully. IJntil then it is assumed From this definition it follows that the meaning of a logic program
that the correct choices can be made. composed just of ground facts, such as Program 1.1, is the program it-
self. In other words, for simple programs, the program "means just what
Chapter 1 27 Basic Constructs

--
--.
it says." Consider Program 1.1 augmented with the two rules defining --

the parent relationship. What is its meaning? It contains, in addition 1.10 Summary
to the facts about fathers and mothers, mentioned explicitly in the pro-
gram, all goals of the form parent(X,Y) for every pair X and Y such We conclude this section with a summary of the constructs and concepts
that f a t h e r (X ,Y) or mother (X ,Y) is in the program. This example shows introduced, filling in the remaining necessary definitions.
that the meaning of a program contains explicitly whatever the program The basic structure in logic programs is a term. A term is a constant,
states implicitly. a variable, or a compound term. Constants denote particular individuals
Assuming that we define the intended meaning of a program also to such as integers and atoms, while variables denote a single but unspec-
be a set of ground goals, we can ask what is the relation between the ified individual. The symbol for an atom can be any sequence of char-
actual and the intended meanings of a program. We can check whether acters, which is quoted if there is possibility of confusion with other
everything the program says is correct, or whether the program says symbols (such as variables or integers). Symbols for variables are distin-
everything we wanted it to say. guished by beginning with an uppercase letter.
Informally, we say that a program is correct with respect to some A compound term comprises a functor (called the principal functor
intended meaning M if the meaning of P, M(P), is a subset of M. That is, of the term) and a sequence of one or more terms called arguments. A
a correct program does not say things that were not intended. A program functor is characterized by its name, which is an atom, and its arity or
is complete with respect to M if M is a subset of M(P).That is, a complete number of arguments. Constants are considered functors of arity 0. Syn-
program says everything that is intended. It follows that a program P is tactically, compound terms have the form f ( t l,tL,.. .,tn)where the functor
correct and complete with respect to an intended meaning M if M = M ( P ) . has name f and is of arity n, and the t, are the arguments. A functor
Throughout the book, when meaningful predicate and constant names f of arity n is denoted f/n. Functors with the same name but different
are used, the intended meaning of the program is assumed to be the one arities are distinct. Terms are ground if they contain no variables; other-
intuitively implied by the choice of names. wise they are nonground. Goals are atoms or compound terms, and are
For example, the program for the son relationship containing only generally nonground.
the first axiom that uses f a t h e r is incomplete with respect to the in- A substitution is a finite set (possibly empty) of pairs of the form X = t ,
tuitively understood intended meaning of son, since it cannot deduce where X is a variable and t is a term, with no variable on the left-hand
s o n ( i s a a c , s a r a h ) . If we add to Program 1.1 the rule side of a pair appearing on the right-hand side of another pair, and no
two pairs having the same variable as left-hand side. For any substitution
O = {XI = t , ,X , = t i , . . . , X , = t,} and term s, the term so denotes the
result of simultaneously replacing in s each occurrence of the variable
XI by t,, 1 I i r n; the term sB is called an instance of s. More will be said
it would make the program incorrect with respect to the intended mean- on this restriction on substitutions in the background to Chapter 4.
ing, since it deduces son(sarah, i s a a c ) . A logic program is a finite set of clauses. A clause or rule is a univer-
The notions of correctness and completeness of a logic program are sally quantified logical sentence of the form
studied further in Chapter 5.
Although the notion of truth is not defined fully here, we will say
that a ground goal is true with respect to an intended meaning if it is where A and the B, are goals. Such a sentence is read declaratively: "A is
a member of it, and false otherwise. We will say it is simply true if it is a implied by the conjunction of the Bi," and is interpreted procedurally "To
member of the intended meaning implied by the names of the predicate answer query A, answer the conjunctive query B1,B2,. . .,Bk." A is called the
and constant symbols appearing in the program. clause's head and the conjunction of the B, the clause's body. If k = 0,
Chapter 1

the clause is known as a fact or unit clause and written A., meaning A
is true under the declarative reading, and goal A is satisfied under the
procedural interpretation. If k = 1, the clause is known as an iterative
Database Programming
clause.
A query is a conjunction of the form
A, ,...,A,? n>0,
where the A, are goals. Variables in a query are understood to be existen-
tially quantified.
A computation of a logic program P finds an instance of a given query
logically deducible from P. A goal G is deducible from a program P if
there is an instance A of G where A -B1,. . .,Bn, n r 0, is a ground instance There are two basic styles of using logic programs: defining a logical
of a clause in P , and the B, are deducible from P. Deduction of a goal database, and manipulating data structures. This chapter discusses data-
from an identical fact is a special case. base programming. A logic database contains a set of facts and rules.
The meaning of a program P is inductively defined using logical de- We show how a set of facts can define relations, as in relational data-
duction. The set of ground instances of facts in P are in the meaning. A bases. We show how rules can define complex relational queries, as in
ground goal G is in the meaning if there is a ground instance G -BJ,. . .,Bn relational algebra. A logic program composed of a set of facts and rules
of a rule in P such that B, ,. . .,B, are in the meaning. The meaning consists of a rather restricted format can express the functionalities associated
of the ground instances that are deducible from the program. with relational databases.
An intended meaning M of a program is also a set of ground unit goals.
A program P is correct with respect to an intended meaning M if M ( P ) is ---- --
- - --
a subset of M . It is complete with respect to M if M is a subset of M ( P ) .
Clearly, it is correct and complete with respect to its intended meaning,
2.1 Simple Databases
which is the desired situation, if M = M ( P ) .
We begin by revising Program 1.1, the biblical database, and its aug-
A ground goal is true with respect to an intended meaning if it is a
mentation with rules expressing family relationships. The database
member of it, and false otherwise.
itself had four basic predicates, f a t h e r / 2 , mother/2, male/l, and f e -
Logical deduction is defined syntactically here, and hence also the
male/l. We adopt a convention from database theory and give for
meaning of logic programs. In Chapter 5 , alternative ways of describing
each relation a relation scheme that specifies the role that each po-
the meaning of logic programs are presented, and their equivalence with
sition in the relation (or argument in the goal) is intended to repre-
the current definition is discussed.
sent. Relation schemes for the four predicates here are, respectively,
f a t h e r ( F a t h e r , Child), mother (Mother , C h i l d ) , male (Person), and
female (Person). The mnemonic names are intended to speak for them-
selves.
Variables are given mnemonic names in rules, but usually X or Y when
discussing queries. Multiword names are handled differently for vari-
ables and predicates. Each new word in a variable starts with an upper-
case letter, for example, NieceOrNephew, while words are delimited by
Chapter 2 Database Programming

abraham f isaac. abraham f haran. abraham f lot.


underscores for predicate and function names, for example, schedule-
abraham # milcah. abraham f yiscah. isaac f haran.
conflict. isaac f lot. isaac f milcah. isaac f yiscah.
New relations are built from these basic relationships by defining suit- haran f lot. haran f milcah. haran f yiscah.
able rules. Appropriate relation schemes for the relationships introduced lot f milcah. lot f yiscah. milcah f yiscah.
in the previous chapter are son (Son, P a r e n t ) , d a u g h t e r (Daughter,
Figure 2.1 Defining inequality
P a r e n t ) , p a r e n t ( P a r e n t , C h i l d ) , and g r a n d p a r e n t (Grandparent,
Grandchild). From the logical viewpoint, it is unimportant which re-
lationships are defined by facts and which by rules. For example, if the
available database consisted of p a r e n t , male and female facts, the rules
uncle(Uncle,Person) -
defining son and g r a n d p a r e n t are still correct. New rules must be writ- sibling(Sibl,Sib2) -
brother(Uncle,Parent), parent(Parent,Person).

parent (Parent ,Sib11 , parent (Parent ,Sib2), Sib1 f Sib2


ten for the relationships no longer defined by facts, namely, f a t h e r and
mother. Suitable rules are
cousin(Cousinl,Cousin2)
parent (Parent 1,Cousinl) ,
-
f a t h e r (Dad, Child) - p a r e n t (Dad, C h i l d ) , male at ad) . parent(Parent2,Cousin2),
mother (Mum, Child) - p a r e n t ( ~ u mc,h i l d ) , female ( ~ u m .) sibling(Parentl,Parent2).

Program 2.1 Defining family relationships


Interesting rules can be obtained by making relationships explicit that
are present in the database only implicitly. For example, since we know
the father and mother of a child, we know which couples produced off- we introduce a predicate # (Terml ,Term2). It is convenient to write this
spring, or to use a Biblical term, procreated. This is not given explicitly in predicate as an i n k operator. Thus Terml f Term2 is true if Term1 and
the database, but a simple rule can be written recovering the information. Term2 are different. For the present it is restricted to constant terms.
The relation scheme is procreated(Man, Woman). It can be defined, in principle, by a table X f Y for every two different
individuals X and Y in the domain of interest. Figure 2.1 gives part of
procreated(Man, Woman) - the appropriate table for Program 1.1.
f a t h e r (Man, C h i l d ) , mother (woman, C h i l d ) . The new brother rule is
This reads: "Man and Woman procreated if there is a C h i l d such that Man
is the father of C h i l d and Woman is the mother of Child."
brother (Brother,Sib) -
~ a r e n(tP a r e n t , B r o t h e r ) ,
Another example of information that can be recovered from the simple parent (Parent, Sib) ,
information present is sibling relationships - brothers and sisters. We male ( B r o t h e r ) ,
give a rule for b r o t h e r ( B r o t h e r , S i b l i n g ) . Brother f S i b .
brother(Brother,Sib) - The more relationshps that are present, the easier it is to define com-
p a r e n t ( P a r e n t , B r o t h e r ) , p a r e n t ( P a r e n t , S i b ) , male ( B r o t h e r ) . plicated relationships. Program 2.1 defines the relationships
u n c l e ( U n c l e , P e r s o n ) , s i b l i n g ( S i b l , S i b 2 ) , and cousin(Cousin1,
This reads: "Brother is the brother of S i b if P a r e n t is a parent of both
Cousin2). The definition of u n c l e in Program 2.1 does not define the
B r o t h e r and S i b , and B r o t h e r is male."
There is a problem with this definition of brother. The query b r o t h e r husband of a sister of a parent to be an uncle. This may or may not be
(X,X)? is satisfied for any male child X, which is not our understanding
the intended meaning. In general, different cultures define these family
of the brother relationship. relationshps differently. In any case, the logic makes clear exactly what
the programmer means by these family relationshps.
In order to preclude such cases from the meaning of the program,
Chapter 2 Database Programming

Another relationship implicit in the family database is whether a


woman is a mother. This is determined by using the mother/2 relation-
s h p . The new relation scheme is mother (Woman), defined by the rule
mother (Woman) - mother (Woman,Child) .
T h s reads: "Woman is a mother if she is the mother of some Child." Note
that we have used the same predicate name, mother, to describe two
inverter (Input,Output)-
Output is the inversion of Input.
different mother relationshps. The mother predicate takes a different
number of arguments, i.e., has a different arity, in the two cases. In
general, the same predicate name denotes a different relation when it has
a different arity.
We change examples, lest the example of family relationships become
nand-gate(Input1,lnput2,Output) -
Output is the logical nand of Inputl and Input2.
incestuous, and consider describing simple logical circuits. A circuit can
be viewed from two perspectives. The first is the topological layout of
the physical components usually described in the circuit diagram. The
second is the interaction of functional units. Both views are easily ac-
commodated in a logic program. The circuit diagram is represented by and-gate(lnputl,lnputZ,Output) -
a collection of facts, while rules describe the functional components. Output is the logical and of Inputl and Inputi'.
Program 2.2 is a database ghing a simplified view of the logical and-
gate drawn in Figure 2.2. The facts are the connections of the particular
resistors and transistors comprising the circuit. The relation scheme
for resistors is resistor (Endl,End2) and for transistors transis-
Program 2.2 A circuit for a logical and-gate
tor(Gate,Source,Drain).

The program demonstrates the style of commenting of logic programs


Power we will follow throughout the book. Each interesting procedure is pre-
ceded by a relation scheme for the procedure, shown in italic font, and by
English text defining the relation. We recommend this style of comment-
ing, whch emphasizes the declarative reading of programs, for Prolog
programs as well.
Particular configurations of resistors and transistors fulfill roles cap-
tured via rules defining the functional components of the circuit. The
circuit describes an and-gate, which takes two input signals and pro-
duces as output the logical and of these signals. One way of building
an and-gate, and how this circuit is composed, is to connect a nand-gate
with an inverter. Relation schemes for these three components are and-
gate(Inputl,Input2,0utput), nand-gate(Inputl,Input2,0utput),
and inverter (Input,Output).
Figure 2.2 A logical circuit
35 Database Programming
Chapter 2 I

To appreciate Program 2.2, let us read the inverter rule. T h s states that 2.2 Structured Data and Data Abstraction
an inverter is built up from a transistor with the source connected to the
ground, and a resistor with one end connected to the power source. The A limitation of Program 2.2 for describing the and-gate is the treatment
gate of the transistor is the input to the inverter, whde the free end of the of the circuit as a black box. There is no indication of the structure of the
resistor must be connected to the drain of the transistor, whlch forms circuit in the answer to the and-gate query, even though the structure
the output of the inverter. Sharing of variables is used to insist on the has been implicitly used in finding the answer. The rules tell us that
common connection. the circuit represents an and-gate, but the structure of the and-gate is
Consider the query and-gate (In1 , I d ,Out) ? to Program 2.2. It has present only implicitly. We remedy this by adding an extra argument to
the solution {Inl=n3,In2=n5,Out=nl]. T h s solution confirms that the each of the goals in the database. For uniformity, the extra argument
circuit described by the facts is an and-gate, and indicates the inputs and becomes the first argument. The base facts simply acquire an identifier.
output. Proceeding from left to right in the diagram of Figure 2.2, we label the
resistors rl and r2, and the transistors tl, t2,and t3.
2.1.1 Exercises for Section 2.1 Names of the functional components should reflect their structure. An
inverter is composed of a transistor and a resistor. To represent t h s ,
(i) Modify the rule for brother on page 21 to give a rule for sister, we need structured data. The technique is to use a compound term,
the rule for uncle in Program 2.1 to give a rule for niece, and inv (T ,R) , where T and R are the respective names of the inverter's com-
the rule for sibling in Program 2.1 so that it only recognizes full ponent transistor and rcsistor. Analogously, the name of a nand-gate will
siblings, i.e., those that have the same mother and father. be nand(T1 ,T2,R),where TI, T2, and R name the two transistors and re-
sistor that comprise a nand-gate. Finally, an and-gate can be named in
(11) Using a predicate married-couple (Wife ,Husband),define the rela-
terms of an inverter and a nand-gate. The modified code containing the
tionships mother-in-law, brother-in-law, and son-in-law.
names appears in Program 2.3.
(iii) Describe the layout of objects in Figure 2.3 with facts using the The query and-gate (G,In1 , In2,Out)? has solution {G=and(nand(t2,
predicates left-of (ObjectI,Object2) and above(0bjectl ,Ob- t3,r2) ,inv(tl ,rl)), Inl=n3,In2=n5,Out=nl}. Inl, In2, and Out have
ject2). Define predicates right-of (Object1 ,Object2) and below their previous values. The complicated structure for G reflects accurately
(Object l,Object2) in terms of lef t-of and above, respectively. the functional composition of the and-gate.
Structuring data is important in programming in general and in logic
programming in particular. It is used to organize data in a meaningful
way. Rules can be written more abstractly, ignoring irrelevant details.
More modular programs can be achieved this way, because a change of
data representation need not mean a change in the whole program, as
shown by the following example.
Consider the following two ways of representing a fact about a lecture
course on complexity given on Monday from 9 to 11 by David Hare1 in
the Feinberg building, room A:

and
Figure 2.3 Still-life objects
Database P r o g r a m m i n g
Chapter 2

resistor ( R , N o d e l, N o d e 2 ) -
R is a resistor between Node1 and N o d e 2 .
resistor(rl,power,nl). The first fact represents course as a relation between eight items - a
resistor(r2,power,n2). course name, a day, a starting hour, a finishng hour, a lecturer's first
transistor ( T,Gate,Source,Drain) - name, a lecturer's surname, a building, and a room. The second fact
T is a transistor whose gate is Gate, makes course a relation between four items - a name, a time, a lecturer,
source is Source, and drain is Drain. and a location with further qualification. The time is composed of a day,
a starting time, and a finishing time; lecturers have a first name and
a surname; and locations are specified by a building and a room. The
second fact reflects more elegantly the relations that hold.
inverter (l,lnput,Output)
I is an inverter that inverts
- Input to Output
The four-argument version of course enables more concise rules to
be written by abstracting the details that are irrelevant to the query.
inverter(inv(T,R) ,input ,Output)
transistor(~,~nput,ground,Outp~t),
- Program 2.4 contains examples. The occupied rule assumes a predicate
less than or equal, represented as a binary infix operator I .
resistor(R,power,Output).
nand-gate(Nand,Inputl,Input2,Output)
Nand is a gate forming the logical nand, Output,
- Rules not using the particular values of a structured argument need
not "know" how the argument is structured. For example, the rules for
of lnputl and Input2. duration and teaches represent time explicitly as time(Day,Start,
Finish) because the Day or Start or Finish times of the course are de-
nand-gate (nand (TI , T 2 ,R) ,Input1,Input2,Output) '
transistor (TI ,Input1,X,output), sired. In contrast, the rule for lecturer does not. T h s leads to greater
transistor ( T 2 ,Input2 ,ground ,X), modularity, because the representation of time can be changed without
resistor(R,power,Output). affecting the rules that do not inspect it.
and-gate(And,Inputl ,lnput2Output)
And is a gate forming the logical and,
- Output,
We offer no definitive advice on when to use structured data. Not using
structured data allows a uniform representation where all the data are
of Input1 and Input2. simple. The advantages of structured data are compactness of represen-
and-gate(and(N,I) ,Input1,1nput:!,Output) ' tation, which more accurately reflects our perspective of a situation, and
nand-gate ( N ,Input1 ,Input2 ,X),
inverter(I,X,Output).

Program 2.3 The circuit database with names


duration(Course,Length) -
course (Course,time (Day,Start ,Finish),Lecturer,Location) ,
plus(Start,Length,Finish).

occupied(Room,Day,Time) -
course (Course,time(Day,Start ,Finish),Lecturer,Room),
Start 5 Time, Time 5 Finish.

Program 2.4 Course rules


Chapter 2 39 Database Programming

modularity. We can relate the discussion to conventional programming


languages. Facts are the counterpart of tables, whle structured data cor-
2.3 Recursive Rules
respond to records with aggregate fields.
We believe that the appearance of a program is important, particularly The rules described so far define new relationshps in terms of existing
ones. An interesting extension is recursive definitions of relationshps
when attempting difficult problems. A good structuring of data can make
a difference when programming complex problems. that define relationships in terms of themselves. One way of viewing
Some of the rules in Program 2.4 are recovering relations between two recursive rules is as generalization of a set of nonrecursive rules.
individuals, binary relations, from the single, more complicated one. Consider a series of rules defining ancestors - grandparents, great-
All the course information could have been written in terms of binary grandparents, etc:
relations as follows: grandparent (Ancestor,Descendant) -
day (complexity ,monday) . parent(Ancestor,Person), parent(Person,Descendant).
start-time(complexity,9) . greatgrandparent(Ancestor,Descendant) -
f inish-time (complexity, 11) . parent(Ancestor,Person), grandparent(Person,Descendant).
lecturer (complexity,harel) . greatgreatgrandparent(Ancestor,Descendant) -
building(complexity,feinberg). parent (Ancestor ,person), greatgrandparent (Person,
room(complexity ,a) . Descendant).

Rules would then be expressed differently,reverting to the previous style A clear pattern can be seen, which can be expressed in a rule defining the
of malung implicit connections explicit. For example, relationship ancestor (Ancestor ,Descendant):
teaches (Lecturer ,Day) -
lecturer (Course,Lecturer) , day (course,Day)

2.2.1 Exercises for Section 2.2 This rule is a generalization of the previous rules.
A logic program for ancestor also requires a nonrecursive rule, the
(i) Add rules defining the relations location(Course,~uilding), choice of which affects the meaning of the program. If the fact ances-
busy (Lecturer,Time), and cannot-meet (Lecturer1 ,~ecturer2). tor (X ,X) is used, defining the ancestor relationship to be reflexive, peo-
Test with your own course facts. ple will be considered to be their own ancestors. This is not the intuitive
meaning of ancestor. Program 2.5 is a logic program defining the ances-
(ii) Possibly using relations from Exercise (i),define the relation sched- tor relationship, where parents are considered ancestors.
ule-conf lict (Time ,Place,Course1 ,Course2).

(iii) Write a program to check if a student has met the requirements for
a college degree. Facts will be used to represent the courses that the
ancestor (Ancestor,Descendant) -
Ancestor is an ancestor of Descendant.
student has taken and the grades obtained, and rules will be used
to enforce the college requirements.
(iv) Design a small database for an application of your own choice. Use
a single predicate to express the information, and invent suitable
rules. Program 2.5 The ancestor relationship
Chapter 2 Database P r o g r a m m i n g

The ancestor relationshp is the transitive closure of the parent re-


lationship. In general, finding the transitive closure of a relationship is
easily done in a logic program by using a recursive rule.
Program 2.5 defining ancestor is an example of a linear recursive pro-
gram. A program is linear recursive if there is only one recursive goal in
the body of the recursive clause. The linearity can be easily seen from
considering the complexity of proof trees solving ancestor queries. A
proof tree establishing that two individuals are n generations apart given
Program 2.5 and a collection of parent facts has 2 . n nodes.
There are many alternative ways of defining ancestors. The declarative
content of the recursive rule in Program 2.5 is that Ancestor is an ances-
tor of Descendant if Ancestor is a parent of an ancestor of Descendant.
Another way of expressing the recursion is by observing that Ancestor Figure 2.4 A simple graph
would be an ancestor of Descendant if Ancestor is an ancestor of a par-
ent of Descendant. The relevant rule is

ancestor(Ancestor,Descendant) -
ancestor (Ancestor,Person) , parent (Person,~escendant).
Program 2.6 A directed graph
Another version of defining ancestors is not linear recursive. A pro-
gram identical in meaning to Program 2.5 but with two recursive goals in
the recursive clause is connected( N o d e 1 , N o d e 2 ) -
N o d e 1 is connected to Node2 in the
ancestor(Ancestor,Descendant) - graph defined by the edge/2 relation.
parent (Ancestor ,Descendant). connected(Node,Node).
ancestor(Ancestor,Descendant) - connected(Nodel,Node2) - edge(Nodel,Link), connected(Link,Node2)
.
ancestor (Ancestor ,Person) , ancestor (~erson,~escendant)
Program 2.7 The transitive closure of the edge relation
,
Consider the problem of testing connectivity in a directed graph. A
directed graph can be represented as a logic program by a collection i
nected(X,Y), where X and Y are connected. Note that connected is a
of facts. A fact edge (Node1,Node2) is present in the program if there 1 transitive reflexive relation because of the choice of base fact.
is an edge from Node1 to Node2 in the graph. Figure 2.4 shows a graph;
Program 2.6 is its description as a logic program. i
Two nodes are connected if there is a series of edges that can be tra- I 2.3.1 Exercises for Section 2.3
versed to get from the first node to the second. That is, the relation con- I
nected(Node1 ,Node2),which is true if Node1 and Node2 are connected,
is the transitive closure of the edge relation. For example, a and e are
1
1
(i) A stack of blocks can be described by a collection of facts on
(Blockl,Block2), whch is true if Blockl is on Block2. Define a
connected in the graph in Figure 2.4, but b and f are not. Program 2.7 I
predicate above(Blockl,Block2) that is true if Blockl is above
defines the relation. The meaning of the program is the set of goals con- I Block2 in the stack. (Hint: above is the transitive closure of on.)
Chapter 2 Database Programming

(ii) Add recursive rules for left-of and above from Exercise 2.l(iii) on r-diff-s(X1, . . . ,Xn) -- XI, . . . ,Xn), not S O , , . . . ,x,) .
p. 34. Define higher (Objectl ,Object2), which is true if Objectl is
Cartesian product can be defined in a single rule. If r is a relation of
on a line hlgher than Object2 in Figure 2.3.For example, the bicycle
arity m, and s is a relation of arity n, then r-x-s is a relation of arity
is hgher than the fish in the figure.
m + n defined by
(iii) How many nodes are there in the proof tree for connected(a,e)
using Programs 2.6 and 2.7? In general, using Program 2.6 and a
collection of edge/2 facts, how many nodes are there in a proof tree
establishing that two nodes are connected by a path containing n
intermediate nodes? Projection involves forming a new relation comprising'only some of
the attributes of an existing relation. This is straightforward for any
particular case. For example, the projection r13 selecting the first and
----
third arguments of a relation r of arity 3 is
2.4 Logic Programs and the Relational Database Model

Logic programs can be viewed as a powerful extension to the relational


database model, the extra power coming from the ability to specify rules. Selection is similarly straightforward for any particular case. Consider
Many of the concepts introduced haire meaningful analogues in terms of a relation consisting of tuples whose third components are greater than
databases. The converse is also true. The basic operations of the rela- their second, and a relation where the first component is Smith or Jones.
tional algebra are easily expressed within logic programming. In both cases a relation r of arity 3 is used to illustrate. The first example
Procedures composed solely of facts correspond to relations, the arity creates a relation r l :
of the relation being the arity of the procedure. Five basic operations
define the relational algebra: union, set difference, Cartesian product,
projection, and selection. We show how each is translated into a logic
The second example creates a relation r2, which requires a disjunctive
program.
relationship, smith-or- jones:
The union operation creates a relation of arity n from two relations r
and s, both of arity n. The new relation, denoted here r-union-s, is the
union of r and s.It is defined directly as a logic program by two rules:
r2(X1 ,X2,X3) - XI ,X2,X3), smith-or-jones(X,).
smith-or-jones (smith) .
smith-or- jones (jones) .

Some of the derived operations of the relational algebra are more


Set difference involves negation. We assume a predicate not. Intu- closely related to the constructs of logic programming. We mention two,
itively, a goal not G is true with respect to a program P if G is not a intersection and the natural join. If r and s are relations of arity n, the
logical consequence of P. Negation in logic programs is discussed in intersection, r-meet-s is also of arity n and is defined in a single rule.
Chapter 5, where limitations of the intuitive definition are indicated. The
definition is correct, however, if we deal only with ground facts, as is the
case with relational databases.
The definition of r-diff -s of arity n, where r and s are of arity n, is A natural join is precisely a conjunctive query with shared variables.
44 Chapter 2

2.5 Background
Recursive Programming
Readers interested in pursuing the connection between logic program-
ming and database theory are referred to the many papers that have
been written on the subject. A good starting place is the review paper by
Gallaire et al. (1984). There are earlier papers on logic and databases in
Gallaire and Minker (1978). Another interesting book is about the imple-
mentation of a database query language in Prolog (Li, 1984). Our discus-
sion of relational databases follows Ullman (1982).Another good account
of relational databases can be found in Maier (1983).
In the seven years between the appearance of the first edition and the
second edition of t h s book, the database community has accepted logic The programs of the previous chapter essentially retrieve information
programs as extensions of relational databases. The term used for a data- from, and manipulate, finite data structures. In general, mathematical
base extended with logical rules is logic database or deductive database. power is gained by considering infinite or potentially infinite structures.
There is now a wealth of material about logic databases. The rewritten Finite instances then follow as special cases. Logic programs harness this
version of Ullman's text (1989)discusses logic databases and gives point- power by using recursive data types.
ers to the important literature. Logical terms can be classified into types. A type is a (possibly infinite)
Perhaps the major difference between logic databases as taught from set of terms. Some types are conveniently defined by unary relations. A
a database perspective and the view presented here is the way of evalu- relation p/l defines the type p to be the set of X's such that p ( X ) .
ating queries. Here we implicitly assume that the interpreter from Figure For example, the male/l and f emale/l predicates used previously de-
4.2 will be used, a top-down approach. The database community prefers fine the male and female types.
a bottom-up evaluation mechanism. Various bottom-up strategies for an- More complex types can be defined by recursive logic programs. Such
swering a query with respect to a logic database are given in Ullman types are called recursive types. Types defined by unary recursive pro-
(1989). grams are called simple recursive types. A program defining a type is
In general, an n-ary relation can be replaced by n + 1 binary relations, called a w p e definition.
as shown by Kowalski (1979a). If one of the arguments forms a key for In this chapter, m7eshow logic programs defining relations over simple
the relation, as does the course name in the example in Section 2.2, n recursive types, such as integers, lists, and binary trees, and also pro-
binary relations suffice. grams over more complex types, such as polynomials.
The addition of an extra argument to each predicate in the circuit,
as discussed at the beginning of Section 2.2, is an example of an en-
hancement of a logic program. The technique of developing programs 3.1 Arithmetic
by enhancement is of growing importance. More will be said about this
in Chapter 13. The simplest recursive data type, natural numbers, arises from the foun-
dations of mathematics. Arithmetic is based on the natural numbers.
This section gives logic programs for performing arithmetic.
In fact, Prolog programs for performing arithmetic differ considerably
from their logical counterparts, as we will see in later chapters. How-
ever, it is useful to spend time discussing the logic programs. There are
Chapter 3 Recursive Programming

natural-number (XI -
X is a natural number.

Program 3.1 Defining the natural numbers

two main reasons. First, the operations of arithmetic are usually thought
of functionally rather than relationally. Presenting examples for such a
familiar area emphasizes the change in thmlung necessary for compos-
ing logic programs. Second, it is more natural to discuss the underlying
mathematical issues, such as correctness and completeness of programs.
The natural numbers are built from two constructs, the constant sym-
bol 0 and the successor function s of arity 1. All the natural numbers are
then recursively given as 0, s (O),s ( s (0)) , s (s(s(0))1, . . . . We adopt
the convention that sn(0) denotes the integer n, that is, n applications
of the successor function to 0.
As in Chapter 2, we give a relation scheme for each predicate, together
with the intended meaning of the predicate. Recall that a program P
is correct with respect to an intended meaning M if the meaning of
...
P is a subset of M. It is complete if M is a subset of the meaning of Figure 3.1 Proof trees establishing completeness of programs
P. It is correct and complete if its meaning is identical to M. Proving
correctness establishes that everythng deducible from the program is
intended. Proving completeness establishes that everythng intended is natural-number (s(. . .s(0).. .) ) contains n reductions, using the rule in
deducible from the program. Two correctness and completeness proofs Program 3.1, to reach the fact natural-number (O),as shown in the left
are given in t h s section. half of Figure 3.1.
The simple type definition of natural numbers is neatly encapsulated (2) Correctness. Suppose that natural-number(X) is deducible from
in the logic program, shown as Program 3.1. The relation scheme used Program 3.1, in n deductions. We prove that natural-number (X) is in
is natural-number (X),with intended meaning that X is a natural num- the intended meaning of the program by induction on n. If n = 0, then
ber. The program consists of one unit clause and one iterative clause (a the goal must have been proved using a unit clause, whlch implies that X
clause with a single goal in the body). Such a program is called minimal = 0. If n > 0, then the goal must be of the form natural-number (s(X')1,
recursive. since it is deducible from the program, and further, natural-number (X')
Proposition is deducible in n - 1 deductions. By the induction hypothesis, X' is in the
intended meaning of the program, i.e., X'=sk(01 for some k 2 0.
Program 3.1 is correct and complete with respect to the set of goals
natural-number (si(0)), for i > 0.
The natural numbers have a natural order. Program 3.2 is a logic pro-
Proof ( 1 ) Completeness. Let n be a natural number. We show that the gram defining the relation less than or equal to according to the order.
goal natural-number(n) is deducible from the program by giving an We denote the relation with a binary infuc symbol, or operator, ,I accord-
explicit proof tree. Either n is 0 or of the form sn(0). The proof tree ing to mathematical usage. The goal 0 I X has predicate symbol I of
for the goal natural-number(0) is trivial. The proof tree for the goal arity 2, has arguments 0 and X, and is syntactically identical to ' I (0,X) .
)
Recursive Programming
Chapter 3

X5Y - ~Ius(X,Y,Z) -
X , Y , and Z are natural numbers
X and Y are natural numbers, such that Z is the sum of X and Y

0 IX
such that X is less than or equal to Y.
- -
natural-number (XI .
plus ( 0 ,X,X)- -
natural-number (X) .
plus(s()o,Y,s(z)) plus(X,Y,z).
s(X) I s(Y) X 2 Y.
natural-number (X) - See Program 3.1 . natural-number (X) - See Program 3.1 .
Program 3.3 Addition
Program 3.2 The less than or equal relation

(2) Correctness. Let plus(X,Y ,Z) be in the meaning. A simple induc-


The relation scheme is Nl r NZ.The intended meaning of Program 3.2 tive argument on the size of X, similar to the one used in the previous
is all ground facts X 5 Y, where x and Y are natural numbers and X is proposition, establishes that X+Y=Z.
8
less than or equal to Y. Exercise (ii) at the end of this section is to prove
the correctness and completeness of Program 3.2. Addition is usually considered to be a function of two arguments
The recursive definition of 5 is not computationally efficient. The proof rather than a relation of arity 3. Generally, logic programs corresponding
tree establishing that a particular N is less than a particular M has M + 2 to functions of n arguments define relations of arity n + 1. Computing
nodes. We usually think of testing whether one number is less than the value of a function is achieved by posing a query with n arguments
another as a unit operation, independent of the size of the numbers. instantiated and the argument place corresponding to the value of the
Indeed, Prolog does not define arithmetic according to the axioms pre- function uninstantiated. The solution to the query is the value of the
sented in this section but uses the underlpng arithmetic capabilities of function with the given arguments. To make the analogy clearer, we give
the computer directly. a functional definition of addition corresponding to the logic program:
Addition is a basic operation defining a relation between two natural
numbers and their sum. In Section 1.1, a table of the p l u s relation was
assumed for all relevant natural numbers. A recursive program captures
the relation elegantly and more compactly, and is given as Program 3.3. One advantage that relational programs have over functional programs
The intended meaning of Program 3.3 is the set of facts plus(X,Y ,Z), is the multiple uses that can be made of the program. For example, the
where X, Y, and Z are natural numbers and X+Y=Z. query p l u s (s (0) ,s (0) ,s ( s (0) 1 ) 7 means checking whether 1 + 1 = 2.
(We feel free to use the more readable decimal notation when mentioning
Proposition numbers.) As for ,I the program for p l u s is not efficient. The proof tree
Programs 3.1 and 3.3 constitute a correct and complete axiomatization confirming that the sum of N and M is N + M has N + M + 2 nodes.
of addition with respect to the standard intended meaning of p l u s / 3 . Posing the query p l u s ( s (0) ,s (0) ,X) ?, an example of the standard
use, calculates the sum of 1 and 1. However, the program can just as eas-
Proof (1) Completeness. Let X, Y, and z be natural numbers such that ily be used for subtraction by posing a query such as p l u s ( s (0) , x , s ( s
X+Y=Z. We give a proof tree for the goal p l u s (X ,Y,Z) . If X equals 0, then ( s ( 0 ) ) ) )?. The computed value of X is the difference between 3 and 1,
Y equals Z. Since Program 3.1 is a complete axiomatization of the natural namely, 2. Similarly, asking a query with the first argument uninstanti-
numbers, there is a proof tree for n a t u r a l - n m b e r ( Y ) , which is easily ated, and the second and t h r d instantiated, also performs subtraction.
extended to a proof tree for p l u s ( 0 , Y , Y ). Otherwise. X equals s n (0) for A more novel use exploits the possibility of a query having multiple so-
some n. If Y equals sm(O), then z equals ~ " ' ~ ( 0 )The . proof tree in the lutions. Consider the query p l u s (X, Y , s ( s ( s (0) ) ) ) ?. It reads: "Do there )
right half of Figure 3.1 establishes completeness.
Chapter 3 Recursive Programming

exist numbers X and Y that add up to 3." In other words, find a partition times ( X , Y,Z) -
of the number 3 into the sum of two numbers, X and Y. There are several X, Y,and Z are natural numbers
such that Z is the product of X and Y.
solutions.
A query with multiple solutions becomes more interesting when the
properties of the variables in the query are restricted. There are two
times(O,X,O).
times(s(X),Y,Z) - times(X,Y,XY), plus(XY,Y,Z).

forms of restriction: using extra conjuncts in the query, and instanti- plus(X,Y ,Z) - See Program 3 . 3 .
ating variables in the query. We saw examples of t h s when querylng a Program 3.4 Multiplication as repeated addition
database. Exercise (ii) at the end of t h s section requires to define a pred-
icate even(X) , whch is true if X is an even number. Assuming such a
predicate, the query plus (X ,Y ,N) ,even (X) ,even (Y)? gives a partition
of N into two even numbers. The second type of restriction is exemplified
exp(N,X,Y ) -
N, X, and Y are natural numbers
by the query plus (s (s (x)) ,s (s(Y) ) ,N)7 , which insists that each of the such that Y equals X raised to the power N .
numbers adding up to N is strictly greater than 1. exp(s(X) ,0,0).
Almost all logic programs have multiple uses. Consider Program 3.2 exp(O,s(X) ,s(O)).
for I, for example. The query s (0) 2 s (s (0)) ? checks whether 1 is less exp(s(N),X,Y) -
exp(N,X,Z), times(Z,X,Y).
than or equal to 2. The query X I s (s (0) ) ? finds numbers X less than times(X,Y,Z) - See Program 3.4 .
or equal to 2. The query x IY? computes pairs of numbers less than or
equal to each other. Program 3.5 Exponentiation as repeated multiplication
Program 3.3 defining addition is not unique. For example, the logic
program
as 0 i a and plus (0,a, a), where a is an arbitrary constant, will be
plus (x,0,X) -- natural-number ()o .
plus(X,s(Y) ,s(Z)) -
plus(X,Y,Z).
in the programs' meanings. Type conditions are necessary for correct
programs. However, type conditions distract from the simplicity of the
has precisely the same meaning as Program 3.3 for plus. Two programs programs and affect the size of the proof trees. Hence in the following
are to be expected because of the symmetry between the first two argu- we might omit explicit type conditions from the example programs, Pro-
ments. A proof of correctness and completeness given for Program 3.3 grams 3.4-3.7.
applies to this program by reversing the roles of the symmetric argu- The basic programs shown are the building blocks for more compli-
ments. cated relations. A typical example is defining multiplication as repeated
The meaning of the program for plus would not change even if it addition. Program 3.4 reflects this relation. The relation scheme is
consisted of the two programs combined. T h s composite program is un- times (X ,Y, Z) , meaning X times Y equals Z.
desirable, however. There are several different proof trees for the same Exponentiation is defined as repeated multiplication. Program 3.5 for
goal. It is important both for runtime efficiency and for textual concise- exp(N,X, Y) expresses the relation that xN=y. It is analogous to Pro-
ness that axiomatizations of logic programs be minimal. gram 3.4 for times (X,Y ,Z), with exp and times replacing times and
We define a type condition to be a call to the predicate defining the plus, respectively. The base cases for exponentiation are xO=l for all pos-
type. For natural numbers, a type condition is any goal of the form itive values of X, and oN=O for positive values of N.
natural-number (X). A definition of the factorial function uses the definition of multiplica-
In practice, both Programs 3.2 and 3.3 are simplified by omitting the tion. Recall that N! = N . N - 1 . . . . . 2 1. The predicate factorial(N,F)
body of the base rule, natural-number(X). Without t h s test, facts such relates a number N to its factorial F. Program 3.6 is its axiomatization.
Chapter 3 Recursive Programming

factorial (N,F) - mod(X,Y,Z) -


F equals N factorial. Z is the remainder of the integer division of X by Y.
mod(X,Y,Z) - Z < Y , times(Y,Q,QY), plus(QY,Z,X).
Program 3.8a A nonrecursive definition of modulus
times(X,Y,z) - See Program 3.4
Program 3.6 Computing factorials mod(X,Y,Z) -
Z is the remainder of the integer division of X by Y

minimum(Nl,NZ,Min) - mod(X,Y,X)
mod(X,Y,Z)
-
-
X < Y.
plus(Xl,Y,X), m o d ( X l , Y , Z ) .
The minimum of the natural numbers N1 and N2 is Min.
Program 3.8b A recursive definition of modulus

NI 5 N 2 - See Program 3.2 . In contrast to Program 3.8a. Program 3.8b is defined recursively. It con-
Program 3.7 The minimum of two numbers stitutes an algorithm for finding the integer remainder based on repeated
subtraction. The first rule says that X mod Y is X if X is strictly less than
Y. The second rule says that the value of X mod Y is the same as X - Y
Not all relations concerning natural numbers are defined recursively. mod Y. The effect of any computation to determine the modulus is to re-
Relations can also be defined in the style of programs in Chapter 2. An peatedly subtract Y from X until it becomes less than Y and hence is the
example is Program 3.7 determining the minimum of two numbers via correct value.
the relation minimum(N1, N2 ,Min) . The mathematical function X mod Y is not defined when Y is zero. Nei-
Composing a program to determine the remainder after integer divi- ther Program 3.8a nor Program 3.8b has goal mod (X ,0 ,Z) in its meaning
sion reveals an interesting phenomenon-different mathematical defini- for any values of X or Z. The test of < guarantees that.
tions of the same concept are translated into different logic programs. The computational model gives a way of distinguislung between the
Programs 3.8a and 3.8b give two definitions of the relation mod(~,Y,Z), two programs for mod. Given a particular X, Y, and Z satisfflng mod,
which is true if Z is the value of X modulo Y, or in other words. Z is the re- we can compare the sizes of their proof trees. In general, proof trees
mainder of X divided by Y. The programs assume a relation < as specified produced with Program 3.8b will be smaller than those produced with
in Exercise (i) at the end of this section. Program 3.8a. In that sense Program 3.8b is more efficient. We defer more
Program 3.8a illustrates the direct translation of a mathematical defi- rigorous discussions of efficiency till the discussions on lists, where the
nition, which is a logical statement, into a logic program. The program insights gained will carry over to Prolog programs.
corresponds to an existential definition of the integer remainder: "Z is Another example of translating a mathematical definition directly into
the value of X mod Y if Z is strictly less than Y, and there exists a num- a logic program is writing a program that defines Ackermann's function.
ber Q such that X = Q . Y + Z. In general, mathematical definitions are Ackermann's function is the simplest example of a recursive function
easily translated to logic programs. that is not primitive recursive. It is a function of two arguments, defined
We can relate Program 3.8a to constructive mathematics. Although by three cases:
seemingly an existential definition, it is also constructive, because of the ackermann(0,N ) = N + 1.
constructive nature of <, plus, and times. The number Q,for example.
ackermann(M,0 ) = ackermann(M - 1 , l ) .
proposed in the definition will be explicitly computed by times in any
use of mod. ackermann(M,N ) = ackermann(M - 1 , ackermann(M,N - 1)).
Recursive Programming
Chapter 3

of the above mathematical statement about greatest common divisors.


ackermann(X,Y,A) - The proof that the Euclidean algorithm is correct similarly rests on t h s
A is the value of Ackermann's
function for the natural numbers X and E' result.
The second fact in Program 3.10 is the base fact. It must be specified
ackerrnann(O,N,s(N)).
ackermann(s(M) ,O ,Val) -- ackermann(M,~ ( 0,Val)
) . that X is greater than 0 to preclude gcd(0,0,0) from being in the mean-
ing. The gcd of 0 and 0 is not well defined.
ackermann(s (M) ,s(N) ,Val)
ackermann(s (M) ,N ,Vall) , ackermann(M ,Val1 ,Val).

Program 3.9 Ackermann's function 3.1.1 Exercises for Section 3.1

(i) Modify Program 3.2 for to axiomatize the relations <, >, and r .
gcd(X,Y,Z) - Discuss multiple uses of these programs.
Z is the greatest common divisor of
the natural numbers X and Y. (ii) Prove that Program 3.2 is a correct and complete axiomatization of
1.

(iii) Prove that a proof tree for the query sn ( 0 ) a sm (01 using Pro-
Program 3.10 The Euclidean algorithm gram 3.2 has m + 2 nodes.
(iv) Define predicates even(X) and odd(X) for determining if a natural
number is even or odd. (Hint: Modify Program 3.1 for natural-
Program 3.9 is a translation of the functional definition into a logic pro- number.)
gram. The predicate ackermann(M ,N ,A) denotes that ~=ackermann(M,N ) .
The third rule invol~~es two calls to Ackermann's function, one to com- (v) Write a logic program defining the relation fib(N,F) to determine
pute the value of the second argument. the Nth Fibonacci number F.
The functional definition of Ackermann's function is clearer than the (vi) The predicate times can be used for computing exact quotients
relational one given in Program 3.9. In general, functional notation is with queries such as times (s (s (0)) ,X,s (s (s (s ( 0 ) ) I ) ? to find
more readable for pure functional definitions, such as Ackermann's the result of 4 divided by 2. The query times (s (s (0)) ,x ,s (s(s
function and the factorial function (Program 3.6). Expressing constraints (0) 1 ) I ? to find 3i2 has no solution. Many applications require the
can also be awkward with relational logic programs. For example, Pro- use of integer division that would calculate 312 to be 1. Write a
gram 3.8a says less directly that X = Q . Y + Z . program to compute integer quotients. (Hint: Use repeated subtrac-
The final example in this section is the Euclidean algorithm for finding tion.)
the greatest common divisor of two natural numbers. recast as a logic
program. Like Program 3.8b, it is a recursive program not based on the (vii) Modify Program 3.10 for finding the gcd of two integers so that
recursive structure of numbers. The relation scheme is gcd(X, Y , Z) , with it performs repeated subtraction directly rather than use the mod
intended meaning that z is the greatest common divisor (or gcd) of two function. (Hint: The program repeatedly subtracts the smaller num-
natural numbers X and Y. It uses either of the two programs, 3.8a or 3.8b, ber from the larger number until the two numbers are equal.)
for mod. (viii) Rewrite the logic programs in Section 3.1 using a different represen-
The first rule in Program 3.10 is the logical essence of the Euclidean tation of natural numbers, namely as a sum of 1's. For example, the
algorithm. The gcd of X and Y is the same as the gcd of Y and X mod modified version of Program 3.1 would be
Y. A proof that Program 3.10 is correct depends on the correctness
Recursive Programming
Chapter 3

natural-number (1) . Formal object Cons pair syntax Element syntax


natural-number ( l+X) -- natural-number (X) .
.(a,[I) [all 11 [a1
Note that + is used as a binary operator, and 0 is not defined to be .(a,.(b,[1)) [alIbl[ 111 lab1
a natural number. . I [al[bl[c I[ 1111 [a,b,cl
.(a,X) [al XI [al XI
.(a,.(b,X)) Ial[blXll [a,blXl
3.2 Lists
Figure 3.2 Equivalent forms of lists
The basic structure for arithmetic is the unary successor functor. Al-
though complicated recursive functions such as Ackermann's function
can be defined, the use of a unary recursive structure is limited. This sec- list(Xs) -
Xs is a list.
tion discusses the binary structure, the list.
The first argument of a list holds an element, and the second argument list([ I).
is recursively the rest of the list. Lists are sufficient for most computa- - l i s t ( X s ).
l i s t ( CX l Xsl )
tions - attested to by the success of the programming language Lisp, Program 3.1 1 Defining a list
whlch has lists as its basic compound data structure. Arbitrarily complex
structures can be represented with lists, though it is more convenient to
use different structures when appropriate.
Terms built with the dot functor are more general than lists. Program
For lists, as for numbers, a constant symbol is necessary to terminate
3.11 defines a list precisely. Declaratively it reads: "A list is either the
recursion. This "empty list," referred to as nil, will be denoted here by
empty list or a cons pair whose tail is a list." The program is analogous to
the symbol [ 1. We also need a functor of arity 2. Historically, the usual
Program 3.1 defining natural numbers, and is the simple type definition
functor for lists is "." (pronounced dot), which overloads the use of the
of lists.
period. It is convenient to define a separate, special syntax. The term
Figure 3.3 gives a proof tree for the goal list ( [a,b, cl ). Implicit in the
. (X,Y) is denoted [XIYI . Its components have special names: X is called
proof tree are ground instances of rules in Program 3.11, for example,
the head and Y is called the tail.
The term [X/Ylcorresponds to a cons pair in Lisp. The corresponding
list ( [a,b, cl -
list ( [b,cl >.We specify the particular instance here
explicitly, as instances of lists in cons pair notation can be confusing.
words for head and tail are, respectively, car and cdr.
[a,b ,cl is an instance of [X l Xsl under the substitution {X=a,Xs= [b ,cl }.
Figure 3.2 illustrates the relation between lists written with different
Because lists are richer data structures than numbers, a great variety of
syntaxes. The first column writes lists with the dot functor, and is the
interesting relations can be specified with them. Perhaps the most basic
way lists are considered as terms in logic programs. The second column
operation with lists is determining whether a particular element is in
gives the square bracket equivalent of the dot syntax. The third column
a list. The predicate expressing tlvs relation is member (Element,List).
is an improvement upon the syntax of the second column, essentially
Program 3.12 is a recursive definition of member/2.
lvding the recursive structure of lists. In this syntax, lists are written
Declaratively, the reading of Program 3.12 is straightforward. X is an
as a sequence of elements enclosed in square brackets and separated
element of a list if it is the head of the list by the first clause, or if it
by commas. The empty list used to terminate the recursive structure is
is a member of the tail of the list by the second clause. The meaning
suppressed. Note the use of "cons pair notation" in the third column
of the program is the set of all ground instances member (X ,Xs) , where
when the list has a variable tail.
Chapter 3 Recursive Programming

prefix (Prefix,List) -
Prefix is a prefix of List.
p r e f i x ( I ,Ys) .
p r e f i x ( [XIXsl , [ X I Ysl-pref i x ( X s , Y s ) .
suffix (Suffix,List) -
Suffix is a suflix of List.

Program 3.13 Prefixes and suffixes of a list

Figure 3.3 Proof tree verifying a list Our next example is a predicate sublist (Sub,List) for determining
whether Sub is a sublist of List. A sublist needs the elements to be
consecutive: [b,cl is a sublist of [a,b, c ,dl,whereas [a,cl is not.
-
m e m b e r (Element,List)
Element is an element of the list List.
It is convenient to define two special cases of sublists to make the defi-
nition of sublist easier. It is good style when composing logic programs
member ( X , [ X I Xsl ) . to define meaningful relations as auxiliary predicates. The two cases con-
-
member ( X , [Y I Ysl ) member ( X ,Ys) . sidered are initial sublists, or prefixes, of a list, and terminal sublists, or
suffxes, of a list. The programs are interesting in their own right.
Program 3.12 Membership of a list
The predicate prefix (Prefix ,List) is true if Prefix is an initial sub-
list of List, for example, prefix ( [a,bl , [a,b, cl ) is true. The compan-
X is an element of XS. We omit the type condition in the first clause. ion predicate to prefix is suffix (Suff ix,List), determining if Suffix
Alternatively, it would be written is a terminal sublist of List. For example, suffix ( [b,cl , [a,b, cl ) is
true. Both predicates are defined in Program 3.13. A type condition ex-
member (X, [X I XS] ) - list (Xs) . pressing that the variables in the base facts are lists should be added to
the base fact in each predicate to give the correct meaning.
This program has many interesting applications, to be revealed
An arbitrary sublist can be specified in terms of prefixes and suffixes:
throughout the book. Its basic uses are checlung whether an element namely, as a suffix of a prefix, or as a prefix of a suffix. Program 3.14a
is in a list with a query such as member (b, [a,b, cl ) ?, finding an ele- expresses the logical rule that Xs is a sublist of Ys if there exists Ps such
ment of a list with a query such as member (X, [a,b, cl ) ?, and finding a that Ps is a prefix of Ys and Xs is a suffix of Ps. Program 3.14b is the dual
list containing an element with a query such as member (b,X)?. Thls last definition of a sublist as a prefix of a suffuc.
query may seem strange, but there are programs that are based on t h s The predicate prefix can also be used as the basis of a recursive
use of member. definition of sublist. Thls is given as Program 3.14~.The base rule reads
We use the following conventions wherever possible when naming vari- that a prefix of a list is a sublist of a list. The recursive rule reads that the
ables in programs involving lists. If X is used to denote the head of a sublist of a tail of a list is a sublist of the list itself.
list, then Xs will denote its tail. More generally, plural variable names will The predicate member can be viewed as a special case of sublist de-
denote lists of elements, and singular names will denote individual ele fined by the rule
ments. Numerical suffxes will denote variants of lists. Relation schemes
will still contain mnemonic names. member (X,Xs) - sublist ( [XI ,Xs) .
Recursive P r o g r a m m i n g
Chapter 3

sublist (Sub,List) -
Sub is a sublist of List.
a: Suffix of a prefix
sublist(Xs ,Ys) - pref ix(Ps ,YS), suff ix(Xs,Ps).
b: Prefuc of a suffix
sublist (Xs ,Ys) - pref ix(Xs ,ss), suffix(Ss,Ys).
c: Recursive definition of a sublist
sublist (Xs ,Ys) - - pref ix(xs,Ys).
sublist(Xs, [Y ~ Y s ] ) sublist ( X S,YS). Figure 3.4 Proof tree for appending two lists
d: Prefix of a suffix, using append
sublist (Xs ,AsXsBs) - as append(Ca,b,cl, [d,el ,Xs)? with answer Xs=[a,b,c,d,el. A query
append(As ,XsBs ,A s ~ s B s ), append(Xs ,Bs ,XSBS) . >
such as append(Xs, [c, dl , [a,b, c ,dl ? finds the difference Xs= [a,b]
e: Suffuc of a prefix, using append between the lists Cc, dl and [a,b, c ,dl.Unlike plus, append is not sym-
sublist(Xs,AsXsBs) - metric in its first two arguments, and thus there are two distinct versions
append(AsXs ,Bs,AsXsBs) , append(As ,Xs ,ASXS). of finding the difference between two lists.
The analogous process to partitioning a number is splitting a list. The
Program 3.14 Determining sublists of lists
query append(As ,Bs , [a,b, c ,dl ) ?, for example, asks for lists As and Bs
such that appending Bs to As gives the list [a,b,c,dl. Queries about
a p p e n d (Xs,Ys,XsYs) -
X s Y s is the result of concatenating
splitting lists are made more interesting by partially specifying the na-
ture of the split lists. The predicates member, sublist, prefix, and suf -
the lists X s and Ys. fix, introduced previously, can all be defined in terms of append by
append( [ 1 ,Ys ,Ys). viewing the process as splitting a list.
append( [XI Xsl ,Ys,[XI Zsl ) - append(Xs ,Ys ,Zs) The most straightforward definitions are for prefix and suffix,whlch
Program 3.1 5 Appending two lists just specify which of the two split pieces are of interest:

ref ix(Xs, Ys) - append(Xs, As, Ys) .


The basic operation with lists is concatenating two lists to give a t h r d suff ix(Xs,Ys) - append(As,Xs,Ys) .
list. This defines a relation, append(Xs ,Ys ,Zs), between two lists Xs, Ys Sublist can be written using two append goals. There are two distinct
and the result Zs of joining them together. The code for append, Pro- variants, given as Programs 3.14d and 3.14e. These two programs are
gram 3.15, is identical in structure to the basic program for combining obtained from Programs 3.14a and 3.14b, respectively, where prefix and
two numbers, Program 3.3 for plus. suffix are replaced by append goals.
Figure 3.4 gives a proof tree for the goal append ( [a,bl , LC, dl , [a,b, Member can be defined using append, as follows:
c ,dl ) . The tree structure suggests that its size is linear in the size of
the first list. In general, if Xs is a list of n elements, the proof tree for
append(Xs ,Ys,Zs) has n + 1 nodes.
Thls says that X is a member of Ys if Ys can be split into two lists where
There are multiple uses for append similar to the multiple uses for
X is the head of the second list.
plus. The basic use is to concatenate two lists by posing a query such
Chapter 3 Recursive Programming

-
reverse( List, Tsil)
Tsil is the result of reversing the list List.
a: Naive reverse
reverse([ I,[ I ) .
reverse( [XI Xs] ,Zs) - reverse(Xs ,Ys) , append(ls, [XI , Z S )
b: Reverse-accumulate
reverse(Xs ,Ys) - reverse(Xs, C 1 ,Ys).
reverse( [XIXsl ,Acc,Ys) - reverse(Xs, [XIACC] ,YS).
reverse([ I , Y s , Y s ) .

Program 3.16 Reversing a Iist

A similar rule can be written to express the relation adjacent (X ,Y,Zs)


that two elements X and Y are adjacent in a list Zs:

Another relation easily expressed through append is determining the


last element of a list. The desired pattern of the second argument to
append, a list with one element, is built into the rule:

Repeated applications of append can be used to define a predicate


reverse (List ,Tsil).The intended meaning of reverse is that Tsil is a
list containing the elements in the list List in reverse order to how they
appear in List. An example of a goal in the meaning of the program is
reverse ( [a,b ,c] , [c ,b ,a] ) . The naive version, given as Program 3.16a,
is the logical equivalent of the recursive formulation in any language:
recursively reverse the tail of the list, and then add the first element at
the back of the reversed tail.
There is an alternative way of defining reverse without calling append Figure 3.5 Proof trees for reversing a list
directly. We define an auxiliary predicate reverse (Xs ,Ys ,Zs) , whlch is
true if Zs is the result of appending Ys to the elements of Xs reversed.
It is defined in Program 3.16b. The predicate reverse/3 is related to
reverse/2 by the first clause in Program 3.16b.
Program 3.16b is more efficient than Program 3.16a. Consider Fig-
ure 3.5, showing proof trees for the goal reverse ( [a,b ,cl , [c ,b ,a1 > us-
ing both programs. In general, the size of the proof tree of Program 3.16a
Chapter 3 Recursive Programming

lengrh(Xs,N) -
The list Xs has N elements.
(ii) Write recursive programs for adjacent and last that have the
same meaning as the predicates defined in the text in terms of
append.

(iii) Write a program for double (List,ListList) , where every element


Program 3.17 Determining the length of a list in List appears twice in ListList, e.g., double( [I ,2,31 , [ I , I ,2,
2,3,3]) is true.
is quadratic in the number of elements in the list to be reversed, whlle
(iv) Compute the size of the proof tree as a function of the size of the
that of Program 3.16b is linear. input list for Programs 3.16a and 3.16b defining reverse.
The insight in Program 3.16b is the use of a better data structure for
representing the sequence of elements, which we discuss in more detail (v) Define the relation sum(List0f Integers,Sum), whlch holds if Sum
in Chapters 7 and 15. is the sum of the ListOf Integers,
The final program in this section, Program 3.17, expresses a rela-
tion between numbers and lists, using the recursive structure of each. (a) Using plus/3;
The predicate length(Xs,N) is true if Xs is a list of length N, that (b) Without using any auxiliary predicate.
is, contains N elements, where N is a natural number. For example,
length( [a,b] ,s (s (0)) ) , indicating that [a,b] has two elements, is in (Hint: Three axioms are enough.)
the program's meaning.
Let us consider the multiple uses of Program 3.17. The query length
([a,bl ,X)? computes the length, 2, of a list [a,b]. In t h s way, length 3.3 Composing Recursive Programs
is regarded as a function of a list, with the functional definition
length( [ 1 ) = 0 No explanation has been given so far about how the example logic pro-
length( [XI Xsl ) = s (length(Xs) ) . grams have been composed. The composition of logic programs is a slull
that can be learned by apprenticeshp or osmosis, and most definitely by
The query length ( [a,bl ,s (s (0)) ) ? checks whether the list [a,bl has practice. For simple relations, the best axiomatizations have an aesthetic
length 2. The query length (xs ,s (s (0) ) ) ? generates a list of length 2 elegance that look obviously correct when written down. Through solv-
with variables for elements. ing the exercises, the reader may find, however, that there is a difference
between recognizing and constructing elegant logic programs.
3.2.1 Exercises for Section 3.2 Thls section gives more example programs involving lists. Their, pre-
sentation, however, places more emphasis on how the programs might be
(i) A variant of Program 3.14 for sublist is defined by the following composed. Two principles are illustrated: how to blend procedural and
three rules: declarative thinlung, and how to develop a program top-down.
subsequence ( [X I Xs] , [X I Ys] ) -- subsequence (Xs ,YS) . We have shown the dual reading of clauses: declarative and procedural.
subsequence (Xs , [Y I Ys] ) - subsequence (XS ,YS) . How do they interrelate when composing logic programs? Pragmatically,
one thinks procedurally when programming. However, one thinks declar-
subsequence ( [ I ,Ys) .
atively when considering issues of truth and meaning. One way to blend
Explain why this program has a different meaning from Pro- them in logic programming is to compose procedurally and then inter-
gram 3.14. pret the result as a declarative statement. Construct a program with a
Chapter 3 Recursive Programming

given use in mind; then consider if the alternative uses make declarative delete(List,X,HasNoXs) -
The list HasNoXs is the result of removing all
sense. We apply this to a program for deleting elements from a list. occurrences of X from the list List.
The first, and most important, step is to specify the intended meaning
of the relation. Clearly, three arguments are involved when deleting ele-
ments from a list: an element X to be deleted, a list L1 that might have
occurrences of X, and a list L2 with all occurrences of X deleted. An ap-
propriate relation scheme is delete (L1,X,L 2 ) . The natural meaning is Program 3.18 Deleting all occurrences of an element from a list
all ground instances where L2 is the list L1 with all occurrences of X re-
moved.
When composing the program, it is easiest to think of one specific
use. Consider the query delete ( [a,b , c ,b] ,b ,X) ?, a typical example of
select (X,HasXs,OneLessXs) -
The list OneLessXs is the result of removing
finding the result of deleting an element from a list. The answer here is one occurrence of X from the list HasXs.
X= [a,CI . The program will be recursive on the first argument. Let's don select (X, [XI Xsl ,Xs) .
our procedural thinking caps. -
select (X, [Y I Ysl , [Y I Zsl) select (X,Ys,Zs).
We begin with the recursive part. The usual form of the recursive ar- Program 3.19 Selecting an element from a list
gument for lists is [XJXs].There are two possibilities to consider, one
where X is the element to be deleted, and one where it is not. In the first
case, the result of recursively deleting X from Xs is the desired answer to
the query. The appropriate rule is fact delete( C I ,X, C 1). The complete program is collected together as
Program 3.18.
delete ( [XI Xs] ,X,Ys) - delete (Xs , X 2Ys). Let us review the program h7ehave written, and consider alternative
Switching hats, the declarative reading of this rule is: "The deletion of formulations. Omitting the condition Xf Z from the second rule in Pro-
X from [XIXsl is Ys if the deletion of X from Xs is Ys." The condition gram 3.18 gives a variant of delete. This variant has a less natural mean-
that the head of the list and the element to be deleted are the same is ing, since any number of occurrences of an element may be deleted. For
specified by the shared variable in the head of the rule. example, delete ( [a,b, c,bl ,b, [a,cl 1, delete ( [a,b, c,bl ,b, [a,c,
The second case where the element to be deleted is different from X, bl), delete([a,b,c,bl ,b, [a,b,cl), and delete([a,b,c,bl ,b,[a,b,
the head of the list, is similar. The result required is a list whose head c ,b] ) are all in the meaning of the variant.
is X and whose tail is the result of recursively deleting the element. The Both Program 3.18 and the variant include in their meaning instances
rule is where the element to be deleted does not appear in either list, for ex-
>
ample, delete ( Cal ,b, [a1 is true. There are applications where thls is
not desired. Program 3.19 defines select (X,LI,L2), a relation that has
The rule's declarative reading is: "The deletion of Z from CXlXsl is a different approach to elements not appearing in the list. The meaning
CXIYsl if Z is different from X and the deletion of Z from Xs is Ys." In of select (X,L1 ,L2) is all ground instances where L2 is the list L1 where
contrast to the previous rule, the condition that the head of the list and exactly one occurrence of X has been removed. The declarative reading
the element to be deleted are different is made explicit in the body of the of Program 3.19 is: "X is selected from [XIXsl to give Xs; or X is selected
rule. from [YIYsl to give [YIZsl if X is selected from Ys to give Zs."
The base case is straightforward. No elements can be deleted from the A major thrust in programming has been the emphasis on a top-down
empty list, and the required result is also the empty list. This gives the design methodology, together with stepwise refinement. Loosely, the
Chapter 3 Recursive P r o g r a m m i n g

methodology is to state the general problem, break it down into subprob- sort (Xs,Ys) -
The list Ys is an ordered permutation of the list Xs.
lems, and then solve the pieces. A top-down programming style is one
natural way for composing logic programs. Our description of programs sort(Xs,Ys) permutation(Xs,Ys), ordered(Ys).
-
+-

throughout the book will be mostly top-down. The rest of t h s section de- permutation(Xs,[ZIZsl) select(Z,Xs,Ys), permutation(Ys,Zs)
scribes the composition of two programs for sorting a list: permutation permutation( [ I , [ I ) .
sort and quicksort. Their top-down development is stressed. ordered( [ I ) .
ordered( [XI 1.
A logical specification of sorting a list is finding an ordered permuta-
tion of a list. T h s can be written down immediately as a logic program. ordered([X,YIYsl) - X 5 Y, ordered([YIYsl).

The basic relation scheme is sort (Xs ,Ys), where Ys is a list containing Program 3.20 Permutation sort
the elements in Xs sorted in ascending order:
sort (Xs ,Ys) -- permutation(Xs ,Ys) , ordered(Ys) The predicate insert can be defined in terms of Program 3.19 for se-
lect:
The top-level goal of sorting has been decomposed. We must now define
permutation and ordered. insert (X ,Ys ,ZS) -- select (X,Zs,Ys) .
Testing whether a list is ordered ascendingly can be expressed in the
two clauses that follow. The fact says that a list with a single element Both procedural versions of permutation have clear declarative read-
is necessarily ordered. The rule says that a list is ordered if the first ings.
element is less than or equal to the second, and if the rest of the list, The "naive" sorting program, which we call permutation sort, is col-
beginning from the second element, is ordered: lected together as Program 3.20. It is an example of the generate-and-test
paradigm, discussed fully in Chapter 14. Note the addition of the extra
ordered ( [XI ) . base case for ordered so that the program behaves correctly for empty
ordered([X,YIYs]) -X IY, ordered([YIYsl). lists.
The problem of sorting lists is well studied. Permutation sort is not a
A program for permutation is more delicate. One view of the process
good method for sorting lists in practice. Much better algorithms come
of permuting a list is selecting an element nondeterministically to be the
from applying a "divide and conquer" strategy to the task of sorting. The
first element of the permuted list, then recursively permuting the rest
insight is to sort a list by dividing it into two pieces, recursively sorting
of the list. We translate this view into a logic program for permutation,
the pieces, and then joining the two pieces together to give the sorted
using Program 3.19 for select. The base fact says that the empty list is
list. The methods for dividing and joining the lists must be specified.
its own unique permutation:
There are two extreme positions. The first is to make the dividing hard,
permutation(Xs, [Z I Zs] ) -- select (Z,XS ,Ys), permutation(~s,~s). and the joining easy. Thls approach is taken by the quicksort algorithm.
permutation( C I , C 1 ) . The second position is malung the joining hard, but the dividing easy.
T h s is the approach of merge sort, which is posed as Exercise (v) at the
Another procedural view of generating permutations of lists is recur- end of t h s section, and insertion sort, shown in Program 3.21.
sively permuting the tail of the list and inserting the head in an arbitrary In insertion sort, one element (typically the first) is removed from the
position. T h s view also can be encoded immediately. The base part is list. The rest of the list is sorted recursively; then the element is inserted,
identical to the previous version: preserving the orderedness of the list.
The insight in quicksort is to divide the list by choosing an arbitrary
element in it, and then to split the list into the elements smaller than the
Chapter 3 Recursive Programming

sort (Xs,Ys) -
The list Ys is an ordered permutation of the list Xs.
lists [XILittlesl and Bigs if X is less than or equal to Y, and partitioning
Xs according to Y gives the lists Littles and Bigs." The second clause
sort( [XlXsl ,Ys)
sort([ I,[ I).
- sort (Xs,Zs), insert (X,ZS,YS). for partition has a similar reading. The base case is that the empty list
is partitioned into two empty lists.
insert(X,[ ],[XI).
-
insert (X, [Y I Ysl , [Y I Zsl ) X > Y, insert (X ,YS,2s).
insert(X,[YIYsl,[~,~I~s]) X 5 Y. - 3.3.1 Exercises for Section 3.3
Program 3.21 Insertion sort
(i) Write a program for substitute(X,Y,LI,L2), where L 2 is the
result of substituting Y for all occurrences of X in Li, e.g., sub-
quicksort (Xs,Ys) - stitute (a,x, [a,b, a, cl , [x,b,x, cl ) is true, whereas substi-
The list Ys is an ordered permutation of the list Xs. tute(a,x, [a,b,a,cl, [a,b,x,cl) is false.
quicksort ( [X I Xs] ,Ys) - (ii) What is the meaning of the variant of select:
partition(Xs,X,Littles,Bigs),
quicksort (Littles ,Ls) ,
quicksort (Bigs ,Bs) , select (X, [XI Xsl ,Xs) .
append(Ls, [XIBs] ,Ys).
quicksort ( [ 1 , [ 1 ) .
-
select(X, [ Y ~ Y s l , ~ Y l Z s l ) X f Y,
select (X,Ys, Zs) .
partition([XIXs] ,Y,[XILs] ,Bs)
partition([XIXs] ,Y,Ls,[X(BsJ)
-- X 5 Y, p a r t i t i o n ( ~ s , ~ , ~ ~ , ~ ~ ) .
X > Y, partition(~s,~,~s,B~).
(iii) Write a program for no-doubles (Ll ,L2), where L2 is the result of
partition([ l,Y,[ I , [ 1 ) . removing all duplicate elements from L1, e.g., no-doubles ( [a,b, c ,
bl , [a,c ,bl ) is true. (Hint: Use member.)
Program 3.22 Quicksort
(iv) Write programs for even-permutation (Xs ,Ys) and odd-permuta-
tion(Xs,Ys) that find Ys, the even and odd permutations, respec-
chosen element and the elements larger than the chosen element. The tively, of a list Xs. For example, even-permutat ion ( [I,2,31, [2,3,
sorted list is composed of the smaller elements, followed by the chosen 11 ) and odd-permutation( [I,2,31, [2,I,31 ) are true.
element, and then the larger elements. The program we describe chooses
the first element of the list as the basis of partition. (v) Write a program for merge sort.
Program 3.22 defines the quicksort algorithm. The recursive rule for (vi) Write a logic program for kth-largest (Xs,K) that implements the
quicksort reads: "Ys is a sorted version of [XIXsl if Littles and Bigs linear algorithm for finding the kth largest element K of a list XS.
are a result of partitioning Xs according to X; Ls and Bs are the result of The algorithm has the following steps:
sorting Littles and Bigs recursively; and Ys is the result of appending
[XIBsl to Ls." Break the list into groups of five elements.
Partitioning a list is straightforward, and is similar to the program for Efficiently find the median of each of the groups, which can be done
deleting elements. There are two cases to consider: when the current with a fixed number of comparisons.
head of the list is smaller than the element being used for the parti- Recursively find the median of the medians.
tioning, and when the head is larger than the partitioning element. The Partition the original list with respect to the median of medians.
declarative reading of the first partition clause is: "Partitioning a list Recursively find the kth largest element in the appropriate smaller
whose head is X and whose tail is Xs according to an element Y gives the list.
Chapter 3 Recursive P r o g r a m m i n g

(vii) Write a program for the relation better-poker-hand(Hand1,


Hand2 ,Hand) that succeeds if Hand is the better poker hand be-
tween Hand1 and Hand2. For those unfamiliar with this card game,
here are some rules of poker necessary for answering t h s exercise:
would be represented as
(a) The order of cards is 2, 3, 4, 5, 6, 7, 8, 9, 10, jack, queen, lung, tree (a,tree (b,void,void) ,tree (C ,void,void)) .
ace.
Logic programs manipulating binary trees are similar to those manip-
(b) Each hand consists of five cards. ulating lists. As with natural numbers and lists, we start with the type
definition of binary trees. It is given as Program 3.23. Note that the pro-
(c) The rank of hands in ascending order is no pairs < one pair <
gram is doubly recursive;that is, there are two goals in the body of the
two pairs < three of a lund < flush < straight < full house <
recursive rule with the same predicate as the head of the rule. T h s re-
four of a lund < straight flush.
sults from the doubly recursive nature of binary trees and will be seen
(d) Where two cards have the same rank, the hlgher denomination also in the rest of the programs of this section.
wins, for example, a pair of kings beats a pair of 7's. Let us write some tree-processing programs. Our first example tests
whether an element appears in a tree. The relation scheme is tree-
(Hints: (1) Represent a poker hand by a list of terms of the form member(E1ement ,Tree). The relation is true if Element is one of the
card(Suit,Value). For example a hand consisting of the 2 of nodes in the tree. Program 3.24 contains the definition. The declarative
clubs, the 5 of spades, the queen of hearts, the queen of dia- reading of the program is: "X is a member of a tree if it is the element at
monds, and the 7 of spades would be represented by the list [card the node (by the fact) or if it is a member of the left or right subtree (by
(clubs,2),card(spades, 5),card(hearts, queen),card(diamonds , the two recursive rules)."
queen) ,card(spades, 7 ) ] . (2) It may be helpful to define relations The two branches of a binary tree are distinguishable, but for many ap-
such as has-f lush(Hand), whch is true if all the cards in Hand are plications the distinction is not relevant. Consequently, a useful concept
of the same suit; has-full-house (Hand), whch is true if Hand has
three cards with the same value but in different suits, and the other
two cards have the same different value; and has-straight (Hand),
binary_tree( Tree) -
Tree is a binary tree.
which is true if Hand has cards with consecutive values. (3) The
number of cases to consider is reduced if the hand is first sorted.)

Program 3.23 Defining binary trees


3.4 Binary Trees

We next consider binary trees, another recursive data type. These struc-
t r e e - m e m b e r (Element,Tree)
Element is an element of
-the binary tree Tree.
tures have an important place in many algorithms.

--
tree-member(X,tree(X,Left,Right)).
Binary trees are represented by the ternary functor tree(Element, tree-member(X,tree(Y,Left,Right)) tree-member(X,Left).
Left ,Right), where Element is the element at the node, and Left and tree-member(X,tree(Y,Left,Right)) tree-member(X,Right).
Right are the left and right subtrees respectively. The empty tree is
represented by the atom void. For example, the tree Program 3.24 Testing tree membership
Recursive P r o g r a m m i n g
Chapter 3

substitute(X,Y,TreeX,T r e e Y ) -
The binary tree TreeY is the result of replacing all
occurrences of X in the binary tree T r e e X by Y .
substitute(X,Y,void,void).
substitute ( X ,Y ,tree (Node ,Left,Right),tree(Node1 ,Left 1 ,Right1)) -
replace(X,Y,Node,Nodel),
substitute(X,Y,Left,Leftl),
Figure 3.6 Comparing trees for isomorphism
substitute(X,Y,Right,Rightl).

isotree( Treel,Tree21
Treel and Tree2
-are isomorphc binary trees.
Program 3.26 Substituting for a term in a tree
isotree(void,void) .
isotree (tree(X,Lef tl ,Rightl),tree ( X,Left2,Right2)) -
isotree(Leftl,Left2), isotree(Right1,Right2).
-
isotree(tree(X,lef t l ,Rightl),tree( X, ~ etf2 , ~ i ~ h t 2 )
isotree(Leftl,~ight2),isotree(Right1,left2). The task in Exercise 3.3(i) is to write a program for substituting for el-
Program 3.25 Determining when trees are isomorphic ements in lists. An analogous program can be written for substituting
elements in binary trees. The predicate substitute (X, Y , OldTree ,
NewTree) is true if NewTree is the result of replacing all occurrences
is isomorphism, whch defines when unordered trees are essentially the of X by Y in OldTree. An axiomatization of substitute/4 is given as
same. Two binary trees TI and T2 are isomorphic if T2 can be obtained Program 3.26.
by reordering the branches of the subtrees of TI. Figure 3.6 shows three Many applications involving trees require access to the elements ap-
simple binary trees. The first two are isomorphc; the first and third are pearing as nodes. Central is the idea of a tree traversal, which is a se-
not. quence of the nodes of the tree in some predefined order. There are three
Isomorphsm is an equivalence relation with a simple recursive defini- possibilities for the linear order of traversal: preorder, where the value of
tion. Two empty trees are isomorphc. Otherwise, two trees are isomor- the node is first, then the nodes in the left subtree, followed by the nodes
p h if~ they have identical elements at the node and either both the left in the right subtree; inorder,where the left nodes come first followed by
subtrees and the right subtrees are isomorphic; or the left subtree of one the node itself and then the right nodes; and postorder, where the node
is isomorphc with the right subtree of the other and the two other sub- comes after the left and right subtrees.
trees are isomorphic. A definition of each of the three traversals is given in Program 3.27.
Program 3.25 defines a predicate isotree (Treel ,Tree21, whch is The recursive structure is identical; the only difference between the pro-
true if Treel and Tree2 are isomorphic. The predicate is symmetric in grams is the order in which the elements are composed by the various
its arguments. append goals.
Programs related to binary trees involve double recursion, one for each The final example in this section shows interesting manipulation of
branch of the tree. The double recursion can be manifest in two ways. trees. A binary tree satisfies the heap property if the value at each node
Programs can have two separate cases to consider, as in Program 3.24 for is at least as large as the value at its children (if they exist). Heaps, a class
tree-member. In contrast, Program 3.12 testing membershp of a list has of binary trees that satisfy the heap property, are a useful data structure
only one recursive case. Alternatively, the body of the recursive clause and can be used to implement priority queues efficiently.
has two recursive calls, as in each of the recursive rules for isotree in It is possible to heapify any binary tree containing values for which an
Program 3.2 5. ordering exists. That is, the values in the tree are moved around so that
Chapter 3 Recursive P r o g r a m m i n g

preorder ( Tree,Pre) -
Pre is a preorder traversal of the binary tree Tree.
preorder(tree(X ,L,R) ,Xs) -
preorder(L,L~), preorder(R,Rs) , append( [X ILs] ~ R s ~ x s )
preorder (void, [ 1) .
inorder ( Tree,In) -
In is an inorder traversal of the binary tree Tree.
inorder (tree(X ,L ,R) ,Xs) -
inorder (L ,Ls) , inorder (R ,Rs) , append (Ls, [X 1 Rsl ,Xs)
inorder (void, [ 1 ) .
postorder (Tree,Post -
Post is a postorder traversal of the binary tree Tree.
Figure 3.7 A binary tree and a heap that preserves the tree's shape

postorder(tree(X,L,R) ,Xs) - h e a p i b ( Tree,H e a p ) -


postorder(L,Ls),
The elements of the complete binary tree Tree have been adjusted
~ o s t o r d e r(R,Rs) ,
to form the binary tree H e a p , which has the same shape as Tree and
append(Rs , [XI ,Rsl) ,
satisfies the heap property that the value of each parent node is
append(Ls,Rsl,Xs).
greater than or equal to the values of its children.
postorder (void, [ 1) .
heapify(void,void).
Program 3.27 Traversals of a binary tree heapify(tree(X,L,R) ,Heap) -
heapify (L,HeapL), heapify (R,HeapR), adjust (X,HeapL,HeapR,Heap) .

the shape of the tree is preserved and the heap property is satisfied. An
adjust(X,HeapL,HeapR,tree(X,HeapL,HeapR))
greater (X ,HeapL) , greater (X,HeapR) .
-
example tree and its heapified equivalent are shown in Figure 3.7. adjust(X,tree(Xl,L,R),HeapR,tree(Xl,HeapL,HeapR)) -
An algorithm for heapifying the elements of a binary tree so that the X < XI, greater(Xl,HeapR), adjust(X,L,R,HeapL).
heap property is satisfied is easily stated recursively. Heapify the left and adjust(X,HeapL,tree(Xl,L,R),tree(Xl,HeapL,HeapR))
X < XI, greater(X1 ,HeapL) , adjust (X,L,R,HeapR) .
-
right subtrees so that they both satisfy the heap property and then ad-
greater (X ,void) .
just the element at the root appropriately. Program 3.28 embodies tlvs
algorithm. The relation h e a p i f y / 2 lays out the doubly recursive pro- greater(X,tree(Xl,L,R)) -X 2 XI.
gram structure, and a d j u s t (X,HeapL ,HeapR, Heap) produces the final Program 3.28 Adjusting a binary tree to satisfy the heap property
tree Heap satisfying the heap property from the root value X and the left
and right subtrees HeapL and HeapR satisfying the heap property.
There are three cases for a d j u s t / 4 depending on the values. If the root 3.4.1 Exercises for Section 3.4
value is larger than the root values of the left and right subtrees, then
the heap is t r e e (X ,HeapL ,HeapR). Tlvs is indicated in the first a d j u s t (i) Define a program for s u b t r e e (S ,TI, where S is a subtree of T.
clause in Program 3.28. The second clause handles the case where the
(ii) Define the relation sum-tree (Treeof I n t e g e r s , Sum), whch holds
root node in the left heap is larger than the root node and the root of the if Sum is the sum of the integer elements in TreeOf I n t e g e r s .
right heap. In that case, the adjustment proceeds recursively on the left
heap. The third clause handles the symmetric case where the root node (iii) Define the relation ordered(Tree0f I n t e g e r s ) , which holds if Tree
of the right heap is the largest. The code is simplified by relegating the is an ordered tree of integers, that is, for each node in the tree
concern whether the subtree is empty to the predicate g r e a t e r / 2 . the elements in the left subtree are smaller than the element in
Chapter 3 Recursive P r o g r a m m i n g

the node, and the elements in the right subtree are larger than
the element in the node. (Hint: Define two auxiliary relations,
polynomial (Expression,X) -
Expression is a polynomial in X.
ordered-lef t (X ,Tree) and ordered-right (X,Tree), which hold polynomial (X,X) .
if both Tree is ordered and X is larger (respectively, smaller) than polynomial(Term,X) +

the largest (smallest) node of Tree.) constant(Term).


polynomial(Terml+Term2,X) -
(iv) Define the relation tree-insert (X ,Tree,Treel), which holds if
Treel is an ordered tree resulting from inserting X into the ordered polynomial(Terml-Term2,X) -
polynomial(Terml,X), polynomial(Term2,X).

tree Tree. If X already occurs in Tree, then Tree and Treel are iden-
tical. (Hint: Four axioms suffice.)
polynomial(Terml*~erm2,X) -
polynomial(Terml,X), polynomial(Term2,X).

(v) Write a logic program for the relation path(X ,Tree,Path), where
polynomial(Terml/Term2,X) -
polynomial(Terml,X), polynomial(Term2,X).

Path is the path from the root of the tree Tree to X. polynomial(TermTN,X) -
polynomial(Terml,X), constant(Term2).

natural-number(N), polynomial(Term,X).

Program 3.29 Recognizing polynomials


3.5 Manipulating Symbolic Expressions

The logic programs illustrated so far in this chapter have manipulated


natural numbers, lists, and binary trees. The programming style is ap-
plicable more generally. This section gives four examples of recursive
programming - a program for defining polynomials, a program for sym- says that the sum Terml+Term2 is a polynomial in X if both Term1 and
bolic differentiation, a program for solving the Towers of Hanoi problem, Term2 are polynomials in X.
and a program for testing the satisfiability of Boolean formulae. Other conventions used in Program 3.29 are the use of the unary pred-
The first example is a program for recognizing polynomials in some icate constant for recognizing constants, and the binary functor t to
term X. Polynomials are defined inductively. X itself is a polynomial in denote exponentiation. The term X t Y denotes xY.
X, as is any constant. Sums, differences,and products of polynomials in The next example is a program for taking derivatives. The relation
X are polynomials in X. So too are polynomials raised to the power of a scheme is derivative(Expression,X,DifferentiatedExpression).
natural number, and the quotient of a polynomial by a constant. The intended meaning of derivative is that Diff erentiatedExpres-
An example of a polynomial in the term x is xZ- 3x + 2. Thls follows sion is the derivative of Expression with respect to X.
from its being the sum of the polynomials, x2- 3x and 2, where x Z- 3x As for Program 3.29 for recognizing polynomials, a logic program for
is recognized recursively. differentiation is just a collection of the relevant differentiation rules,
A logic program for recognizing polynomials is obtained by expressing written in the correct syntax. For example, the fact
the preceding informal rules in the correct form. Program 3.29 defines
the relation polynomial (Expression,X) , whch is true if Expression is
a polynomial in X. We give a declarative reading of two rules from the
program. expresses that the derivative of X with respect to itself is 1. The fact
The fact polynomial (X,X) says that a term X is a polynomial in itself.
The rule derivative (sin(X) ,X,cos (X)) .
Recursive Programming
Chapter 3
tification over functions, and is outside the scope of the logic programs
derivative(Expression,X,Differentiate~ssion
DifferentiatedExpression is the derivative of
- we have presented.
Nonetheless, a version of the chain rule is possible for each particular
Expression with respect to X.
function. For example, we give the rule for differentiating xN and sin(X):
derivative(X,X,S(o)).
derivative(XTs(N) ,x,s(N)*XTN)
derivative(sin(~),X,co~(X)).
derivative (UTs (N) ,X,s (N) *UfN*DU) -
derivative(U,X,DU).
derivative (cos (X) ,X,-sin(X)) .
derivative (sin(U), X, cos (U)*DU) -- derivative (U,X,DU)
derivative(etX,X,etX).
derivative(log(X) ,X,1/X) . The difficulty of expressing the chain rule for differentiation arises
derivative(F+G,X,DF+DG) -
derivative (F ,X ,DF) , derivative (G ,X ,DG) .
from our choice of representation of terms. Both Programs 3.29 and
3.30 use the "natural" representation from mathematics where terms
derivative(F-G,X,DF-DG) +

represent themselves. A term such as sin(X) is represented using a


derivative(F,X,DF), d e r i v a t i v e ( ~ , ~ , ~ G ) .
derivative (F*G ,X ,F*DG+DF*G) - unary structure sin. If a different representation were used, for example,
unary-term(sin, X) where the name of the structure is made accessible,
derivative(l/F ,X ,-DF/ (F*F) ) -
derivative(F,X,DF), derivative(~,~,DG).
then the problem with the chain rule disappears. The chain rule can then
derivative(F,X,DF). be formulated as
derivative(F/G,X,(G*~F-F*DG)/(G*G)) - derivative (unary-term ( F , U) ,X ,DF*DU) -
derivative(F,X,DF), derivative(~,X,DG).
derivative(unary-term(F,U),U,DF), derivative(U,X,DU).
Program 3.30 Derivative rules
Note that all the rules in Program 3.30 would have to be reformulated in
terms of this new representation and would appear less natural.
reads: "The derivative of sin(X) with respect to X is cos (XI ." Natural People take for granted the automatic simplification of expressions
mathematical notation can be used. A representative sample of functions when differentiating expressions. Simplification is missing from Program
and their derivatives is given in Program 3.30. 3.30. The answer to the query derivative(3*x+2 ,x,D)? is D=(3*1+Oa
Sums and products of terms are differentiated using the sum rule and X ) +O. We would immediately simplify D to 3, but it is not specified in the
product rule, respectively. The sum rule states that the derivative of a logic program.
sum is the sum of derivatives. The appropriate clause is The next example is a solution to the Towers of Hanoi problem, a
derivative (F+G,X,DF+DG) - standard introductory example in the use of recursion. The problem is
derivative (F ,X ,DF) , derivative (G ,x ,DG) . to move a tower of n disks from one peg to another with the help of an
auxiliary peg. There are two rules. Only one disk can be moved at a time,
The product rule is a little more complicated, but the logical clause is and a larger disk can never be placed on top of a smaller disk.
just the mathematical definition: There is a legend associated with the game. Somewhere hidden in the
derivative (F*G,X ,F*DG+DF*G) - surroundings of Hanoi, an obscure Far Eastern village when the legend
was first told, is a monastery. The monks there are performing a task
derivative (F,X,DF) , derivative (G,X, DG) .
assigned to them by God when the world was created - solving the
Program 3.30 also contains the reciprocal and quotient rules. preceding problem with three golden pegs and 64 golden disks. At the
The chain rule is a little more delicate. It states that the derivative of moment they complete their task, the world will collapse into dust. Since
f ( g ( x ) )with respect to x is the derivative of f ( g ( x ) )with respect to g(x) the optimal solution to the problem with n disks takes Z n - 1 moves, we
times the derivative of g ( x ) with respect to x. As stated, it involves quan-
Chapter 3 Recursive P r o g r a m m i n g

hanoi(N,A,B,C,Moves) -
Moves is a sequence of moves for solving the Towers of
satisfiable(Formu1a) -
There is a true instance of the Boolean formula Formula.
Hanoi puzzle with N disks and three pegs, A, B, and C.

---
satisfiable(true).
h a n o i ( s ( 0 ) ,A,B,C, [A t o B]). satisfiable(X~Y) satisfiable()o, satisfiable(Y1
h a n o i ( s ( N ) ,A,B,C,Moves)- satisfiable(XVY) satisfiable(X).
hanoi(N,A,C,B,Msl) , s a t i s f iable(XVY) s a t i s f iable(Y) .
hanoi(N,C,B,A,Ms2), s a t i s f iable(-X)- invalid(X).
append(Ms1, [A t o B I Ms21 ,Moves) .
invalid(Formu1a) -
There is a false instance of the Boolean formula Formula.
Program 3.31 Towers of Hanoi
invalid(f alse) .

need not lose any sleep over this possibility. The number 2" is comfort-
invalid(XVY1
invalid(XAY)
invalid(XAY)
---
invalid(X) , invalid(Y)
invalid(X1.
invalid(Y1.
.

ingly big.
The relation scheme for solving the problem is hanoi (N ,A , B ,C ,
invalid(-Y) -
s a t i s f i a b l e (Y) .

Moves). It is true if Moves is the sequence of moves for moving a tower Program 3.32 Satisfiability of Boolean formulae
of N disks from peg A to peg B using peg C as the auxiliary peg. T h s is an
extension to usual solutions that do not calculate the sequence of moves
but rather perform them. The representation of the moves uses a binary
functor to, written as an infix operator. The term X to Y denotes that A Boolean formula F is true if
the top disk on peg X is moved to peg Y. The program for solving the
problem is given in Program,3.31.
F = 'true'.
The declarative reading of the heart of the solution, the recursive rule
F = XAY, and both X and Y are true.
in Program 3.31, is: "Moves is the sequence of moves of s (N) disks from
F = X v Y , and either X or Y (or both) are true.
peg A to peg B using peg C as an auxiliary, if Msl is the solution for
F = -X, and X is false.
moving N disks from A to C using B, Ms2 is the solution for moving N disks
from C to B using A, and Moves is the result of appending [A t o BIMs21
to ~ ~ 1 . l '
A Boolean formula F is false if
The recursion terminates with moving one disk. A slightly neater, but
less intuitive, base for the recursion is moving no disks. The appropriate F = 'false'.
fact is F = XAY, and either X or Y (or both) are false.
F = X v Y , and both X and Y are false.
F = -X, and X is true.
The final example concerns Boolean formulae.
A Boolean f o r m u l a is a term defined as follows: The constants rrue and Program 3.32 is a logic program for determining the truth or falsity
false are Boolean formulae; if X and Y are Boolean formulae, so are Xv Y, of a Boolean formula. Since it can be applied to Boolean formulae with
X A Y , and -X, where v and A are binary infix operators for disjunction variables, it is actually more powerful than it seems. A Boolean formula
and conjunction, respectively, and - is a unary prefn operator for nega- with variables is s a t i s f i a b l e if it has a true instance. It is i n v a l i d if it
tion. has a false instance. These are the relations computed by the program.
Chapter 3 Recursive Programming

3.5.1 Exercises for Section 3.5 example, several appear in Clocksin and Mellish (1984) and in the uneven
collection of short Prolog programs, How to Solve It in Prolog by Coelho
(i) Write a program to recognize if an arithmetic sum is normalized, et al. (1980).
that is, has the form A + B, where A is a constant and B is a normal- The latter book has been updated as Coelho and Cotta (1988) and is
ized sum. a source for other simple examples. 'The exercise on describing poker
hands is due to Ken Bowen.
(ii) Write a type definition for Boolean formulae. The classic reference for binary trees is Knuth (1968) and for sorting
(iii) Write a program for recognizing whether a logical formula is in Knuth (1973).
conjunctive normal form, namely, is a conjunction of disjunctions A discussion of the linear algorithm for the kth largest algorithms can
of literals, where a literal is an atomic formula or its negation. be found in most textbooks on algorithms, for example, Horowitz and
Sahni (1978). The discussion of the heap property is taken from Horowitz
(iv) Write a program for the relation negat ion-inwards ( F l ,F2), whch and Sahni (1978).
is true if F2 is the logical formula resulting from moving all nega- Many of the basic programs for arithmetic and list processing have a
tion operators occurring in the formula F1 inside conjunctions and simple structure that allows many correctness theorems to be proved
disjunctions. automatically, see, for example, Boyer and Moore (1979) and Sterling and
(v) Write a program for converting a logical formula into conjunctive Bundy (1982).
normal form, that is, a conjunction of disjunctions. Ackermann's function is discussed by Peter (1967).

(vi) Consider the following representation of a bag, that is, a list of


elements with multiplicities. The function symbol bag(Element,
Multiplicity ,RestofBag) should be used. The atom void can
be used as an empty bag. For example, the term bag (a,3, bag (b,2,
void)) represents a list of three copies of an element a, and two
copies of an element b. Write logic programs to

(a) Take the union of two bags;


(b) Take the intersection of two bags;
(c) Substitute for an element in a bag;
(d) Convert a list into a bag;
(e) Convert a binary tree into a bag.

3.6 Background

Many of the programs in this chapter have been floating around the logic
programming community, and their origins have become obscure. For
The Computation Model of Logic
Programs

The computation model used in the first three chapters of the book has
a severe restriction. All goals appearing in the proof trees are ground. All
rule instances used to derive the goals in the proof trees are also ground.
The abstract interpreter described assumes that the substitutions giving
the desired ground instances can be guessed correctly. In fact, the cor-
rect substitutions can be computed rather than guessed.
Thls chapter presents a general computation model of logic programs.
The first section presents a unification algorithm that removes the guess-
work in determining instances of terms. The second section presents an
appropriately modified abstract interpreter and gives example computa-
tions of logic programs.
The computation model of logic programming we present is especially
well suited to sequential languages such as Prolog. Our model can be
used to describe parallel logic programming languages. However, devel-
opers of these languages have often used other models, such as state
transitions or dynamic tree creation and destruction (see Section 4.3).

4.1 Unification

The heart of our computation model of logic programs is unification.


Unification is the basis of most work in automated deduction and of the
use of logical inference in artificial intelligence.
Necessary terminology for describing the algorithm is repeated from
Chapter 1, and new definitions are introduced as needed.
Chapter 4 The Computation Model of Logic Programs

Recall that a term t is a common instance of two terms, tl and tz, if If S is a variable, and T is a term not containing S, the following hap-
there exist substitutions and 8z such that t equals tl0l and t282. A pens. The stack is searched for all occurrences of S, whch are replaced
by T. Similarly, all occurrences of S in 8 are replaced by T. Then the sub-
term s is more general than a term t if t is an instance of s but s is not
stitution S = T is added to 8. It is significant that S does not occur in T.
an instance of t . A term s is an alphabetic variant of a term t if both
The test embodied by the phrase "not containing" is known as the occurs
s is an instance of t and t is an instance of s. Alphabetic variants are
related by the renaming of variables that occur in the terms. For exam- check.
ple, member (X,tree (Left ,X ,Right)) and member (Y,tree (Left ,Y,Z) ) If T is a variable. and S is a term not containing T, i.e.. T satisfies
are alphabetic variants. the occurs check with respect to S, the symmetric sequence of actions
A unifier of two terms is a substitution making the terms identical. If happens.
Equations are added to the stack if S and T are compound terms with
two terms have a unifier, we say they unify. There is a close relation be-
the same principal functor and arity, f($... ,S,) and f(Tl,.. .,T,,), say.
tween unifiers and common instances. Any unifier determines a common
For the terms to unify, each of the argument pairs must simultaneously
instance, and conversely, any common instance determines a unifier.
For example, append( [I,2,31 , [3,41 ,List) and append( [XIXsl ,Ys, unify. This is achieved by pushing the n equations, S , = TL,onto the stack.
[XIZsl ) unify. A unifying substitution is {X=1,Xs= [2,31 , Ys= [3,41 ,
In any other case, failure is reported, and the algorithm terminates. If
the stack is emptied, the terms unify, and the unifier can be found in
List- [llzs] }. Their common instance, determined by this unifying sub-
stitution, is append ( [I,2,3] , [3,4] , [I(Zs]) . 8 . The complete algorithm is given as Figure 4.1. The occurs check is
A most general unifier, or mgu, of two terms is a unifier such that the
embodied in the phrase "that does not occur in."
associated common instance is most general. It can be shown that if two We do not prove the correctness of t h s algorithm, nor analyze its com-
plexity. The interested reader is referred to the literature in Section 4.3.
terms unify, all mgus are equivalent. Mahng that statement precise is
beyond the scope of this book, but we give pointers in Section 4.3. We .
Consider attempting to unify the terms append( [a, bl [ c ,dl ,Ls) and
append( [XIXsl ,Ys;CXIZsl). The stack is initialized to the equation
proceed by giving an algorithm that computes a most general unifier of
two terms if one exists. append([a,bl,[c,dl,Ls) = append(CXIXsl,Ys, CXlZsl).
The algorithm for unification presented here is based on solving equa-
These two terms have the same functor, append, and arity, 3, so we add
tions. The input for the algorithm is two terms, Ti and Tz. The output
the three equations relating the subterms of the two terms. These are
of the algorithm is an mgu of the two terms if they unify, or failure if
[a,bl=[XJXsl,Cc,dl=Ys,and Ls=[XlZsl.
the terms do not unify. The algorithm uses a pushdown stack for storing
The next equation, [a, bl = [XIXsl , is popped from the stack. These two
the equations that need to be solved and a location, 8, for collecting the
substitution comprising the output. compound terms have the same functor, ".". and arity. 2, so two equa-
The location B is initially empty, and the stack is initialized to contain tions, a=X and [bl =XS are added to the stack. Continuing, the equation
a=X is popped. T h s is covered by the second case in Figure 4.1. X is a
the equation TI = T2. The algorithm consists of a loop of popping an
variable not occurring in the constant, a. All occurrences of X in the stack
equation from the stack and processing it. The loop terminates when
are replaced by a. One equation is affected, namely Ls=[XZsl, whch
the stack becomes empty or if failure occurs in processing an invalid
equation. becomes Ls- [alZsl.The equation X=a is added to the initially empty sub-
We consider the possible actions for dealing with a popped equation stitution, and the algorithm continues.
The next equation to be popped is [bl =Xs.Again tlus is covered by the
S = T. The simplest case is if S and T are identical constants or var-
second case. Xs= [bl is added to the set of substitutions, and the stack is
iables. This equation is correct, and nothing further needs to be done.
checked for occurrences of Xs. There are none, and the next equation is
The computation continues by popping the next equation from the
stack. popped.
The Computation Model of Logic Programs
Chapter 4

ever, most Prolog implementations omit the occurs check from the unifi-
Input: Two terms TI and T2 to be unified
cation algorithm, for pragmatic reasons.
Output: 8,the mgu of 7; and T2,or failure When implementing this unification algorithm for a particular logic
Algorithm: Initialize the substitution 8 to be empty, programming language, the explicit substitution in both the equations on
the stack to contain the equation TI = T,, the stack and the unifier is avoided. Instead, logical variables and other
and failure to false. terms are represented by memory cells with different values, and variable
while stack not empty and no failure do binding is implemented by assigning to the memory cell representing a
pop X = Y from the stack logical variable a reference to the cell containing the representation of
the term the variable is bound to. Therefore,
case
X is a variable that does not occur in Y: Substitute Y for X in stack and in 8.
substitute Y for X in the stack and in Q
Add X = Y to substitutions.
add X = Y to O
I' is a variable that does not occur in X: is replaced by
substitute X for I. in the stack and in 0
add Y = X to O Make X a reference to Y
X. and I' are idcntical constants or variables:
continue 4.1.1 Exercises for Section 4.1
Xis f ( X ,,...,X,,) and Y i s f ( Y l, . . . ,Y,,)
for some functor f and n > 0:
(i) Use the algorithm in Figure 4.1 to compute an mgu of a p p e n d ( [b] ,
push X, = I',, i = 1 . . . n , on the stack [c,dl , L ) and a p p e n d ( CXIXsl , Y s , [XlZsl 1.
other\vise: (ii) Use the algorithm in Figure 4.1 to compute an mgu of h a n o i ( s ( N ) ,
fa~lureis true A , B ,C,Ms) and h a n o i ( s ( s (0)), a , b , c ,Xs).
I f . failure, then output failure else output 0
-- - - -. - -- - - -- --
Figure 4.1 A unification algorithm
4.2 An Abstract Interpreter for Logic Programs

We revise the abstract interpreter of Section 1.8 in the light of the unifi-
cation algorithm. The result is our full computation model of logic pro-
The second case also covers [ c , d l =Ys. Another substitution, Y s = Cc ,
grams. All the concepts introduced previously, such as goal reductions
d l , is added to the collection, and the final equation, Ls= [ a l ~ s,l is and computation traces, have their analogues in the full model.
popped. This is handled by the symmetric first case. L s does not
A computation of a logic program can be described informally as fol-
occur in [alzs], so the equation is added as is to the unifier, and
lows. It starts from some initial (possibly conjunctive) query G and, if it
the algorithm terminates successfully. The unifier is {X=a ,Xs= [bl ,
terminates, has one of two results: success or failure. If a computation
Y s = [ c , d l , Ls= [a/Zsl } . The common instance produced by the unifier is
succeeds, the instance of G proved is conceived of as the output of the
a p p e n d ( [a, bl , [c , d l , [a1Zs] ) . Note that in t h s unification, the substi-
computation. A given query can have several successful computations,
tutions were not updated.
each resulting in a different output. In addition, it may have nontermi-
The occurs check is necessary to prevent the unification of terms such
nating computations, to which we associate no result.
as s (XI and X. There is no finite common instance of these terms. How-
Chapter 4 The Computation Model of Logic Programs

The computation progresses via goal reduction. At each stage, there is Input: A goal G and a program P
some resolvent, a conjunction of goals to be proved. A goal in the resol- Output: An instance of G that is a logical consequence of P,
vent and clause in the logic program are chosen such that the clause's or no otherwise
head unifies with the goal. The computation proceeds with a new resol- Algorithm: Initialize the resolvent to G.
vent, obtained by replacing the chosen goal by the body of the chosen while the resolvent is not empty do
clause in the resolvent and then applying the most general unifier of the choose a goal A from the resolvent
head of the clause and the goal, The computation terminates when the choose a (renamed) clause A' -B,,. . .,B, from P
resolvent is empty. In t h s case, we say the goal is solved by the program. such that A and A' unify with mgu 8
To describe computations more formally, we introduce some useful (if no such goal and clause exist, exit the while loop)
concepts. A computation of a goal Q = Qo by a program P is a (possibly replace A by B,,. . .,B, in the resolvent
infinite) sequence of triples (Q,Gi,CI). is a (conjunctive) goal, Gi is a apply B to the resolvent and to G
goal occurring in Q , and C, is a clause A-B1,. . .,Bkin P renamed so that it I f the resolvent is empty, then output G, else output no.
contains new variable symbols not occurring in Q,, 0 5 j _( i. For all i > 0, Figure 4.2 An abstract interpreter for logic programs
Q+1is the result of replacing G, by the body of Ci in Q , and applying the
substitution GI, the most general unifier of Gi and A,, the head of C,; or
the constant true if GI is the only goal in and the body of C, is empty; instance of G if a proof of such an instance is found, or no if a failure
or the constant fail if Gi and the head of C, do not unify. has occurred during the computation. Note that the interpreter may also
The goals BiOi are said to be derived from G., and C,. A goal G j = Bike, fail to terminate.
where Blk occurs in the body of clause C,, is said to be invoked by GI and An instance of a query for whlch a proof is found is called a solution to
C,. G, is the parent of any goal it invokes. Two goals with the same parent the query.
goal are sibling goals. The policy for adding and removing goals from the resolvent is called
A trace of a computation of a logic program (Q,G,,C,)is the sequence the scheduling policy of the interpreter. The abstract interpreter leaves
of pairs (Gi,OI), where 81 is the subset of the mgu 0, computed at the ith the scheduling policy unspecified.
reduction, restricted to variables in GI. Consider solving the query append ( [a, bl , [ c ,dl ,Ls) ? by Program
We present an abstract interpreter for logic programs. It is an adap- 3.15 for append using the abstract interpreter of Figure 4.2. The resol-
tation of the interpreter for ground goals (Figure 1.1).The restriction to vent is initialized to be append( [a,bl , [c ,dl ,Ls) . It is chosen as the
using ground instances of clauses to effect reductions is lifted. Instead, goal to reduce, being the only one. The rule chosen from the program is
the unification algorithm is applied to the chosen goal and head of the
chosen clause to find the correct substitution to apply to the new resol-
append( CX I Xsl ,Y s , CX I Z s l ) - append(Xs ,Y s ,Zs) .
vent. The unifier of the goal and the head of the rule is {X=a,Xs=[bl ,
Care needs to be taken with the variables in rules to avoid name , L s = [a1Zsl } . A detailed calculation of t h s unifier appeared
Y s = [c , d l
clashes. Variables are local to a clause. Hence variables in different in the previous section. The new resolvent is the instance of ap-
clauses that have the same name are, in fact, different. This is ensured pend (Xs ,Y s ,Zs) under the unifier, namely, append ( Cbl , [ c , d l ,Zs) . This
by renaming the variables appearing in a clause each time the clause is goal is chosen in the next iteration of the loop. The same clause for
chosen to effect a reduction. The new names must not include any of the append is chosen, but variables must be renamed to avoid a clash of
variable names used previously in the computation. variable names. The version chosen is
The revised version of the interpreter is given as Figure 4.2. It solves a
query G with respect to a program P. The output of the interpreter is an append( [XI I Xsll , Y s l , [XI I Z s l l ) - append(Xs1 , Y s l ,Zsl)
The Computation Model o f Logic Programs
Chapter 4

son(S,haran) son(S,haran)
append( Ca,bl , [c ,dl ,Ls) Ls=[a]Zsl
father(haran,S) S=lot male(S) S=lot
append ( [bl , [c ,dl ,Zs) Zs= [blZsl]
male(1ot) father(haran,lot)
append( [ 1 , [c,dl , Z S ~ ) Zsl=[c,d]
true true
true
Output: Ls= [a,b,c,dl Figure 4.4 Different traces of the same solution

Figure 4.3 Tracing the appending of two lists


Solutions to a query obtained using the abstract interpreter may con-
tain variables. Consider the query member(a,Xs)? with respect to Pro-
The unifier of the head and goal is {X1=b, X s l = [ I , ~ s l[c=, d l , gram 3.12 for member. This can be interpreted as asking what list X s has
Z s = [ b / Z s l l j . The new resolvent is append( [ I , [c ,dl ,Z s l ) . This time the element a as a member. One solution computed by the abstract inter-
the fact append( [ I ,Zs2,Zs2) is chosen; we again rename variables as preter is X s = CalYsI , namely, a list with a as its head and an unspecified
necessary. The unifier this time is {Zs2=[c , d l , Z s l = [ c , d l 1. The new tail. Solutions that contain variables denote an infinity of solutions-all
resolvent is empty and the computation terminates. their ground instances.
To compute the result of the computation, we apply the relevant part There are two choices in the interpreter of Figure 4.2: choosing the goal
of the mgu's calculated during the computation. The first unification to reduce, and choosing the clause to effect the reduction. These must be
instantiated LS to [a/Zs]. zs was instantiated to [ b i ~ s l lin the second resolved in any realization of the computation model. The nature of the
unification, and Z s l further became [ c , d l . Putting it together, L s has the choices is fundamentally different.
value [ a ( [bl [c , d l 1I, or more simply, [ a , b , c , d l . The choice of goal to reduce is arbitrary; it does not matter which is
The computation can be represented by a trace. The trace of the fore- chosen for the computation to succeed. If there is a successful computa-
going append computation is presented in Figure 4.3. To make the traces tion by choosing a given goal, then there is a successful computation by
clearer, goals are indented according to the indentation of their parent. choosing any other goal. The two traces in Figure 4.4 illustrate two suc-
A goal has an indentation depth of d + l if its parent has indentation cessful computations, where the choice of goal to reduce at the second
depth d. step of the computation differs.
As another example, consider solving the query s o n ( S , h a r a n ) ? by The choice of the clause to effect the reduction is nondeterministic.
Program 1.2. It is reduced using the clause son(X ,Y) - f a t h e r (Y, X) , Not every choice will lead to a successful computation. For example, in
male (X). A most general unifier is {X=S,Y=haran}. Applying the sub- both traces in Figure 4.4, we could have gone wrong. If we had chosen to
stitution gives the new resolvent f a t h e r ( h a r a n , S) , male (S). This is reduce the goal f a t h e r ( h a r a n , S) with the fact f a t h e r ( h a r a n , y i s c a h ) ,
a conjunctive goal. There are two choices for the next goal to reduce. we would not have been able to reduce the invoked goal male ( y i s c a h ) .
Choosing the goal f a t h e r ( h a r a n , S) leads to the following computation. For the second computation, had we chosen to reduce male(S) with
The goal, unifies with the fact f a t h e r ( h a r a n , l o t ) in the program, and male ( i s a a c ) , the invoked goal f a t h e r ( h a r a n , i s a a c ) could not have
the computation continues with S instantiated to l o t . The new resolvent been reduced.
is male ( l o t ) , which is reduced by a fact in the program, and the compu- For some computations, for example, the computation illustrated in
tation terminates. This is illustrated in the left trace in Figure 4.4. Figure 4.3, there is only one clause from the program that can reduce
The other possibility for computing S=haran is choosing to reduce each goal. Such a computation is called deterministic. Deterministic com-
the goal male (S) before f a t h e r ( h a r a n , S) . This goal is reduced by the putations mean that we do not have to exercise our nondeterministic
fact m a l e ( 1 o t ) with S instantiated to l o t . The new resolvent is f a - imagination.
t h e r ( h a r a n , l o t ) , which is reduced to the empty goal by the correspond- The alternative choices that can be made by the abstract interpreter
ing fact. This is the right trace in Figure 4.4. when trying to prove a goal implicitly define a search tree, as described
Chapter 4 The Computation Model o f Logic Programs

more fully in Section 5.4. The interpreter "guesses" a successful path


in t h s search tree, corresponding to a proof of the goal, if one exists.
However, dumber interpreters, without guessing abilities, can also be
built, with the same power as our abstract interpreter. One possibility
is to search t h s tree breadth-first, that is, to explore all possible choices
in parallel. T h s will guarantee that if there is a finite proof of the goal
(i.e., a finite successful path in the search tree), it will be found.
Another possibility would be to explore the abstract search tree depth-
first. In contrast to the breadth-first search strategy, the depth-first one
does not guarantee finding a proof even if one exists, since the search
tree may have infinite paths, corresponding to potentially infinite com-
putations of the nondeterministic interpreter. A depth-first search of the
tree might get lost in an infinite path, never finding a finite successful
path, even if one exists.
In technical terms, the breadth-first search strategy defines a complete
proof procedure for logic programs, whereas the depth-first one is in-
complete. In spite of its incompleteness, depth-first search is the one
incorporated in Prolog, for practical reasons, as explained in Chapter 6.
Let us give a trace of a longer computation, solving the Towers of
Hanoi problem with three disks, using Program 3.31. It is a deterministic Figure 4.5 Solving the Towers of Hanoi
computation, given as Figure 4.5. The final append goal is given without
unifications. It is straightforward to fill them in.
Computations such as that in Figure 4.5 can be compared to compu-
tations in more conventional languages. Unification can be seen to sub-
sume many of the mechanisms of conventional languages: record alloca-
tion, assignment of and access to fields in records, parameter passing,
and more. We defer the subject until the computation model for Prolog
is introduced in Chapter 6.
A computation of G by P terminates if G, = true or fail for some n 2 Figure 4.6 A nonterminating computation
0. Such a computation is finite and of length n. Successful computations
correspond to terminating computations that end in true. Failing com-
putations end in fail. All the traces given so far have been of successful
computations.
Recursive programs admit the possibility of nonterrninating computa-
tions. The query append(Xs, [ c , dl ,Ys)? with respect to append can be
reduced arbitrarily many times using the rule for append. In the process,
X s becomes a list of arbitrary length. Thls corresponds to solutions of
the query appending [ c , dl to an arbitrarily long list. The nonterminat-
ing computation is illustrated in Figure 4.6.
Chapter 4 The Computation Model o f Logic Programs

All the traces presented so far have an important feature in common. the subject of much investigation: see, for example, Martelli and Monta-
If two goals Gi and Gj are invoked from the same parent, and Gi appears nari (1982), Paterson and Wegman (19781, and Dwork et al. (1984). Typi-
before G, in the trace, then all goals invoked by Gi will appear before cal textbook descriptions appear in Bundy (1983) and Nilsson (1980).
Gj in the trace. T h s scheduling policy makes traces easier to follow, by The definition of unification presented here is nonstandard. Readers
solving queries depth-first. wishlng to learn more about unifiers are referred to the definitive dis-
The scheduling policy has another important effect: instantiating vari- cussion on unification in Lassez, hlaher, and Marriott (1988). This paper
ables before their values are needed for other parts of the computation. points out inconsistencies of the various definitions of unifiers that have
A good ordering can mean the difference between a computation being been proposed in the literature, including the version in this book. Es-
deterministic or not. sentially, we have explained unifiers based on terms to avoid technical
Consider the computation traced in Figure 4.5. The goal issues of composition of substitutions, which are not needed for our de-
scription of logic programming computations.
The computation model we have presented has a sequential bias and
is reduced to the following conjunction is influenced by the computation model for Prolog given in Chapter 6.
Nonetheless, the model has potential for parallelism by selecting several
goals or several rules at a time, and for elaborate control by selecting
complicated computation rules. References for reading about different -
computation models for logic programming are gi\.en in Section 6.3.
Another bias of our computation model is the central place of unifi-
If the append goal is now chosen, the append fact could be used (incor- cation. An exciting development n-ithin logic programming has been the
rectly) to reduce the goal. By reducing the two hanoi goals first, and all realization that unification is just one instance of constraint solving. New
the goals they invoke, the append goal has the correct values for Msl and computation models ha\.e been presented where the solution of equal-
Ms2. ity constraints, i.e., unification, in the abstract interpreter of Figure 4.2
is replaced by solving other constraints. Good starting places to read
4.2.1 Exercises for Section 4.2 about the new constraint-based models are Colmerauer (1990), Jaffar and
Lassez (1987),and Lassez (1991).
(i) Trace the query sort ( [3,1,21,Xs)? using the permutation sort A proof that the choice of goal to reduce from the resolvent is arbitrary
(3.20),insertion sort (3.21),and quicksort (3.22) programs in turn. can be found in Apt and \ a n Emden (1982) or in the text of Llo)-d (1987).
(ii) Give a trace for the goal derivative(3*sin(x)-4*cos(x) ,x,~) A method for replacing the runtime occurs check with compile-time
using Program 3.30for derivative. analysis was suggested by Plaisted (1984).
Attempts have been made to make unification without the occurs
(iii) Practice tracing your favorite computations. check more than a necessary expedient for practical implementations of
Prolog. In particular, Colmerauer ( 1982b) proposes a theoretical model
-- - for such unifications that incorporates computing with infinite terms.
4.3 Background A novel use of unification without the occurs check appears in Eggert
and Chow (1983), where Escher-like drawings that gracefully tend to in-
Unification plays a central role in automated deduction and in the use finity are constructed.
of logical inference in artificial intelligence. It was first described in the
landmark paper of Robinson (1965). Algorithms for unification have been
5 Theory of Logic Programs

A major underlying theme of this book, laid out in the introduction, is


that logic programming is attractive as a basis for computation because
of its basis in mathematical logic, whch has a well-understood, well-
developed theory. In this chapter, we sketch some of the growing theory
of logic programming, which merges the theory inherited from mathe-
matlcal logic with experience from computer science and engineering.
Giving a complete account is way beyond the scope of this book. In thls
chapter, we present some results to direct the reader in important direc-
tions. The first section, on semantics, gives definitions and suggests why
the model-theoretic and proof-theoretic semantics give the same result.
The main issue in the second section, on program correctness, is termi-
nation. Complexity of logic programs is discussed in the third section.
The most important section for the rest of the book is Section 4, which
discusses search trees. Search trees are vital to understanding Prolog's
behavior. Finally, we introduce negation in logic programming.

5.1 Semantics

Semantics assigns meanings to programs. Discussing semantics allows


us to describe more formally the relation a program computes. Chap-
ter 1 informally describes the meaning of a logic program P as the set
of ground instances that are deducible from P via a finite number of ap-
plications of the rule of universal modus ponens. This section considers
more formal approaches.
Chapter 5 Theory o f Logic Programs

parent(terach,abraham). ~arent(abraham,isaac).
There are two predicates, parent/2 and ancestor/2,in Program 5.1.
parent (isaac,j acob) . parent ( jacob ,benj amin) The Herbrand base of Program 5.1 consists of 25 goals for each predi-

--
ancestor (X,Y) parent (X,Y).
cate, where each constant appears as each argument:
ancestor(X,Z) parent(X,Y), ancestor(Y,Z). Cparent(terach,terach), parent(terach,abraham),
Program 5.1 Yet another family example parent (terach,isaac) , parent (terach,jacob) ,
parent (terach,benjamin) , parent (abraham,terach) ,
parent(abraham,abraham), parent(abraham,isaac),
The operational semantics is a way of describing procedurally the parent (abraham ,jacob) , parent (abraham ,benjamin) ,
meaning of a program. The operational meaning of a logic program P parent (isaac ,terach) , parent (isaac,abraham) ,
is the set of ground goals that are instances of queries solved by P using parent (isaac,isaac) , parent (isaac,jacob) ,
the abstract interpreter given in Figure 4.2. Thls is an alternative for- parent (isaac ,benjamin), parent (jacob,terach) ,
mulation of the previous semantics, which defined meaning in terms of parent (jacob,abraham) , parent (jacob,isaac) ,
logical deduction. parent ( j acob,jacob) , parent (jacob,benjamin) ,
The declarative semantics of logic programs is based on the standard parent(benjamin,terach), parent(benjamin,abraham),
model-theoretic semantics of first-order logic. In order to define it, some parent (benjamin,isaac) , parent (benjamin,jacob) ,
new terminology is needed. parent(benjamin,benjamin), ancestor(terach,terach),
ancestor(terach,abraham) , ancestor(terach, isaac) ,
Definition
ancestor(terach, jacob) , ancestor(terach,benjamin),
Let P be a logic program. The Herbrand universe of P, denoted U ( P ) ,is
ancestor (abraham,terach) , ancestor (abraham,abraham) ,
the set of all ground terms that can be formed from the constants and
m ancestor (abraham,isaac) , ancestor (abraham,jacob) ,
function symbols appearing in P.
ancestor(abraham,benjamin), ancestor(isaac,terach),
In this section, we use two running examples-yet another family data- ancestor(isaac, abraham) , ancestor (isaac,isaac) ,
base example, given as Program 5.1; and Program 3.1 defining the natural ancestor (isaac,jacob) , ancestor(isaac, benjamin) ,
numbers, repeated here: ancestor (jacob,terach) , ancestor( jacob,abraham) ,
ancestor(jacob, isaac) , ancestor(jacob, jacob) ,
natural-number(0) . ancestor (jacob,benjamin) , ancestor(benjamin,terach) ,
natural-number(s (X)) - natural-number(x). ancestor(benjamin, abraham) , ancestor (benjamin,isaac) ,
The Herbrand universe of Program 5.1 is the set of all constants appear- ancestor (benjamin,jacob) , ancestor (benjamin,benjamin) ).
ing in the program, namely, {terach,abraham,isaac ,jacob,benjamin}. The Herbrand base is infinite if the Herbrand universe is. For Pro-
If there are no function symbols, the Herbrand universe is finite. In Pro- gram 3.1, there is one predicate, natural-number. The Herbrand base
gram 3.1, there is one constant symbol, 0,and one unary function sym- equals {natural-number(0),natural-number(s (0) ) , . . . } .
bol, s.The Herbrand universe of Program 3.1 is {0,s (0) ,s (s(0) ) , . . . I.
If no constants appear in a program, one is arbitrarily chosen. Definition
An interpretation for a logic program is a subset of the Herbrand base.
Definition
The Herbrand base, denoted B(P), is the set of all ground goals that An interpretation assigns truth and falsity to the elements of the Her-
can be formed from the predicates in P and the terms in the Herbrand brand base. A goal in the Herbrand base is true with respect to an inter-
universe. pretation if it is a member of it, false otherwise.
Chapter 5 Theory of'Logic Programs

Definition The Herbrand universe is [ I,[[ ]],[I I,[ I], . . . , namely, all lists that can be
An interpretation I is a model for a logic program if for each ground built using the constant [ 1. The Herbrand base is all combinations of
instance of a clause in the program A-BI,. . .,B,, A is in I if BI,.. .,B, are lists with the append predicate. The declarative meaning is all ground in-
in I. ¤ stances of append ( [ I ,X s ,Xs) , that is, append ( [ I , [ I , [ I ) ,
append( [ I , [ [ I I , [ [ I I ) , . . . , together with goals such as append
Intuitively, models are interpretations that respect the declarative ( [ [ ] ] , [ I , [ [ I 1 1, which are logically implied by application(s) of
reading of the clauses of a program. the rule. This is only a subset of the Herbrand base. For example,
For Program 3.1, natural-number(0) must be in every model, and append( [ 1 , [ I , [ [ I I ) is not in the meaning of append but is in the
natural-number ( s (X) ) is in the model if natural-number (X) is. Any Herbrand base.
model of Program 3.1 thus includes the whole Herbrand base. Denotational semantics assigns meanings to programs based on asso-
For Program 5.1, the facts p a r e n t ( t e r a c h , abraham), p a r e n t (abra- ciating with the program a function over the domain computed by the
ham, i s a a c ) , p a r e n t ( i s a a c , jacob), and p a r e n t ( j a c o b , benjamin) program. The meaning of the program is defined as the least fucpoint of
must be in every model. A ground instance of the goal a n c e s t o r (X ,Y) is the function, if it exists. The domain of computations of logic programs
in the model if the corresponding instance of p a r e n t (X,Y) is, by the first is interpretations.
clause. So, for example, a n c e s t o r ( t e r a c h , abraham) is in every model.
By the second clause, a n c e s t o r (X, Z) is in the model if p a r e n t (X, Y) and Definition
a n c e s t o r (Y, Z) are. Given a logic program P, there is a natural mapping Tp from interpreta-
It is easy to see that the intersection of two models for a logic program tions to interpretations, defined as follows:
P is again a model. T h s property allows the definition of the intersection
Tp(I)= { A in B(P):A -BI ,B,,. . .,B,l, n 2 0, is a ground instance of
of all models.
a clause in P, and B1,. . .,B, are in I}. a
Definition
The mapping is monotonic, since whenever an interpretation I is con-
The model obtained as the intersection of all models is known as the
tained in an interpretation J, then Tp(I)is contained in Tp(J).
minimal model and denoted M ( P ) . The minimal model is the declarative
This mapping gives an alternative way of characterizing models. An
meaning of a logic program.
interpretation I is a model if and only if Tp(l)is contained in I.
The declarative meaning of the program for natural-number, its min- Besides being monotonic, the transformation is also continuous, a no-
imal model, is the complete Herbrand base {natural-number (0) ,natu- tion that will not be defined here. These two properties ensure that for
ral-number (s ( 0 ) ) ,natural-number(s (S ( 0 ) ) ) . . . 1. every logic program P, the transformation Tp has a least fixpoint, whlch
The declarative meaning of Program 5.1 is { p a r e n t ( t e r a c h , abraham) , is the meaning assigned to P by its denotational semantics.
p a r e n t (abraham, i s a a c ) , p a r e n t ( i s a a c ,j acob) , p a r e n t ( j acob, Happily, all the different definitions of semantics are actually describ-
benjamin) , a n c e s t o r ( t e r a c h , abraham) , a n c e s t o r (abraham, i s a a c ) , ing the same object. The operational, denotational, and declarative se-
a n c e s t o r ( i s a a c ,j acob) , a n c e s t o r ( j a c o b , benj amin) , ancestor mantics have been demonstrated to be equivalent. This allows us to de-
(terach, isaac) , a n c e s t o r ( t e r a c h , jacob) , ancestor (terach, fine the meaning of a logic program as its minimal model.
benj amin) , a n c e s t o r (abraham, jacob) , a n c e s t o r (abraham, ben-
jamin) , a n c e s t o r ( i s a a c , benjamin) 1.
Let us consider the declarative meaning of append, defined as Pro- 5.2 Program Correctness
gram 3.15 and repeated here:
Every logic program has a well-defined meaning, as discussed in Sec-
tion 5.1. This meaning is neither correct nor incorrect.
Chapter 5 Theory o f Logic Programs

The meaning of the program, however, may or may not be what was natural-number (X) X=s (XI)
intended by the programmer. Discussions of correctness must therefore natural-number(X1) X1=s (X2)
natural-number (X2) X2=s (X3)
take into consideration the intended meaning of the program. Our pre-
vious discussion of proving correctness and completeness similarly was
with respect to an intended meaning of a program.
We recall the definitions from Chapter 1. An intended meaning of a Figure 5.1 A nonterrninating computation
program P is a set of ground goals. We use intended meanings to denote
the set of goals intended by the programmer for the program to com-
pute. A program P is correct with respect to an intended meaning M if need to describe recursive data types in a way that allows us to discuss
M(P) is contained in M. A program P is complete with respect to an in- termination.
tended meaning if M is contained in M(P).A program is thus correct and Recall that a type, introduced in Chapter 3, is a set of terms.
complete with respect to an intended meaning if the two meanings coin-
cide exactly. Definition
Another important aspect of a logic program is whether it terminates. A type is complete if the set is closed under the instance relation. With
every complete type T we can associate an incomplete type IT, which is
Definition the set of terms that have instances in T and instances not in T.
A domain is a set of goals, not necessarily ground, closed under the
instance relation. That is, if A is in D and A' is an instance of A, then We illustrate the use of these definitions to find termination domains
A' is in D as well. for the recursive programs using recursive data types in Chapter 3. Spe-
cific instances of the definitions of complete and incomplete types are
Definition given for natural numbers and lists. A (complete) natural number is ei-
A termination domain of a program P is a domain D such that every ther the constant 0, or a term of the form s n ( X ) .An incomplete natural
computation of P on every goal in D terminates. ¤ number is either a variable, X, or a term of the form sn(0),where X is
a variable. Program 3.2 for Iis terminating for the domain consisting
of goals where the first and/or second argument is a complete natural
lisually, a useful program should have a termination domain that in-
cludes its intended meaning. However, since the computation model of number.
logic programs is liberal in the order in which goals in the resolvent can
Definition
be reduced, most interesting logic programs will not have interesting ter-
A list is complete if every instance satisfies the definition given in Pro-
mination domains. This situation will improve when we switch to Prolog.
gram 3.11. A list is incomplete if there are instances that satisfy this
The restrictive model of Prolog allows the programmer to compose non-
definition and instances that do not.
trivial programs that terminate over useful domains.
Consider Program 3.1 defining the natural numbers. This program is For example, the list [a,b , cl is complete (proved in Figure 3.3), while
terminating over its Herbrand base. However, the program is nonter-
the variable X is incomplete. Two more interesting examples: [a,X ,cl is
minating over the domain {natural-number (X) } . This is caused by the a complete list, although not ground, whereas [a,bjXs] is incomplete.
possibility of the nonterminating computation depicted in the trace in
A termination domain for append is the set of goals where the first
Figure 5.1.
and/or the t h r d argument is a complete list. We discuss domains for
For any logic program, it is useful to find domains over which it is
other list-processing programs in Section 7.2, on termination of Prolog
terminating. This is usually difficult for recursive logic programs. We
programs.
Chapter 5 Theory of Logic Programs

5.2.1 Exercises for Section 5.2 length complexity. This is demonstrated in Exercise (i) at the end of thls
section.
(i) Give a domain over which Program 3.3 for p l u s is terminating. The applicability of thls measure to Prolog programs, as opposed to
logic programs, depends on using a unification algorithm without an oc-
(ii) Define complete and incomplete binary trees by analogy with the curs check. Consider the runtime of the straightforward program for ap-
definitions for complete and incomplete lists. pending two lists. Appending two lists, as shown in Figure 4.3, involves
several unifications of append goals with the head of the append rule
append ( CXlXsl ,Ys, CXlZsl 1. At least three unifications, matchng vari-
5.3 Complexity ables against (possibly incomplete) lists, will be necessary. If the occurs
check must be performed for each, the argument lists must be searched.
We have analyzed informally the complexity of several logic programs, This is directly proportional to the size of the input goal. However, if the
for example, I and p l u s (Programs 3.2 and 3.3) in the section on arith- occurs check is omitted, the unification time will be bounded by a con-
metic, and append and the two versions of reverse in the section on lists stant. The overall complexity of append becomes quadratic in the size of
(Programs 3.15 and 3.16). In this section, we briefly describe more formal the input lists with the occurs check, but only linear without it.
complexity measures. We introduce other useful measures related to proofs. Let R be a proof.
The multiple uses of logic programs slightly change the nature of com- We define the depth of R to be the deepest invocation of a goal in the
plexity measures. Instead of looking at a particular use and specifying associated reduction. The goal-size of R is the maximum size of any goal
complexity in terms of the sizes of the inputs, we look at goals in the reduced.
meaning and see how they were derived. A natural measure of the com-
plexity of a logic program is the length of the proofs it generates for Definition
goals in its meaning. A logic program P is of goal-size complexity G(n) if for any goal A in the
meaning of P of size n, there is a proof of A with respect to P of goal-size
Definition less than or equal to G(n). #
The size of a term is the number of symbols in its textual representation.
Definition
Constants and variables, consisting of a single symbol, have size 1. A logic program P is of depth-complexity D(n) if for any goal A in the
The size of a compound term is 1 more than the sum of the sizes of meaning of P of size n, there is a proof of G with respect to P of depth
its arguments. For example, the list [b] has size 3, [a,b] has size 5, <D(n).
and the goal append( [ a , b] , [ c , dl ,Xs) has size 12. In general, a list of
n elements has size 2 . n + 1. Goal-size complexity relates to space. Depth-complexity relates to
space of what needs to be remembered for sequential realizations, and
Definition to space and time complexity for parallel realizations.
A program P is of length complexity L(n) if for any goal G in the meaning
of P of size n there is a proof of G with respect to P of length less than 5.3.1 Exercises for Section 5.3
equal to L(n).
Length complexity is related to the usual complexity measures in com- (i) Show that the size of a goal in the meaning of append joining a
puter science. For sequential realizations of the computation model, it list of length n to one of length m to give a list of length n + m
corresponds to time complexity. Program 3.15 for append has linear is 4 . n + 4 . m + 4. Show that a proof tree has m + 1 nodes. Hence
Chapter 5 Theory o f Logic Programs

show that append has linear complexity. Would the complexity be


altered if the type condition were added?

(ii) Show that Program 3.3 for p l u s has linear complexity.


(iii) Discuss the complexity of other logic programs.

5.4 Search Trees

Computations of logic programs given so far resolve the issue of nonde-


terminism by always making the correct choice. For example, the com-
@
plexity measures, based on proof trees, assume that the correct clause
can be chosen from the program to effect the reduction. Another way of Figure 5.2 Two search trees
computationally modeling nondeterminism is by developing all possible
reductions in parallel. In this section, we discuss search trees, a formal-
ism for considering all possible computation paths.

Definition reduction is the first goal. The edges are labeled with substitutions that
A search tree of a goal G with respect to a program P is defined as are applied to the variables in the leftmost goal. These substitutions are
follows. The root of the tree is G. Nodes of the tree are (possibly con- computed as part of the unification algorithm.
junctive) goals with one goal selected. There is an edge leading from a Search trees correspond closely to traces for deterministic computa-
node N for each clause in the program whose head unifies with the se- tions. The traces for the append query and hanoi query given, respec-
lected goal. Each branch in the tree from the root is a computation of G tively, in Figures 4.3 and 4.5 can be easily made into search trees. This is
by P. Leaves of the tree are success nodes, where the empty goal has been Exercise (i) at the end of this section.
reached, or failure nodes, where the selected goal at the node cannot be Search trees contain multiple success nodes if the query has mul-
further reduced. Success nodes correspond to solutions of the root of the tiple solutions. Figure 5.3 contains the search tree for the query ap-
tree. >
pend(As ,B s , [ a , b , cl ? with respect to Program 3.15 for append, asking
to split the list [ a , b , cl into two. The solutions for A s and B s are found
There are in general many search trees for a given goal with re- by collecting the labels of the edges in the branch leading to the success
spect to a program. Figure 5.2 shows two search trees for the query node. For example, in the figure, following the leftmost branch gives the
s o n ( S , h a r a n ) ? with respect to Program 1.2. The two possibilities cor- solution { A s = [ a , b , cl ,Bs= [ I 1.
respond to the two choices of goal to reduce from the resolvent f a - The number of success nodes is the same for any search tree of a given
t h e r ( h a r a n , S) ,male (S) . The trees are quite distinct, but both have a goal with respect to a program.
single success branch corresponding to the solution of the query S = l o t . Search trees can have infinite branches, which correspond to nonter-
The respective success branches are given as traces in Figure 4.4. rninating computations. Consider the goal append (Xs , [c , d l ,Ys) with
We adopt some conventions when drawing search trees. The leftmost respect to the standard program for append. The search tree is given in
goal of a node is always the selected one. This implies that the goals in Figure 5.4. The infinite branch is the nonterminating computation given
derived goals may be permuted so that the new goal to be selected for in Figure 4.6.
Chapter 5 Theory of Logic Programs

Figure 5.4 Search tree with an infinite branch


Figure 5.3 Search tree with multiple success nodes

5.5 Negation in Logic Programming


Complexity measures can also be defined in terms of search trees. Pro-
Logic programs are collections of rules and facts describing what is true.
log programs perform a depth-first traversal of the search tree. There-
Untrue facts are not expressed explicitlj-; they are omitted. When writing
fore, measures based on the size of the search tree will be a more real-
rules, it is often natural to include negative conditions. For example,
istic measure of the complexity of Prolog programs than those based on
defining a bachelor as an unmarried male could be written as
the complexity of the proof tree. However, the complexity of the search
tree is much harder to analyze.
There is a deeper point lurking. The relation between proof trees and
bachelor(X) - male(X) , not married(X) .
search trees is the relation between nondeterministic computations and if negation w7ere allowed. In this section, we describe an extension to
deterministic computations. Whether the complexity classes defined via the logic programming computation model that allows a limited form of
proof trees are equivalent to complexity classes defined via search trees negation.
is a reformulation of the classic P=NP question in terms of logic program- Researchers have investigated other extensions to logic programming
ming. to allow disjunction, and indeed, arbitrary first-order formulae. Dis-
cussing them is beyond the scope of this book. The most useful of the
extensions is definitely negation.
5.4.1 Exercises for Section 5.4 We define a relation n o t G and give a semantics. The essence of logic
programming is that there is an efficient procedural semantics. There is
(i) Transform the traces of Figure 4.3 and 4.5 into search trees. a natural way to adapt the procedural semantics to negation, namely by
Draw a search tree for the query s o r t ( [2,4,11 ,Xs) ? using permu- negation as failure. A goal G fails, (not G succeeds),if G cannot be derived
(ii)
tation sort. by the procedural semantics.
115 Theory of Logic Programs
Chapter 5

The relation not G is only a partial form of negation from first-order 5.6 Background
logic. The relation nor uses the negarion as failure rule. A goal not G will
be assumed to be a consequence of a program P if G is not a consequence The classic paper on the semantics of logic programs is of van Emden
of P. and Kowalski (1976). Important extensions were given by Apt and van
Negation as failure can be characterized in terms of search trees. Emden (1982). In particular, they showed that the choice of goal to re-
duce from the resolvent is arbitrary by showing that the number of suc-
Definition cess nodes is an invariant for the search trees. Textbook accounts of
- G with respect to a program P is finitely failed if it
A-search tree of a goal
. ~
the theory of logic programming discussing the equivalence between the
has no success nodes or infinite branches. The finite failure set of a logic declarative and procedural semantics can be found in Apt (1990),Deville
program P is the set of goals G such that G has a finitely failed search (1990), and Lloyd (1987).
tree with respect to P. In Shapiro (1984), complexity measures for logic programs are com-
pared with the complexity of computations of alternating Turing ma-
chines. It is shown that goal-size is linearly related to alternating space,
A goal not G is implied by a program P by the "negation as failure" rule
the product of length and goal-size is linearly related to alternating tree-
if G is in the finite failure set of P.
size, and the product of depth and goal-size is linearly related to alter-
Let us see a simple example. Consider the program consisting of two
nating time.
facts:
The classic name for search trees in the literature is SLD trees. The
name SLD was coined by research in automatic theorem proving, which
likes (abraham,pomegranates) . preceded the birth of logic programming. SLD resolution is a particu-
likes (isaac ,pomegranates). lar refinement of the resolution principle introduced in Robinson (1965).
Computations of logic programs can be interpreted as a series of reso-
The goal not likes (sarah ,pomegranates) follows from the program by lution steps, and in fact, SLD resolution steps, and are still commonly
negation as failure. The search tree for the goal likes(sarah,pomegran- described thus in the literature. The acronym SLD stands for Selecting a
ates) has a single failure node. literal, using a Linear strategy, restricted to Definite clauses.
Using negation as failure allows easy definition of many relations. For The first proof of the correctness and completeness of SLD resolution,
example, a declarative definition of the relation disjoint (Xs ,Ys) that albeit under the name LUSH-resolution, was given by Hill (1974).
two lists, XS and Ys, have no elements in common is possible as follows. The subject of negation has received a large amount of attention and
interest since the inception of logic programming. The fundamental work
dls joint (Xs ,Ys) - not (member (X ,XS) , member (X ,Ys)) .
on the semantics of negation as failure is by Clark (1978).Clark's results,
establishing soundness, were extended by Jaffar et al. (1983),who proved
the completeness of the rule.
This reads: "Xs is disjoint from Ys if there is no element X that is a The concept of negation as failure is a restricted version of the closed
member of both Xs and Ys." world assumption as discussed in the database world. For more infor-
An intuitive understanding of negation as failure is fine for the pro- mation see Reiter (1978). There has been extensive research on charac-
grams in this book using negation. There are semantic problems, how- terizing negation in logic programming that has not stabilized at this
ever, especially when integrated with other issues such as completeness time. The reader should look up the latest logic programming conference
and termination. Pointers to the literature are given in Section 5.6, and proceedings to find current thinlung. A good place to start reading to un-
Prolog's implementation of negation as failure is discussed in Chap- derstand the issue is Kunen (1989).
ter 11.
I1 The Prolog Language

In order to implement a practical programming language based on the


computation model of logic programming, three issues need attention.
The first concerns resolving the choices remaining in the abstract inter-
preter for logic programs, defined in Chapter 4. The second concerns
enhancing the expressiveness of the pure computation model of logic
programs by adding meta-logical and extra-logical facilities. Finally, ac-
cess to some of the capabilities of the underlying computer, such as fast
arithmetic and input/output, must be provided. This part discusses hob7
Prolog, the most developed language based on logic programming, han-
Leonardo Da Vinci. Portrait of the Florentine poet Bernardo Bellincioni, en- dles each of these issues.
gaged at the Court of Ludovico Sforza. Woodcut, based on a drawing by
Leonardo. From Bellincioni's Rime. Milan 1493.
Pure Prolog

A pure Prolog program is a logic program, in which an order is defined


both for clauses in the program and for goals in the body of the clause.
The abstract interpreter for logic programs is specialized to take advan-
tage of this ordering information. This chapter discusses the execution
model of Prolog programs in contrast to logic programs, and compares
Prolog to more conventional languages.
The relation between logic programming and Prolog is reminiscent of
the relation between the lambda-calculus and Lisp. Both are concrete re-
alizations of abstract computation models. Logic programs that execute
with Prolog's execution mechanism are referred to as pure Prolog. Pure
Prolog is an approximate realization of the logic programming compu-
tation model on a sequential machine. It is certainly not the only possi-
ble such realization. However, it is a realization with excellent practical
choices, which balance preserving the properties of the abstract model
with catering for efficient implementation.

6.1 The Execution Model of Prolog

Two major decisions must be taken to convert the abstract interpreter


for logic programs into a form suitable for a concrete programming lan-
guage. First, the arbitrary choice of which goal in the resolvent to reduce,
namely, the scheduling policy, must be specified. Second, the nondeter-
ministic choice of the clause from the program to effect the reduction
must be implemented.
Chapter 6 Pure Prolog

Several logic programming languages exist, reflecting different choices. father(abraham,isaac) . male(isaac) .
Prolog and its extensions (Prolog-11,IC-Prolog, and MU-Prolog, for exam- father(haran,lot). male(1ot).
father(haran,milcah). female(yiscah)
ple) are based on sequential execution. Other languages, such as PAR-
f ather(haran, yiscah) . f emale(mi1cah)
LOG, Concurrent Prolog, GHC, Aurora-Prolog, and Andorra-Prolog, are
based on parallel execution. The treatment of nondeterminism distin-
guishes between sequential and parallel languages. The distinction be-
son(X,haran)?
tween Prolog and its extensions is in the choice of goal to reduce.
f ather(haran,X)
Prolog's execution mechanism is obtained from the abstract interpreter by male (lot)
choosing the leftmost goal instead of an arbitrary one and replacing the non- true
deterministic choice of a clause by sequential search for a unifiable clause and Output: X=lot
backtracking.
In other words, Prolog adopts a stack scheduling policy. It maintains father(haran,X)
male (milcah) f
the resolvent as a stack: pops the top goal for reduction, and pushes the
derived goals onto the resolvent stack.
In addition to the stack policy, Prolog simulates the nondeterministic no (more) solutions
choice of reducing clause by sequential search and backtracking. When
Figure 6.1 Tracing a simple Prolog computation
attempting to reduce a goal, the first clause whose head unifies with the
goal is chosen. If no unifiable clause is found for the popped goal, the
computation is unwound to the last choice made, and the next unifiable
clause is chosen. in Section 4.2. We revise the computations of Chapters 4 and 5, indicat-
A computation of a goal G with respect to a Prolog program P is the ing the similarities and differences. Consider the query son(X, h a r a n ) ?
generation of all solutions of G with respect to P. In terms of logic with respect to Program 1.2, biblical family relationships, repeated at the
programming concepts, a Prolog computation of a goal G is a complete top of Figure 6.1. The computation is given in the bulk of Figure 6.1. It
depth-first traversal of the particular search tree of G obtained by always I
corresponds to a depth-first traversal of the first of the search trees in
I
choosing the leftmost goal. Figure 5.2. It is an extension of the first trace in Figure 4.4, since the
Many different Prolog implementations exist with differing syntax and whole search tree is searched.
programming facilities. Recently, there has been an attempt to reach a I
The notation previously used for traces must be extended to handle
Prolog standard based on the Edinburgh dialect of Prolog. At the time of failure and backtracking. An f after a goal denotes that a goal fails, that
writing, the standard has not been finalized. However a complete draft i is there is no clause whose head unifies with the goal. The next goal af-
exists, whch we essentially follow. We refer to the Prolog described in ter a failed goal is where the computation continues on backtracking.
that document as Standard Prolog. The syntax of logic programs that It already appears as a previous goal in the trace at the same depth of
we have been using fits within Standard Prolog except that we use some
characters not available on a standard keyboard. We give the standard
I
I
indentation and can be identified by the variable names. We adopt the
Edinburgh Prolog convention that a ";" typed after a solution denotes a
equivalent of our special characters. Thus :- should be used instead of continuation of the computation to search for more solutions. Unifica-
- in Prolog programs to separate the head of a clause from its body.
All the programs in this book run (possibly with minor changes) in all
tions are indicated as previously.
Trace facilities and answers provided by particular Prolog implementa-
Edinburgh-compatible Prologs. tions vary from our description. For example, some Prolog implementa-
A trace of a Prolog computation is an extension of the trace of a com- tions always give all solutions, while others wait for a user response after
putation of a logic program under the abstract interpreter as described each solution.
Chapter 6 Pure Prolog

append( [XI Xs] ,Ys,[X I Zs] ) - append(Xs ,Ys,Zs) quicksort( [XI XS] ,Ys) -
append( [ 1 ,Ys,Ys). ~artition(xs,~,~ittles,~igs),
quicksort(Littles,Ls),
append(Xs,Ys, Ca,b,cl) Xs= [al Xsll
quicksort(Bigs,Bs),
append(Xsl,Ys, [b,cl) Xsl= [b l Xs21
append(Ls, [XI BS] ,Ys) .
append(Xs2,Ys, LC]) Xs2= [c l Xs31
quicksort( [ I , [ 1 1.
append(Xs3, Ys , [
true
I) Xs3=[ 1 ,Ys=[ I partition( [XIXs] ,Y,[X ILsl ,Bs) -
X i Y, partition(Xs,~,Ls,Bs).
Output: (Xs=[a,b,c] ,Ys=[ 1 )
partition([XIXs] ,y,Ls,[XIBSI)
X > Y, partition(Xs,Y,Ls,Bs).
-
append(Xs2,Ys, [c] ) Xs2= [ I ,Ys=[cl l,Y,C I,[ 1).
true
Output: (Xs= [a,bl ,Ys=[cl ) quicksort([2,1,31 ,Qs)
partition( [1,31 ,2,Ls,Bs) Ls= [I I Lsll
append (Xsl ,Ys , [b ,c] Xsl=C I ,Ys=[b,c] 1 1 2
true partition( [31 ,2,Lsl,Bs)
Output: (Xs= [a1 ,Ys=[b,cl ) 3 5 2 f
partition([31 ,2,Lsl,Bs)
append(Xs,Ys, [a,b,cl) 3 > 2
true partition( [ I ,2,Lsl,Bsl)
Output: (Xs=[ 1 ,Ys=[a,b,cl) quicksort( [ll ,Qsl)
arti it ion( [ 1 ,1,Ls2,Bs2)
no (more) solutions quicksort( [ 1 ,Qs2)
quicksort( [ 1 ,Qs3)
Figure 6.2 Multiple solutions for splitting a list append( [ I , [I] ,Qsl)
quicksort( [31, Qs4)
arti it ion( [ I ,3,Ls3,Bs3) Ls3=[ l=Bs3
quicksort( [ 1 ,Qs5) Qs5=[ 1
The trace of append ( [ a , b] , [c ,dl ,Ls) ? giving the answer Ls= [ a , b ,c , quicksort( [ 1 ,Qs6) qss=r I
dl is precisely the trace given in Figure 4.3. Figure 4.5, giving the trace append( C I , Dl, qs4) 4s4= C31
for solving the Towers of Hanoi with three disks, is also a trace of append( [I] , [2,31 ,Qs) qs= [1 I Ysl
append([ I , [2,31,Ys) Ys= [2,31
the hanoi program considered as a Prolog program solving the query
true
hanoi ( s ( s ( s ( 0 ) ) ) , a , b , c , Ms) ?. The trace of a deterministic computa- Output: (QS=[I,2,31)
tion is the same when considered as a logic program or a Prolog program,
provided the order of goals is preserved. Figure 6.3 Tracing a q u i c k s o r t computation
The next example is answering the query append (Xs ,Y s , [a, b, cl ? >
with respect to Program 3.15 for append. There are several solutions of
the query. The search tree for thls goal was given as Figure 5.3. Figure 6 . 2
gives the Prolog trace.
Tracing computations is a good way to gain understanding of the ex-
ecution model of Prolog. We give a slightly larger example, sorting a
list with the quicksort program (Program 3.22, reproduced at the top of
Figure 6.3). Computations using q u i c k s o r t are essentially deterministic
and show the algorithmic behavior of a Prolog program. Figure 6.3 gives
a trace of the query q u i c k s o r t ( [ 2 , I ,3] ,Xs)?. Arithmetic comparisons
Chapter 6 Pure Prolog

are assumed to be unit operations, and the standard program for append procedure A
is used. call B1,
We introduce a distinction between shallow and deep backtraclung. call BZ,
Shallow backtracking occurs when the unification of a goal and a clause
fails, and an alternative clause is tried. Deep backtracking occurs when
the unification of the last clause of a procedure with a goal fails, and call B,,
control returns to another goal in the computation tree. end.
It is sometimes convenient to include, for the purpose of this defini- Recursive goal invocation in Prolog is similar in behavior and imple-
tion, test predicates that occur first in the body of the clause as part mentation to that of conventional recursive languages. The differences
of unification, and to classif). the backtracking that occurs as a result of show when backtracking occurs. In a conventional language, if a compu-
their failure as shallow. An example in Figure 6.3 is the choice of a new tation cannot proceed (e.g., all branches of a case statement are false), a
clause for the goal partition ( C31 , 2 , Lsl ,Bs) . runtime error occurs. In Prolog, the computation is simply undone to the
last choice made, and a different computation path is attempted.
6.1.1 Exercises for Section 6.1 The data structures manipulated by logic programs, terms, correspond
to general record structures in conventional programming languages.
(i) Trace the execution of daughter (X,haran)? with respect to Pro- The handling of data structures is very flexible in Prolog. Like Lisp, Prolog
gram 1.2. is a declaration-free, typeless language.
The major differences between Prolog and conventional languages in
(ii) Trace the execution of sort ( [3,1,21,Xs)? with respect to Pro-
the use of data structures arise from the nature of logical variables. Log-
gram 3.21.
ical variables refer to individuals rather than to memory locations. Con-
(iii) Trace the execution of sort ([3,1,21 ,Xs)? with respect to Pro- sequently, having once beed specified to refer to a particular individual,
gram 3.20. a variable cannot be made to refer to another individual. In other words,
logic programming does not support destructive assignment where the
contents of an initialized variable can change.
----- --- --

Data manipulation in logic programs is achieved entirely via the unifi-


6.2 Comparison to Conventional Programming Languages cation algorithm. Unification subsumes

A programming language is characterized b17its control and data ma- Single assignment
nipulation mechanisms. Prolog, as a general-purpose programming lan- Parameter passing
guage, can be discussed in these terms, as are conventional languages. Record allocation
In this section, we compare the control flow and data manipulation of
Read/write-once field-access in records
Prolog to that of Algol-like languages.
The control in Prolog programs is like that in conventional procedural We discuss the trace of the quicksort program in Figure 6.3, point-
languages as long as the computation progresses forward. Goal invoca- ing out the various uses of unification. The unification of the initial
tion corresponds to procedure invocation, and the ordering of goals in goal quicksort ( [2,1,31,qs) with the head of the procedure definition
the body of clauses corresponds to sequencing of statements. Specifi- quicksort ( [XI Xsl ,Ys) illustrates several features. The unification of
cally, the clause A -BI,. . .,B, can be viewed as the definition of a pro- [2,1,31 with the term CX I Xsl achieves record access to the list and also
cedure A as follows: selection of its two fields, the head and tail.
Chapter 6 127 Pure Prolog

The unification of [l ,31 with xs achieves parameter passing to the 6.3 Background
p a r t i t i o n procedure, because of the sharing of the variables. T h s gives
the first argument of p a r t i t i o n . Similarly, the unification of 2 with X The origins of Prolog are shrouded in mystery. All that is known is that
passes the value of the second parameter to p a r t i t i o n . the two founders, Robert Kowalslu, then at Edinburgh, and Alain Colmer-
Record creation can be seen with the unification of the goal p a r t i - auer at Marseilles worked on similar ideas during the early 1970% and
t i o n ( [ I , 33 , 2 , L s ,B s ) with the head of the partition procedure p a r t i- even worked together one summer. The results were the formulation of
t i o n ( [X I XS] ,z , [X 1 LS ll ,BS I ) . As a result, L s is instantiated the logic programming philosophy and computation model by Kowalski
to [1I L s l l . Specifically, L s is made into a list and its head is assigned (1974), and the design and implementation of the first logic program-
the value 1 , namely, record creation and field assignment via unifica- ming language Prolog, by Colmerauer and his colleagues (1973). Three
tion. recent articles giving many more details about the beginnings of Prolog
The recursive algorithm embodied by the q u i c k s o r t program can and logic programming are Cohen (1988), Kowalski (1988), and Colmer-
be easily coded in a conventional programming language using linked auer and Roussel(1993).
lists and pointer manipulation. As discussed, unification is achiev- A major force behind the realization that logic can be the basis of a
ing the effect of the necessary pointer manipulations. Indeed, the ma- practical programming language has been the development of efficient
nipulation of logical variables via unification can be viewed as an implementation techniques, as pioneered by Warren (1977). Warren's
abstraction of low-level manipulation of pointers to complex data compiler identified special cases of unification and translated them into
structures. efficient sequences of conventional memory operations. Good accounts
These analogies may provide hints on how to implement Prolog effi- of techniques for Prolog implementation, both interpretation and compi-
ciently on a von Neumann machine. Indeed, the basic idea of compilation lation, can be found in Maier and Warren (1988) and Ait-Kaci (1991).
of Prolog is to translate special cases of unification to conventional mem- Variations of Prolog with extra control features, such as IC-Prolog
ory manipulation operations, as specified previously. (Clark and McCabe, 1979), have been developed but have proved too
Conventional languages typically incorporate error-handling or excep- costly in runtime overhead to be seriously considered as alternatives to
tion-handling mechanisms of various degrees of sophstication. Pure Pro- Prolog. We will refer to particular interesting variations that have been
log does not have an error or exception mechanism built into its defi- proposed in the appropriate sections.
nition. The pure Prolog counterparts of nonfatal errors in conventional Another breed of logic programming languages, which indirectly
programs, e.g., a missing case in a case statement, or dividing by zero, emerged from IC-Prolog, was concurrent logic languages. The first was
cause failure in pure Prolog. the Relational Language (Clark and Gregory, 1981), followed by Concur-
Full Prolog, introduced in the following chapters, includes system rent Prolog (Shapiro, 1983b), PARLOG (Clark and Gregory, 1984), GHC
predicates, such as arithmetic and I/O, whch may cause errors. (Ueda, 1985), and a few other proposals.
Current Prolog implementations do not have sophsticated error- References for the variations mentioned in the text are, for Prolog-
handling mechanisms. Typically, on an error condition, a system pred- I1 (van Caneghem, 1982), IC-Prolog (Clark et al., 1982), and MU-Prolog
icate prints an error message and either fails or aborts the computa- (Naish, 1986). Aurora-Prolog is described in Disz et al. (1987), while a
tion. starting place for reading about AKL, a language emerging from Andorra-
T h s brief discussion of Prolog's different way of manipulating data Prolog is Janson and Haridi (1991).
does not help with the more interesting question: How does program- The syntax of Prolog stems from the clausal form of logic due to
ming in Prolog compare with programming in conventional program- Kowalski (1974). The original Marseilles interpreter used the terminol-
ming languages? That is the major underlying topic of the rest of this ogy of positive and negative literals from resolution theory. The clause
book -
A B 1 , .. . ,B, was written +A - B1 . . . - B , .
Chapter 6

David H. D. Warren adapted Marseilles Prolog for the DEC-10 at the Uni-
versity of Edinburgh, with help from Fernando Pereira. Their decisions
have been very influential. Many systems adopted most of the conven- Programming in Pure Prolog
tions of Prolog-10 (Warren et al., 1979), whch has become known more
generically as Edinburgh Prolog. Its essential features are described in
the widespread primer on Prolog (Clocksin and Mellish, 1984). T h s book
follows the description of Standard Prolog existing as Scowen (1991).
A paper by Cohen (1985) delves further into the relation between Pro-
log and conventional languages.

A major aim of logic programming is to enable the programmer to pro-


gram at a hgher level. Ideally one should write axioms that define the
desired relations, maintaining ignorance of the way they are going to
be used by the execution mechanism. Current logic programming lan-
guages, Prolog in particular, are still far away from allowing t h s ideal of
declarative programming. The specific, well-defined choices of how their
execution mechanisms approximate the abstract interpreter cannot be ig-
nored. Effective logic programming requires knowing and utilizing these
choices.
This chapter discusses the consequences of Prolog's execution model
for the logic programmer. New aspects of the programming task are
introduced. Not only must programmers come up with a correct and
complete axiomatization of a relation but they must also consider its
execution according to the model.

7.1 Rule Order

Two syntactic issues, irrelevant for logic programs, are important to con-
sider when composing Prolog programs. The rule order, or clause order,
of clauses in each procedure must be decided. Also the goal order of
goals in the bodies of each clause must be determined. The consequences
of these decisions can be immense. There can be orders of magnitude
of difference in efficiency in the performance of Prolog programs. In ex-
treme though quite common cases, correct logic programs will fail to give
solutions because of nontermination.
Chapter 7 Programming in Pure Prolog

parent(terach,abraham). parent(abraham,isaac). intuitive: X = l , X=2, X=3. With the rules swapped, the order is X=3, X=2,
parent (isaac ,jacob) . parent ( jacob ,benj amin) . X = l . The order of Program 3.12 is more intuitive and hence preferable.
- parent(X,Y). When the search tree for a given goal has an infinite branch, the or-
ancestor(X,Y)
ancestor(X,Z) - parent(X,Y), ancestor(Y,Z). der of clauses can determine if any solutions are given at all. Consider
the query append (Xs , Cc ,dl ,Ys) ? with respect to append. As can be seen
Program 7.1 Yet another family example
from the search tree in Figure 5.4, no solutions would be given. If, how-
ever, the append fact appeared before the append rule, an infinite number
of pairs X s ,Y s satisfying the query would be given.
The rule order determines the order in which solutions are found. There is no consensus as to how to order the clauses of a Prolog pro-
cedure. Clearly, the standard dictated in more conventional languages,
Changing the order of rules in a procedure permutes the branches of testing for the termination condition before proceeding with the iter-
in any search tree for a goal using that procedure. The search tree is ation or recursion is not mandatory in Prolog. This is demonstrated in
traversed depth-first. So permuting the branches causes a different order Program 3.15 for append as well as in other programs in thls book. The
of traversal of the search tree, and a different order of finding solutions. reason is that the recursive or iterative clause tests its applicability by
The effect is clearly seen when using facts to answer an existential query. unification. This test is done explicitly and independently of the other
With our biblical database and a query such as f a t h e r (X,Y)?, changing clauses in the procedure.
the order of facts will change the order of solutions found by Prolog. Clause order is more important for general Prolog programs than it
Deciding how to order facts is not very important. is for pure Prolog programs. Other control features, notably the cut to
The order of solutions of queries solved by recursive programs is also be discussed in Chapter 11, depend significantly on the clause order.
determined by the clause order. Consider Program 5.1, a simple bibli- When such constructs are used, clauses lose their independence and
cal database together with a program for the relationshp a n c e s t o r , re- modularity, and clause order becomes significant.
peated here as Program 7.1. In this chapter, for the most part, the convention that the recursive
For the query a n c e s t o r ( t e r a c h , X ) ? with respect to Program 7.1, the clauses precede the base clauses is adopted.
solutions will be given in the order, X=abraham, X=isaac, X=jacob, and
X=benjamin. If the rules defining a n c e s t o r are swapped, the solutions 7.1.1 Exercises for Section 7.1
will appear in a different order, namely, X=benj amin, X=j acob, X=isaac,
and X=abraham. (i) Verify the order of solutions for the query a n c e s t o r (abraham,X)?
The different order of a n c e s t o r clauses changes the order of searchng with respect to Program 7.1, and its variant with different rule order
the implicit family tree. In one order, Prolog outputs solutions as it goes for a n c e s t o r , claimed in the text.
along. With the other order, Prolog travels to the end of the family tree
and gives solutions on the way back. The desired order of solutions is (ii) What is the order of solutions for the query a n c e s t o r (X, benja-
min)? with respect to Program 7.1? What if the rule order for
determined by the application, and the rule order of a n c e s t o r is chosen
a n c e s t o r were swapped?
accordingly.
Changing the order of clauses for the member predicate (Program 3.12)
also changes the order of search. As written, the program searches the
list until the desired element is found. If the order of the clauses is 7.2 Termination
reversed, the program always searches to the end of the list. The order
of solutions will also be affected, for example, responding to the query Prolog's depth-first traversal of search trees has a serious problem. If
member (X , [ I , 2 , 3 1 ) ?. In the standard order, the order of solutions is the search tree of a goal with respect to a program contains an infinite
Chapter 7 Programming in Pure Prolog

branch, the computation will not terminate. Prolog may fail to find a the appropriate analysis, using the concepts of domains and complete
solution to a goal, even though the goal has a finite computation. structures introduced in Section 5.2, can determine which queries will
Nontermination arises with recursive rules. Consider adding a relation- terminate with respect to recursive programs.
s h p married (Male,Female) to our database of family relationshps. A Let us consider an example, Program 3.1 5 for appending two lists. The
sample fact from the biblical situation is married(abraham,sarah). A program for append is everywhere terminating for the set of goals whose
user querying the married relationship should not care whether males first and/or last argument is a complete list. Any append query whose
or females are first, as the relationship is commutative. The "obvious" first argument is a complete list will terminate. Similarly, all queries
way of overcoming the commutativity is adding a recursive rule mar- where the third argument is a complete list will terminate. The program
ried(X ,Y) - married(Y, X). If t h s is added to the program, no com- will also terminate if the first and/or third argument is a ground term
putation involving married would ever terminate. For example, the trace that is not a list. The behavior of append is best summed up by consid-
of the query married(abraham, sarah) ? is given in Figure 7.1. ering the queries that do not terminate, namely, when both the first and
Recursive rules that have the recursive goal as the first goal in the third arguments are incomplete lists that are unifiable.
body are known as left recursive rules. The problematic married axiom The condition for when a query to Program 3.12 for member terminates
is an example. Left recursive rules are inherently troublesome in Prolog. is also stated in terms of incomplete lists. A query does not terminate if
They cause nonterminating computations if called with inappropriate the second argument is an incomplete list. If the second argument of a
arguments. query to member is a complete list, the query terminates.
The best solution to the problem of left recursion is avoidance. The Another guaranteed means of generating nonterminating computa-
married relationship used a left recursive rule to express commutativity. tions, easy to overlook, is circular definitions. Consider the pair of rules
Commutative relationships are best handled differently, by defining a
parent (X,Y) - child (Y X) .
new predicate that has a clause for each permutation of the arguments
of the relationship. For the relationship married, a new predicate, are-
child (x,Y) - parent (Y,X) .
married (Person1 ,Person2), say, would be defined using two rules: Any computation involving parent or child, for example, parent
are-married (X,Y) - married (X,Y) . (haran,lot)?, will not terminate. The search tree necessarily contains

are-married(X,Y) - married(Y ,X) . an infinite branch, because of the circularity.

Unfortunately, it is not generally possible to remove all occurrences of 7.2.1 Exercises for Section 7.2
left recursion. All the elegant minimal recursive logic programs shown
in Chapter 3 are left recursive, and can cause nontermination. However, (i) Discuss the termination behavior of both programs in Program 3.13
determining prefixes and suffutes of lists.

married(X,Y) - married(Y,X).
(ii) Discuss the termination of Program 3 . 1 4 ~
for sublist
married(abraham,sarah).
married(abraham,sarah)
married(sarah, abraham) 7.3 Goal Order
married(abraham,sarah)
married(sarah,abraham) Goal order is more significant than clause order. It is the principal means
of specifying sequential flow of control in Prolog programs. The pro-
grams for sorting lists, e.g., Program 3.22 for quicksort, exploit goal
Figure 7.1 A nonterminating computation order to indicate the sequence of steps in the sorting algorithms.
Chapter 7 Programming in Pure Prolog

We first discuss goal order from the perspective of database program- a query such as g r a n d p a r e n t (X, i s a a c ) ?, the second rule finds the solu-
ming. The order of goals can affect the order of solutions. Consider tion more directly. If efficiency is important, then it is advisable to have
the query d a u g h t e r (X, h a r a n ) ? with respect to a variant of Program two distinct relationshps, g r a n d p a r e n t and g r a n d c h i l d , to be used ap-
1.2, where the order of the facts female (milcah) and female ( y i s c a h ) propriately at the user's discretion.
is interchanged. The two solutions are given in the order X=milcah, In contrast to rule order, goal order can determine whether computa-
X=yiscah. If the goal order of the d a u g h t e r rule were changed to be tions terminate. Consider the recursive rule for a n c e s t o r :
d a u g h t e r (X, Y) -
female (X) ,f a t h e r ( Y , X ) . , the order of the solutions
a n c e s t o r (X, Y) +- p a r e n t (X, Z) , a n c e s t o r (Z, Y) .
to the query, given the same database, would be X=yiscah, X=milcah.
The reason that the order of goals in the body of a clause affects If the goals in the body are swapped, the a n c e s t o r program becomes
the order of solutions to a query is different from the reason that the left recursive, and all Prolog computations with a n c e s t o r are nontermi-
order of rules in a procedure affects the solution order. Changing rule nating.
order does not change the search tree that must be traversed for a given The goal order is also important in the recursive clause of the quicksort
query. The tree is just traversed in a different order. Changing goal order algorithm in Program 3.22:
changes the search tree. q u i c k s o r t ( [X 1 Xsl ,Ys) -
Goal order determines the search tree. partiti~n(X , X~, L i t t l e s , B i g s ) ,
quicksort (Littles ,Ls),
Goal order affects the amount of searching the program does in solv- q u i c k s o r t (Bigs ,Bs) ,
ing a query by determining whch search tree is traversed. Consider the append(Ls, [X 1 B s l ,Ys) .
two search trees for the query son(X, h a r a n ) ?, given in Figure 5.2. They The list should be partitioned into its two smaller pieces before recur-
represent two different ways of finding a solution. In the first case, solu- sively sorting the pieces. If, for example, the order of the p a r t i t i o n goal
tions are found by searching for children of h a r a n and checlung if they and the recursive sorting goal is swapped, no computations terminate.
are male. The second case corresponds to the rule for son being written We next consider Program 3.16a for reversing a list:
with the order of the goals in its body swapped, namely, son(X,Y) +-

male (X) , p a r e n t (Y, X). Now the query is solved by searching through reverse([ I , [ I ) .
all the males in the program and checlung if they are chldren of ha- reverse ( [X ( X s ] ,Zs) - r e v e r s e (XS ,Ys) , append (Ys, [XI ,Zs) -
ran. If there were many male facts in the program, more search would
The goal order is significant. As written, the program terminates with
be involved. For other queries, for example, s o n ( s a r a h , X ) ? , the reverse goals where the first argument is a complete list. Goals where the first
order has advantages. Since s a r a h is not male, the query would fail more argument is an incomplete list give nonterminating computations. If the
quickly. goals in the recursive rule are swapped, the determining factor of the ter-
The optimal goal order of Prolog programs varies with different uses. mination of r e v e r s e goals is the second argument. Calls to r e v e r s e with
Consider the definition of g r a n d p a r e n t . There are two possible rules: the second argument a complete list terminate. They do not terminate if
g r a n d p a r e n t (X, Z) - p a r e n t (X,Y) , p a r e n t (Y Z, . the second argument is an incomplete list.
- p a r e n t (Y, Z) ,
9

(X, Z) 7 Y, . A subtler example comes from the definition of the predicate sub-
l i s t in terms of two append goals, specifying the sublist as a suf-
If you wish to find someone's grandson with the g r a n d f a t h e r relation- fur of a prefuc, as given in Program 3.14e. Consider the query sub-
ship with a query such as grandparent(abraham,X)?, the first of the l i s t ( [2,31 , [ I , 2 , 3 , 4 1 ) ? with respect to the program. The query is
rules searches more directly. If looking for someone's grandparent with reduced to append(AsXs ,Bs, [ I , 2 , 3 , 4 1 ) , append(As, [2,3l ,AsXs)?.
Chapter 7 Programming in Pure Prolog

This has a finite search tree, and the initial query succeeds. If Pro- duction means an extra branch in the search tree. The bigger the search
gram 3.14e had its goals reversed, the initial query would be reduced tree, the longer a computation will take. It is desirable in general to keep
to append (As, [2,3] ,AsXs) ,append( AsXs ,Bs , [ I , 2,3,41 I?. This leads the size of the search tree as small as possible.
to a nonterrninating computation because of the first goal, as illustrated Having a redundant program may cause, in an extreme case, exponen-
in Figure 5.4. tial increase in runtime, in the event of backtraclung. If a conjunction of
A useful heuristic for goal order can be given for recursive programs n goals is solved, and each goal has one redundant solution, then in the
with tests such as arithmetic comparisons, or determining whether two event of backtraclung, the conjunction may generate Z n solutions, thus
constants are different. The heuristic is to place the tests as early as possibly changing a polynomial-time program (or even a linear one) to be
possible. An example comes in the program for partition, which is part exponential.
of Program 3.22. The first recursive rule is One way for redundancy to occur in Prolog programs is by covering the
same case with several rules. Consider the following two clauses defining
the relation minimum.
The test X IY should go before the recursive call. This leads to a
smaller search tree.
In Prolog programming (in contrast, perhaps, to life in general) our goal
is to fail as quickly as possible. Failing early prunes the search tree and The query minimum(2,2,M)? with respect to these two clauses has a
brings us to the right solution sooner. unique solution M=2, which is given twice; one is redundant.
Careful specification of the cases can avoid the problem. The second
7.3.1 Exercises for Section 7.3 clause can be changed to

(i) Consider the goal order for Program 3.14e defining a sublist of
a list as a suffix of a prefix. Why is the order of the append
goals in Program 3.14e preferable? (Hint: Consider the query sub- Now only the first rule covers the case when the two numbers have equal
I values.
list (Xs, [a,b,cl )?.I I

! Similar care is necessary with the definition of partition as part of


(ii) Discuss the clause order, goal order, and termination behavior for II Program 3.22 for quicksort. The programmer must ensure that only one
substitute, posed as Exercise 3 . W . of the recursive clauses for partition covers the case when the number
being compared is the same as the number being used to split the list.
I Another way redundancy appears in programs is by having too many
Redundant Solutions special cases. Some of these can be motivated by efficiency. An extra fact
can be added to Program 3.15 for append, namely, append(Xs, [ I ,Xs),
An important issue when composing Prolog programs, irrelevant for to save recursive computations when the second argument is an empty
logic programs, is the redundancy of solutions to queries. The mean- list. In order to remove redundancy, each of the other clauses for append
ing of a logic program is the set of ground goals deducible from it. No would have to cover only lists with at least one element as their second
distinction is made between whether a goal in the meaning could be argument.
deduced uniquely from the program, or whether it could be deduced We illustrate these points when composing Program 7.2 for the relation
in several distinct ways. This distinction is important for Prolog when merge (Xs ,Ys,Zs), which is true if Xs and Ys are lists of integers sorted in
considering the efficiency of searchng for solutions. Each possible de- ascending order and Zs is the ordered list resulting from merging them.
Chapter 7 Programming in Pure Prolog

merge(Xs,Ys,Zs) -
Z s is an ordered list of integers obtained from
m e m b e r - c h e c k (X,Xs)
X is a member of the list Xs.
-
merging the ordered lists of integers Xs and Ys. member-check(X, [X 1 Xs1 ) .
merge(CXIXsl,CYlYsl,CXIZsl) -
X < Y , merge(Xs,[YIYsl ,Zs).
member-check()(, [Y IYsl) -
X # Y , member-check(X,Ys) .

merge(CXIXsl,[YIYsl,[X,XIZsl) - Program 7.3 Checking for list membership


X =:= Y , merge(Xs,Ys,Zs).
merge([XlXsl,[YIYsl,[YIZsl) -
X > Y , merge([XIXsl ,Ys,Zs).
merge([ I , CXIXsl, [XIXsl).
merge(Xs, [ I ,Xs).

Program 7.2 Merging ordered lists

There are three separate recursive clauses. They cover the three pos-
sible cases: when the head of the first list is less than, equal to, or
greater than the head of the second list. We discuss the predicates <,
=:=, and > in Chapter 8. Two cases are needed when the elements in ei-
ther list have been exhausted. Note that we have been careful that the
goal merge ( C 1 , [ 1 , [ I ) is covered by only one fact, the bottom one.
Redundant computations occur when using member to find whether
a particular element occurs in a particular list, and there are multiple
occurrences of the particular element being checked for in the list. For Figure 7.2 Variant search trees
example, the search tree for the query member (a, [a,b ,a, cl ) would have
two success nodes.
The redundancy of previous programs was removed by a careful con- We restrict use of Program 7.3 to queries where both arguments are
sideration of the logic. In t h s case, the member program is correct. If we ground. This is because of the way # is implemented in Prolog, discussed
want a different behavior, the solution is to compose a modified version in Section 11.3.
of member.
Program 7.3 defines the relation member-check (X ,Xs) whch checks p- -- --

whether an element X is a member of a list Xs. The program is a vari- 7.5 Recursive Programming in Pure Prolog
ant of Program 3.12 for member that adds a test to the recursive clause.
It has the same meaning but, as a Prolog program, it behaves differ- Lists are a very useful data structure for many applications written in
ently. Figure 7.2 shows the difference between the search trees for the Prolog. In this section, we revise several logic programs of Sections 3.2
identical query to the two programs. The left tree is for the goal mem- and 3.3 concerned with list processing. The chosen clause and goal or-
ber (a, [a, b ,a,cl ) with respect to Program 3.12. Note there are two suc- ders are explained, and their termination behavior presented. The section
cess nodes. The right tree is for the goal member-check(a, Ca,b,a,cl) also discusses some new examples. Their properties are analyzed, and a
with respect to Program 7.3. It has only one success node. reconstruction offered of how they are composed.
Chapter 7 P r o g r a m m i n g i n Pure Prolog

select-first (X,Xs,Y s ) -
Y s is the list obtained by removing the
n o n m e m b e r (X,Xs) -
X is not a member of the list Xs.
first occurrence of X from the list Xs.
s e l e c t - f i r s t ( X , [ X IXs] ,Xs) .
nonmember ( X , [Y I Ysl )
nonmember ( X , C 1 ) .
-
X f Y , nonmember (X ,Ys) .

select-first(X,[Y1Ysl,[YIZsl)
X
-
f Y , s e l e c t - f i r s t (X , Y s , Zs) . Program 7.5 Nonrnembershp of a list

Program 7.4 Selecting the first occurrence of an element from a list

Programs 3.12 and 3.1 5 for member and append, respectively, are cor- The goal order and the termination behavior of permutation are closely
rect Prolog programs as written. They are both minimal recursive pro- related. Computations of permutation goals where the first argument
grams, so there is no issue of goal order. They are in their preferred is a complete list will terminate. The query calls select with its sec-
clause order, the reasons for whch have been discussed earlier in this ond argument a complete list, whch terminates generating a complete
chapter. The termination of the programs was discussed in Section 7.2. list as its third argument. Thus there is a complete list for the recur-
Program 3.19 for select is analogous to the program for member: sive permutation goal. If the first argument is an incomplete list, the
permutation query will not terminate, because it calls a select goal
select (X, [XI Xsl ,Xs) .
select(X, [YIYS], [YIZsl) - select(X,YsyZs). that will not terminate. If the order of the goals in the recursive rule
for permutation is swapped, the second argument of a permutation
The analysis of select is similar to the analysis of member. There is no query becomes the significant one for determining termination. If it
issue of goal order because the program is minimal recursive. The clause is an incomplete list, the computation will not terminate; otherwise it
order is chosen to reflect the intuitive order of solutions to queries such will.
as select (X, [a,b,cl ,Xs), namely, {X=a,Xs=[b,c] 1 , {X=b,Xs=Ca,cll, A useful predicate using # is nonmember (X,Ys) which is true if X is not
{X=c ,Xs= [a,b] }. The first solution is the result of choosing the first a member of a list Ys. Declaratively the definition is straightforward: An
element, and so forth. The program terminates unless both the second element is a nonmember of a list if it is not the head and is a nonmember
and third arguments are incomplete lists. of the tail. The base case is that any element is a nonmember of the
A variant of select is obtained by adding the test X # Y in the recur- empty list. T h s program is given as Program 7.5.
sive clause. As before, we assume that # is only defined for ground argu- Because of the use of f , nonmember is restricted to ground instances.
ments. The variant is given as Program 7.4 defining the relation select- This is sensible intuitively. There are arbitrarily many elements that are
first(X,Xs,Ys). Programs 3.12 and 7.3 defining member and member- not elements of a given list, and also arbitrarily many lists not containing
check have the same meaning. Program 7.4, in contrast, has a different a given element. Thus the behavior of Program 7.5 with respect to these
meaning from Program 3.19. The goal select (a, [a,b,a,cl , [a,b,cl ) is queries is largely irrelevant.
in the meaning of select, whereas select-first(a, [a,b,a,cl , Ca,b, The clause order of nonmember follows the convention of the recursive
cl ) is not in the meaning of select-f irst. clause preceding the fact. The goal order uses the heuristic of putting the
The next program considered is Program 3.20 for permutation. The test before the recursive goal.
order of clauses, analogously to the clause order for append, reflects the We reconstruct the composition of two programs concerned with the
more likely mode of use: subset relation. Program 7.6 defines a relation based on Program 3.12
for member, and Program 7.7 defines a relation based on Program 3.19
permutation(Xs, [XI Ys] ) - select (X,Xs,Zs), permutation(~s,Ys). for select. Both consider the occurrences of the elements of one list in
permutation( [ I , [ I). a second list.
Chapter 7 Programming in P u r e Prolog

members(Xs,Ys) -
Each element of the list Xs is a member of the list Ys.
translate(Words,Mots) -
Mots is a list of French words that is the
members( [X 1 Xs] ,Ys) - member ( X,Ys) , members ( X S,Ys) . translation of the list of English words Words.
translate ( [Word I Words] , [Mot I Mots] ) -
members( [ 1 ,Ys).
dict(Word,Mot), translate(Words,Mots).
Program 7.6 Testing for a subset translate([: I , [ 1).
dict (the,le) . dict(dog,chien)
selects(Xs,Ys) -
The list Xs is a subset of the list Ys.
dict(chases,chasse). dict(cat,chat).

Program 7.8 Translating word for word

select(X,Ys,Zs) - See Program 3.19. lation. It assumes a dictionary of pairs of corresponding English and
French words, the relation scheme being dict (Word,Mot). The trans-
Program 7.7 Testing for a subset
lation is very naive, ignoring issues of number, gender, subject-verb
agreement, and so on. Its range is solving a query such as trans-
Program 7.6 defining members(Xs ,Ys) ignores the multiplicity of ele- late ( [the,dog, chases,the,cat1 ) ,X) ? with solution X= [le,chien,
ments in the lists. For example, members ( [b ,b] , [a,b ,c] is in the mean- chasse, le,chat]. T h s program can be used in multiple ways. English
ing of the program. There are two occurrences of b in the first list, but sentences can be translated to French, French ones to English, or two
only one in the second. sentences can be checked to see if they are correct mutual translations.
Program 7.6 is also restrictive with respect to termination. If either Program 7.8 is a typical program performing m a p p i n g , that is, convert-
the first or the second argument of a members query is an incomplete ing one list to another by applying some function to each element of the
list, the program will not terminate. The second argument must be a list. The clause order has the recursive rule(s) first, and the goal order
complete list because of the call to member, whde the first argument calls dict first, so as not to be left recursive.
must also be complete, since that is providing the recursive control. The We conclude this section with a discussion of the use of data structures
query members (Xs , [I,2,31) ? aslung for subsets of a given set does not in Prolog programs. Data structures are handled somewhat differently in
terminate. Since multiple copies of elements are allowed in Xs, there Prolog than in conventional programming languages. Rather than having
are an infinite number of solutions, and hence the query should not a global structure, all parts of whlch are accessible, the programmer
terminate. specifies logical relations between various substructures of the data.
Both these limitations are avoided by Program 7.7. The revised relation Talung a more procedural view, in order to build and modify struc-
is selects(Xs,Ys). Goals in the meaning of Program 7.7 have at most tures, the Prolog programmer must pass the necessary fields of the struc-
as many copies of an element in the first list as appear in the second. ture to subprocedures. These fields are used and/or acquire values dur-
Related to this property, Program 7.7 terminates whenever the second ing the computation. Assignment of values to the structures happens via
argument is a complete list. A query such as selects (Xs, [a,b, cl > has unification.
as solution all the subsets of a given set. Let us look more closely at a generic example - producing a single
We now consider a different example: translating a list of English output from some given input. Examples are the standard use of ap-
words, word for word, into a list of French words. The relation is trans- pend, joining two lists together to get a thlrd, and using Program 7.8 to
late(Words ,Mots), where Words is a list of English words and Mots the translate a list of English words into French. The computation proceeds
corresponding list of French words. Program 7.8 performs the trans- recursively. The initial call instantiates the output to be an incomplete
Programming in P u r e Prolog
Chapter 7

list [X I Xsl . The head X is instantiated by the call to the procedure, often
no-doubles(Xs,Ys) -
Y s is the list obtained by
removing
in unification with the head of the clause. The tail Xs is progressively in- duplicate elements from the list Xs.
stantiated whle solving the recursive call. The structure becomes fully
instantiated with the solution of the base case and the termination of the
no-doubles ( [X 1 Xsl ,Ys) -
computation. -
member(X,Xs), no-doubles(Xs,Ys).
no-doubles ( [X 1 Xsl , [X I YS] )
Consider appending the list [c,dl to the list [a,bl , as illustrated nonmember(X,Xs), no-doubles(Xs,Ys).
in Figure 4.3. The output Ls= [a,b, c ,dl is constructed in stages, as no-doubles( [ 1 , [ 1).
Ls= [a I Zsl , Zs= [blZsl] , and finally Zs1= [c,dl, when the base fact of nonmember (X ,Xs) - See Program 7.5.
append is used. Each recursive call partially instantiates the originally in-
Program 7.9 Removing duplicates from a list
complete list. Note that the recursive calls to append do not have access
to the list being computed. This is a t o p - d o w n construction of recursive
structures and is typical of programming in Prolog. may be preferred. This latter result is possible if the program is rewrit-
The top-down construction of recursive data structures has one limi- ten. Each element is deleted from the remainder of the list as it is found.
tation. Pieces of the global data structure cannot be referred to deeper In terms of Program 7.9, this is done by replacing the two recursive calls
in the computation. This is illustrated in a program for the relation no- by a rule
doubles (XXs ,Xs), which is true if Xs is a list of all the elements appear-
ing in the list XXs with all duplicates removed.
no-doubles ( [X I Xs] , [X I Ys] ) -
delete(X,Xs,Xsl), no-doubles(Xs1,Ys).
Consider trying to compose no-doubles top-down. The head of the
recursive clause will be The new program builds the output top-down. However, it is inefficient
no-doubles ( [X I Xsl , . . . ) - for large lists, as will be discussed in Chapter 13. Briefly, each call to
delete rebuilds the whole structure of the list.
where we need to fill in the blank. The blank is filled by calling no- The alternative to building structures top-down is building them
doubles recursively on Xs with output Ys and integrating Ys with X. If bottom-up. A simple example of bottom-up construction of data struc-
X has not appeared in the output so far, then it should be added, and the tures is Program 3.16b for reversing a list:
blank will be [X/Ysl.If X has appeared, then it should not be added and
the blank is Ys. This cannot be easily said. There is no way of knowing reverse (Xs ,Ys) - reverse (XS, 1 ,Ys)
[:
what the output is so far. reverse ( [X 1 Xs] ,Revs,Ys) - reverse (Xs , [X 1 ~ e v s l,Ys) .
A program for no-doubles can be composed by thinlung differently reverse([ I ,Ys,Ys).
about the problem. Instead of determining whether an element has al-
An extra argument is added to reverse/2 and used to accumulate the
ready appeared in the output, we can determine whether it will appear.
values of the reversed list as the computation proceeds. This procedure
Each element X is checked to see if it appears again in the tail of the list
for reverse builds the output list bottom-up rather than top-down. In
Xs. If X appears, then the result is Ys, the output of the recursive call to
the trace in Figure 7.3 solving the goal reverse ( [a,b ,cl ,Xs ) , the suc-
no-doubles. If X does not appear, then it is added to the recursive result.
This version of no-doubles is given as Program 7.9. It uses Program 7.5 cessive values of the middle argument of the calls to reverse/3 [ I,
[a], [b ,a1 , and [c ,b ,a1 represent the structure being built.
for nonmember.
A bottom-up construction of structures allows access to the partial
A problem with Program 7.9 is that the list without duplicates may not
results of the structure during the computation. Consider a relation nd-
have the elements in the desired order. For example, no-doubles ( [a,b,
reverse(Xs,Ys) combining the effects of no-doubles and reverse. The
c ,bl ,Xs) ? has the solution Xs= [a,c ,bl , where the solution Xs= [a, b ,cl
Chapter 7 147 Programming in P u r e Prolog

reverse( Ca,b,cl ,Xs) Accumulators can also be viewed as a special case of incomplete data
reverse( [ a , b ,cl , [ 1 ,Xs) structures, as is discussed in Chapter 15.
reverse( [b,c], [a],Xs)
reverse ( [cl , [b ,a1 ,Xs) 7.5.1 Exercise for Section 7.5
reverse([ I , Cc,b,al ,Xs) Xs=[c,b,al
true
(i) Write Program 7.9 for no-doubles, building the structure bottom-
Figure 7.3 Tracing a reverse computation UP.

nd-reverse (Xs,Y s ) - 7.6 Background


Ys is the reversal of the list obtained by
removing duplicate elements from the list Xs. Prolog was envisaged as a first approximation to logic programming,
nd-reverse(Xs,Ys) - nd-reverse(xs,[ 1 ,Ys). whch would be superseded by further research. Its control has always
nd-reverse( [XI Xs] ,Revs ,Ys) - been acknowledged as being limited and naive. An oft-cited slogan, cred-
mernber(X,Revs), nd-reverse(Xs,Revs,Ys). ited to Kowalslu (1979b),is "Algorithm = Logic + Control." The particular
nd-reverse( [XI Xs] ,Revs , Y s ) - control provided in pure Prolog was intended as just one solution on the
nonmember(X ,Revs) , nd-reverse(Xs, [XI ~ e v s ,Ys).
]
path to declarative programming and intelligent control. Time has shown
nd-reverse( [ 1 ,Ys,Ys) .
nonmember (X , X S ) - See Program 7.5. otherwise. The control of Prolog has proven adequate for a large range of
applications, and the language has not only endured but has blossomed.
Program 7.10 Reversing .with no duplicates Nonetheless, logic programming researchers have investigated other
forms of control. For example, LOGLISP (Robinson and Sibert, 1982) has
breadth-first traversal of the search tree, and IC-Prolog (Clark and Mc-
Cabe, 1979) has co-routining. MU-Prolog (Naish, 1986) allows suspension
meaning of nd-reverse is that Ys is a list of elements in Xs in reverse or-
to provide a correct implementation of negation and to prevent the com-
der and with duplicates removed. Analogously to reverse, nd-reverse
putation from searchmg infinite branches in certain cases. Wait declara-
calls nd_reverse/3 with an extra argument that builds the result bottom-
tions are generated (Naish, 1985b) that are related to the conditions on
up. This argument is checked to see whether a particular element ap-
termination of Prolog programs given in Section 7.2.
pears, rather than checking the tail of the list as in Program 7.9 for
A methodology for systematically constructing simple Prolog pro-
no-doubles. The program is given as Program 7.10.
grams is given in Deville (1990). Essential to Deville's methods are
We emphasize the characteristics of bottom-up construction illus-
specifications, a subject touched upon in Section 13.3.
trated here. One argument behaves as an accumulator of the final data
Analysis of Prolog programs, and logic programs more generally, has
structure. It is augmented in the recursive call, so that the more complex
become a hot topic of research. Most analyses are based on some form of
version is in the body of the clause rather than in its head. T h s contrasts
abstract interpretation, a topic beyond the scope of this book. The initial
with top-down construction, where the more complex version of the data
work in Prolog can be found in Mellish (1985), and a view of leading
structure being built is in the head of the clause. Another argument is
research groups can be found in a special issue of the Journal of Logic
used solely for returning the output, namely, the final value of the ac-
Programming (1993).
cumulator. It is instantiated with the satisfaction of the base fact. The
Extensive work has also appeared recently on analyzing termination of
argument is explicitly carried unchanged in the recursive call.
Prolog programs. A starting place for thls topic is Pliimer (1990).
The technique of adding an accumulator to a program can be general-
ized. It is used in Chapter 8 discussing Prolog programs for arithmetic.
Arithmetic

The logic programs for performing arithmetic presented in Section 3.1


are very elegant, but they are not practical. Any reasonable computer
provides very efficient arithmetic operations directly in hardware, and
practical logic programming languages cannot afford to ignore thls fea-
ture. Computations such as addition take unit time on most computers
independent of the size of the addends (as long as they are smaller than
some large constant). The recursive logic program for p l u s (Program 3.3)
takes time proportional to the first of the numbers being added. This
could be improved by switching to binary or decimal notation but still
won't compete with direct execution by dedicated hardware.
Every Prolog implementation reserves some predicate names for
system-related procedures. Queries to these predicates, called system
predicates, are handled by special code in the implementation in contrast
to calls to predicates defined by pure Prolog programs. A Prolog imple-
mentor should build system predicates that complement pure Prolog
naturally and elegantly. Other names for system predicates are evaluable
predicates, builtin predicates, or bips, the latter two being referred to in
the draft for Standard Prolog.

8.1 System Predicates for Arithmetic

The role of the system predicates for arithmetic introduced in Prolog is


to provide an interface to the underlying arithmetic capabilities of the
computer in a straightforward way. The price paid for t h s efficiency is
Chapter 8 Arithmetic

that some of the machne-oriented arithmetic operations are not as gen- What happens if the term to be evaluated is not a valid arithmetic ex-
eral as their logical counterparts. The interface provided is an arithmetic pression? An expression can be invalid for one of two reasons, whlch
evaluator, whch uses the underlying arithmetic facilities of the com- should be treated differently, at least conceptually. A term such as
puter. Standard Prolog has a system predicate is (Value,Expression) 3+x for a constant x cannot be evaluated. In contrast, a term 3+Y
for arithmetic evaluation. Goals with the predicate is are usually written for a variable Y may or may not be evaluable, depending on the value
in binary infix form, talung advantage of the operator facility of Prolog, of Y.
about whch we now digress. The semantics of any logic program is completely defined, and, in this
Operators are used in order to make programs more readable. People sense, logic programs cannot have runtime "errors." For example, the
are very flexible and learn to adjust to strange surroundings-they can goal X is 3+Y has solutions {X=3,Y=O}.However, when interfacing logic
become accustomed to reading Lisp and Fortran programs, for example. programs to a computer, the limitations of the machne should be taken
We believe nonetheless that syntax is important; the power of a good into account. A runtime error occurs when the machine cannot determine
notation is well known from mathematics. An integral part of a good the result of the computation because of insufficient information, that
syntax for Prolog is the ability to specify and use operators. is, uninstantiated variables. T h s is distinct from goals that simply fail.
Operators, for example # and <, have already been used in earlier Extensions to Prolog and other logic languages handle such "errors" by
chapters. Standard Prolog provides several operators, whch we intro- suspending until the values of the concerned variables are known. The
duce as they arise. Programmers can also define their own operators execution model of Prolog as introduced does not permit suspension.
using the built-in predicate op/3. An explanation of the mechanism for Instead of simply failure, we say an error condition occurs.
operator declarations, together with a list of pre-defined operators and The query (X is 3+x)? fails because the right-hand argument cannot
their precedences is given in Appendix B. be evaluated as an arithmetic expression. The query (X is 3+Y)? is an
Queries using the arithmetic evaluator provided by Prolog have the example of a query that would succeed if Y were instantiated to an arith-
form Value is Expression?. Queries to the evaluator are interpreted metic expression. Here an error condition should be reported.
as follows. The arithmetic expression Expression is evaluated and the A common misconception of beginning Prolog programmers is to re-
result is unified with Value. Once arithmetic evaluation succeeds, the gard is as taking the place of assignment as in conventional program-
query succeeds or fails depending on whether unification succeeds or ming languages. It is tempting to write a goal such as (N is N+I). This
fails. is meaningless. The goal fails if N is instantiated, or causes an error if N
Here are some examples of simple addition, illustrating the use and is a variable.
behavior of the evaluator. The query (X is 3+5)? has the solution X=8. Further system predicates for arithmetic are the comparison operators.
T h s is the standard use of the evaluator, instantiating a variable to the Instead of the logically defined <, I (written = <), >, 2 (written > =),
value of an arithmetic expression. The query (8 is 3+5)? succeeds. Hav- Prolog directly calls the underlying arithmetic operations. We describe
ing both arguments to is instantiated allows checlung the value of an the behavior of <; the others are virtually identical. To answer the query
arithmetic expression. (3+5 is 3+5)? fails because the left-hand argu- (A < B)?, A and B are evaluated as arithmetic expressions. The two
ment, 3+5,does not unify with 8,the result of evaluating the expression. resultant numbers are compared, and the goal succeeds if the result of
Standard Prolog specifies a range of arithmetic operations that should evaluating A is less than the result of evaluating B. Again, if A or B is not
be supported by Prolog for both integers and reals represented as an arithmetic expression, the goal will fail, and an error condition should
floating-point numbers. In particular, the evaluator provides for addi- result if A or B are not ground.
tion, subtraction, multiplication, and division (+, -, *, /) with their usual Here are some simple examples. The query (1 < 2)? succeeds, as
mathematical precedences. In this book, we restrict ourselves to integer does the query (3-2 < 2*3+1)?. On the other hand, (2 < I)? fails, and
arithmetic. (N < 1)? generates an error when N is a variable.
Chapter 8 Arithmetic

Tests for equality and inequality of values of arithmetic expressions -


factorial ( N , F )
F is the integer N factorial.
are implemented via the builtin predicates =: = and =/=, which evaluate
both of their arguments and compare the resulting values. factorial(N,F) -
N > 0 , N1 is N-1, factorial(Nl,Fl), F is N*Fl.
factorial(0,l).

8.2 Arithmetic Logic Programs Revisited Program 8.2 Computing the factorial of a number

Performing arithmetic via evaluation rather than logic demands a recon-


Program 3.7 for minimum, for example, can be used reliably only for find-
sideration of the logic programs for arithmetic presented in Section 3.1.
ing the minimum of two integers.
Calculations can certainly be done more efficiently. For example, finding
The other feature missing from Prolog programs for arithmetic is the
the minimum of two numbers can use the underlying arithmetic com-
recursive structure of numbers. In logic programs, the structure is used
parison. The program syntactically need not change from Program 3.7.
to determine whch rule applies, and to guarantee termination of compu-
Similarly, the greatest common divisor of two integers can be computed
tations. Program 8.2 is a Prolog program for computing factorials closely
efficiently using the usual Euclidean algorithm, given as Program 8.1.
corresponding to Program 3.6. The recursive rule is more clumsy than
Note that the explicit condition J > 0 is necessary to avoid multiple
before. The first argument in the recursive call of f a c t o r i a l must be cal-
solutions when J equals 0 and errors from calling mod with a zero ar-
culated explicitly rather than emerging as a result of unification. Further-
gument.
more, the explicit condition determining the applicability of the recursive
Two features of logic programs for arithmetic are missing from their
rule, N > 0, must be given. This is to prevent nonterminating computa-
Prolog counterparts. First, multiple uses of programs are restricted. Sup-
tions with goals such as f a c t o r i a l (-1 ,N)? or even f a c t o r i a l ( 3 , F ) ? .
pose we wanted a predicate p l u s (X ,Y,Z) that performed as before, built
Previously, in the logic program, unification with the recursive structure
using i s . The obvious definition is
prevented nonterminating computations.
Program 8.2 corresponds to the standard recursive definition of the
factorial function. Unlike Program 3.6, the program can be used only to
This works correctly if X and Y are instantiated to integers. However,
calculate the factorial of a given number. A f a c t o r i a l query where the
we cannot use the same program for subtraction with a goal such as
first argument is a variable will cause an error condition.
p l u s (3, X , 8) ?, which raises an error condition. Meta-logical tests are
We must modify the concept of correctness of a Prolog program to
needed if the same program is to be used for both addition and sub-
accommodate behavior with respect to arithmetic tests. Other system
traction. We defer this until meta-logical predicates are introduced in
predicates that generate runtime "errors" are handled similarly. A Prolog
Chapter 10.
program is totally correct over a domain D of goals if for all goals in D
Programs effectively become specialized for a single use, and it is
the computation terminates, does not produce a runtime error, and has
tricky to understand what happens when the program is used differently.
the correct meaning. Program 8.2 is totally correct over the domain of
goals where the first argument is an integer.
greatest~common~divisor ( X ,Y,Z) -
Z is the greatest common divisor of the integers X and Y 8.2.1 Exercisesfor Section8.2
greatest~common~divisor(I,O,I).
greatest~common~divisor(I,J,Gcd) - (i) The Nth triangular number is the sum of the numbers up to and in-
J > 0 , R is I mod J , g r e a t e s t ~ c o m m o n ~ d i v i s o r ( ~ , ~ , ~ ~ d ) .
cluding N. Write a program for the relation t r i a n g l e (N,T), where
Program 8.1 Computing the greatest common divisor of two integers T is the Nth triangular number. (Hint: Adapt Program 8.2.)
Chapter 8 Arithmetic

(ii) Write a Prolog program for power(X,N,V), where V equals xN. factorial ( N ) ;
I i s 0; T i s 1;
Whch way can it be used? (Hint: Model it on Program 3.5 for exp.)
while I < N do
(iii) Write Prolog programs for other logic programs for arithmetic given I i s 1 + 1; T i s T * I end;
r e t u r n T.
in the text and exercises in Section 3.1.
Figure 8.1 Computing factorials iteratively
(iv) Write a Prolog program to generate a Huffman encoding tree from a
list of symbols and their relative frequencies.

factorial (N,F) -
F is the integer N factorial.

8.3 Transforming Recursion into Iteration

In Prolog there are no iterative constructs as such, and a more general


concept, namely recursion, is used to specify both recursive and iterative
algorithms. The main advantage of iteration over recursion is efficiency, Program 8.3 An iterative factorial
mostly space efficiency. In the implementation of recursion, a data struc-
ture (called a stack frame) has to be maintained for every recursive call
that has not terminated yet. A recursive computation involving n recur- Pascal-like language using a while loop is given in Figure 8.1. Its iterative
sive procedure calls would require, therefore, space linear in n. On the behavior can be encoded directly in Prolog with an iterative program.
other hand, an iterative program typically uses only a constant amount Prolog does not have storage variables, whlch can hold intermediate
of memory, independent of the number of iterations. results of the computation and be modified as the computation pro-
Nevertheless, there is a restricted class of recursive programs that cor- gresses. Therefore, to implement iterative algorithms, whch require the
responds quite closely to conventional iterative programs. Under some storage of intermediate results, Prolog procedures are augmented with
conditions, explained further in Section 11.2 on tail recursion optimiza- additional arguments, called accumulators. Typically, one of the interme-
tion, such Prolog programs can be implemented with almost the same diate values constitutes the result of the computation upon termination
efficiency as iterative programs in conventional languages. For t h s rea- of the iteration. T h s value is unified with the result variable using the
son, it is preferable to express a relation using an iterative program, if unit clause of the procedure.
possible. In this section, we show how recursive programs can be made This technique is demonstrated by Program 8.3, which is a Prolog def-
iterative using accumulators. inition of f a c t o r i a l that mirrors the behavior of the while loop in Fig-
Recall that a pure Prolog clause is iterative if it has one recursive call F ) , which is true if F is the value of N
ure 8.1. It uses f a c t o r i a l ( I ,N,T,
in the body. We extend t h s notion to full Prolog, and allow zero or factorial, and I and T are the values of the corresponding loop variables
more calls to Prolog system predicates before the recursive call. A Prolog before the ( I + l ) t hiteration of the loop.
procedure is iterative if it contains only unit clauses and iterative clauses. The basic iterative loop is performed by the iterative procedure f a c t o -
Most simple arithmetic calculations can be implemented by iterative r i a l / 4 . Each reduction of a goal using f a c t o r i a l / 4 corresponds to an
programs. iteration of the while loop. The call of f a c t o r i a l / 4 by f a c t o r i a l / 2 cor-
Factorials can be computed, for example, in a loop where the numbers responds to the initialization stage. The first argument of f a c t o r i a l / 4 ,
up to the desired factorial are multiplied together. A procedure in a the loop counter, is set to 0.
Chapter 8 Arithmetic

factorial (N,F) -
F is the integer N factorial.
between(l,J,K) -
K is an integer between the integers I and J inclusive.
factorial(N,F) - factorial(N,l,F). between(I,J,I) -- I I J.
- I < J, I1 is I+1, between(Il,J,K).
factorial(N,T,F) -
N > 0, TI is T*N, N1 is N-1, factorial(Nl,TI,F)
between(I,J,K)

Program 8.5 Generating a range of integers


factorial(O,F,F).

Program 8.4 Another iterative factorial


sumlist (Is,Sum)
S u m is the sum
- of the list of integers Is.
The third argument of f a c t o r i a l / 4 is used as an accumulator of the sumlist([IIIs],Sum) - sumlist(Is,IsSum), Sum is I+IsSum.
running value of the product. It is initialized to 1 in the call to f a c - sumlist ( [ I ,O) .
t o r i a l / 4 by f a c t o r i a l / 2 . The handling of both accumulators in Pro-
Program 8.6a Summing a list of integers
gram 8.3 is a typical programming techmque in Prolog. It is closely re-
lated to the use of accumulators in Programs 3.16b and 7.10 for collect-
ing elements in a list.
Accumulators are logical variables rather than locations in memory.
sumlist (ls,Sum) -
Sum is the sum of the list of integers Is.
The value is passed between iterations, not an address. Since logical sumlist(Is,Sum) - sumlist(Is,O,Sum).
variables are "write-once," the updated value, a new logical variable, is
passed each time. Stylistically, we use variable names with the suffix 1,
sumlist ( [I I Is] ,Temp,Sum) -
Temp1 is Temp+I, sumlist(Is,Templ,Sum).
for example, TI and 11, to indicate updated values. sumlist( [ I ,Sum,Sum).
The computation terminates when the counter I equals N. The rule for
f a c t o r i a l / 4 in Program 8.3 no longer applies, and the fact succeeds.
Program 8.6b Iterative version of summing a list of integers using an accu-
mulator
With this successful reduction, the value of the factorial is returned.
This happens as a result of the unification with the accumulator in the
base clause. Note that the logical variable representing the solution, the A useful iterative predicate is between(1, J ,K), which is true if K is an
final argument of f a c t o r i a l / 4 , had to be carried throughout the whole integer between I and J inclusive. It can be used to generate nondeter-
computation to be set on the final call of f a c t o r i a l . This passing of ministically integer values within a range (see Program 8.5).This is useful
values in arguments is characteristic of Prolog programs and might seem in generate-and-test programs, explained in Section 14.1, and in failure-
strange to the newcomer. driven loops, explained in Section 12.5.
Program 8.3 exactly mirrors the while loop for factorial given in Fig- Iterative programs can be written for calculations over lists of integers
ure 8.1. Another iterative version of f a c t o r i a l can be written by count- as well. Consider the relation s u m l i s t ( I n t e g e r l i s t , Sum), where Sum is
ing down from N to 0, rather than up from 0 to N. The basic program the sum of the integers in the list I n t e g e r L i s t . We present two pro-
structure remains the same and is given as Program 8.4. There is an ini- grams for the relation. Program 8.6a is a recursive formulation. To sum
tialization call that sets the value of the accumulator, and recursive and a list of integers, sum the tail, and then add the head. Program 8.6b uses
base clauses implementing the while loop. an accumulator to compute the progressive sum precisely as Program 8.3
Program 8.4 is marginally more efficient than Program 8.3. In general, for f a c t o r i a l uses an accumulator to compute a progressive product.
the fewer arguments a procedure has, the more readable it becomes, and An auxiliary predicate, s u m l i s t / 3 , is introduced with an extra argument
the faster it runs. for the accumulator, whose starting value, 0, is set in the initial call to
Chapter 8 Arithmetic

inner-product (Xs,Ys,Value) -
Value is the inner product of the vectors
area (Chain,Area) -
Area is the area of thepolygon enclosed by the list of points
represented by the lists of integers Xs and Ys. Chain,where the coordinates of each point are represented by
-
inner-product ( [XIXsl , [Y I Ysl ,IP) a pair (X,Y) of integers.
area( [Tuple] ,0).
inner-product(Xs,Ys,IPl), IP is X*Y+IP1.
inner-product ( [ 1 , [ ],0) . -
area( [(XI ,Y1), (X2,Y2) IXYsl ,Area)
area( [(X2,Y2) 1 XYsl ,Areal),
Program 8.7a Computing inner products of vectors Area is (X1*~2-Y1*X2)/2 + Areal.

Program 8.8 Computing the area of polygons


inner-product (Xs,Ys,Value)
Value is the inner product
- of the vectors
represented by the lists of integers Xs and Ys. alent iterative programs is an interesting research question. Certainly it
inner-product(Xs,Ys,IP) - inner-product(Xs,Ys,O,IP). can be done for the simple examples shown here.
-
inner-product ( [XI Xs] , [Y I Ys] ,Temp,I P ) The sophistication of a Prolog program depends on the underlying
logical relation it axiomatizes. Here is a very elegant example of a simple
Temp1 is X*Y+Temp, inner-product(Xs,Ys,Templ,IP).
inner-product ( [ 1 , C I ,IP,IP) . Prolog program solving a complicated problem.
Consider the following problem: Given a closed planar polygon chain
Program 8.7b Computing inner products of vectors iteratively
{Pl,P2,.. .,P,}, compute the area of the enclosed polygon and the orienta-
tion of the chain. The area is computed by the line integral
sumlist/3. The sum is passed out in the final call by unification with the
base fact. The only difference between Program 8.6b and the iterative ver-
sions of factorial is that the recursive structure of the list rather than where the integral is over the polygon chain.
a counter is used to control the iteration. The solution is given in Program 8.8, whlch defines the relation
Let us consider another example. The inner product of two vec- area(Chain,Area). Chain is given as a list of tuples, for example,
tors Xi,Yi is the sum XI . Yl + . . . + X , . Y,. If we represent vectors as [(4,6),(4,2),(0,8),(4,6)].The magnitude of Area is the area of the poly-
lists, it is straightforward to write a program for the relation inner- gon bounded by the chain. The sign of Area is positive if the orientation
product (Xs ,Ys,IP), where IP is the inner product of Xs and Ys. Pro- of the polygon is counterclockwise, and negative if it is clockwise.
grams 8.7a and 8.7b are recursive and iterative versions, respectively. The query area( [(4,6) , (4,2) , (0,8) , (4,6)] ,Area)? has the s o h -
The iterative version of inner-product bears the same relation to the tion Area = -8. The polygon gains opposite orientation by reversing
recursive inner-product that Program 8.6b for sumlist bears to Pro- the order of the tuples. The solution of the query area( C (4,6) , (0,8) ,
gram 8.6a. (4,2),(4,6)l,Area)?isArea = 8.
Both Programs 8.7a and 8.7b are correct for goals inner-product (Xs, The program shown is not iterative. Converting it to be iterative is the
Ys, Zs), where xs and Ys are lists of integers of the same length. There subject of Exercise (v) at the end of the section.
is a built-in check that the vectors are of the same length. The programs An iterative program can be written to find the maximum of a list of
fail if Xs and Ys are of different lengths. integers. The relation scheme is maxlist (Xs ,Max), and the program is
The similarity of the relations between Programs 8.6a and 8.6b, and given as Program 8.9. An auxiliary predicate maxlist (Xs ,X ,Max) is used
Programs 8.7a and 8.7b, suggests that one may be automatically trans- for the relation that Max is the maximum of X and the elements in the
formed to the other. The transformation of recursive programs to equiv- list Xs. The second argument of maxlist/3 is initialized to be the first
Chapter 8 Arithmetic

maxlist (Xs,N) -
N is the maximum of the list of integers Xs.
length(Xs,N) -
N is the length of the list Xs.
maxlist( [XIXs] ,M) - maxlist (Xs,X,M) . length( [XIXs] ,N) length(xs,~l), N is N1+1.
-
+-

maxlist ([XIXsl ,Y,M) maximum(X,Y,Yl), maxlist(Xs,Y1 ,M). length( [ 1,O).


maxlist(C 1 ,M,M).
Program 8.1 1 Finding the length of a list
- X I Y.
maximum(X,Y,Y)
maximum(X,Y,X) - X > Y.

Program 8.9 Finding the maximum of a list of integers range(M,N,Ns)


N s is the list
-of integers between M and N inclusive.
range(~,N,[MINS] ) - M < N, MI is M+1, range (MI,N ,Ns) .
length (Xs,N) -
Xs is a list of length N.
range ( N , N, [Nl ) .

Program 8.12 Generating a list of integers in a given range


length([XIXs],N) - N > 0, N1 is N-1, length(xs,~l).
length( [ 1 ,0) .
Similar considerations about the intended use of a program occur
Program 8.10 Checking the length of a list
when trying to define the relation range (M,N,Ns), where Ns is the list
of integers between M and N inclusive. Program 8.12 has a specific use:
generating a list of numbers in a desired range. The program is totally
element of the list. Note that the maximum of an empty list is not defined correct over all goals range (M ,N ,Ns) where M and N are instantiated. The
by this program. program cannot be used, however, to find the upper and lower limits
The standard recursive program for finding the maximum of a list of of a range of integers, because of the test M < N. Removing this test
integers constitutes a slightly different algorithm. The recursive formula- would allow the program to answer a query range (M,N, [ I , 2,31) ?, but
tion finds the maximum of the tail of the list and compares it to the head then it would not terminate for the intended use, solving queries such as
of the list to find the maximum element. In contrast, Program 8.9 keeps range(1,3,Ns)?.
track of the running maximum as the list is traversed.
Program 3.1 7 for finding the length of a list is interesting, affording
8.3.1 Exercises for Section 8.3
several ways of translating a logic program into Prolog, each of which has
its separate features. One possibility is Program 8.10, which is iterative.
(i) Write an iterative version for triangle(N,T), posed as Exer-
Queries length(Xs,N)? are handled correctly if N is a natural number,
cise 8.2(i).
testing if the length of a list is N, generating a list of N uninstantiated
elements, or failing. The program is unsuitable, however, for finding (ii) Write an iterative kersion for power (X,N,V) , posed as Exercise
the length of a list with a call such as length( [1,2,31 ,N)?. This query 8.2(ii).
generates an error.
The length of a list can be found using Program 8.11. This program (iii) Rewrite Program 8.5 so that the successive integers are generated
cannot be used, however, to generate a list of N elements. In contrast to in descending order.
Program 8.10, the computation does not terminate if the first argument fiv) Write an iterative program for the relation timeslist (Integer-
is an incomplete list. Different programs for length are needed for the List,Product) computing the product of a list of integers, anal-
different uses. ogous to Program 8.6b for sumlist.
Chapter 8

(v) Rewrite Program 8.8 for finding the area enclosed by a polygon so
that it is iterative.
(vi) Write a program to find the minimum of a list of integers.
Structure Inspection
(vii) Rewrite Program 8.11 for finding the length of a list so that it is
iterative. (Hint: Use a counter, as in Program 8.3.)
(viii) Rewrite Program 8.12 so that the range of integers is built bottom-
up rather than top-down.

8.4 Background
Standard Prolog has several predicates related to the structure of terms.
The examples given in t h s chapter are small and do not especially ex- These predicates are used to recognize the different types of terms, to
ploit Prolog's features. Algorithms that are fundamentally recursive are decompose terms into their functor and arguments, and to create new
more interesting in Prolog. A good example of such a program is the Fast terms. T h s chapter discusses the use of predicates related to term struc-
Fourier Transform, for which efficient versions have been written in Pro- ture.
log.
A good place for reading about Huffman encoding trees for Exercise
8.2(iv)is Abelson and Sussman (1985).
9.1 Type Predicates
A program for transforming recursive programs to iterative ones,
whlch handles the examples in the text, is described in Bloch (1984).
Type predicates are unary relations that distinguish between the different
Program 8.8, computing the area of a polygon, was shown to us by
types of terms. System predicates exist that test whether a given term is
Martin Nilsson.
a structure or a constant, and further, whether a constant is an atom, an
integer or floating-point. Figure 9.1 gives the four basic type predicates
in Standard Prolog, together with their intended meanings.
Each of the basic predicates in Figure 9.1 can be regarded as an infi-
nite table of facts. The predicate i n t e g e r / l would consist of a table of
integers:
integer (0) . i n t e g e r (1) . i n t e g e r (-1) .
The predicate atom/l would consist of a table of atoms in the program:

The predicate compound/l would consist of a table of the function sym-


bols in the program with variable arguments, etc.
Chapter 9 Structure Inspection

integer(X)
atom(X) -- -
X is an integer.
X is an atom.
X is a floating-point number.
flatten(Xs,Ys)
Ys is a list
-
of the elements of Xs.
real(X)
compound(X) -
X is a compound term.
flatten( [XlXsl ,Ys) +

flatten(X,Ysl), flatten(Xs,Ys2), append(Ysl,Ys2,Ys)


Figure 9.1 Basic system type predicates f latten(X, [XI ) -
constant ( 0 , X# C 1 .
flatten([ I , [ 1).

Other type predicates can be built from the basic type predicates. For Program 9.la Flattening a list with double recursion
example, that a number is either an integer or floating-point can be rep-
resented by two clauses:
The simplest program for flattening uses double recursion. To flatten
number (X) - i n t e g e r (X) . an arbitrary list [XIXsl , where X can itself be a list, flatten the head of the
number (X) - r e a l (X) . list X, flatten the tail of the list X s , and concatenate the results:

Standard Prolog includes a predicate number/l effectively defined in


this way. It also includes a predicate atomic(X), which is true if X is an
atom or a number. In this book, we prefer to call the predicate con-
s t a n t / l . To run under Standard Prolog, the following clause may be
What are the base cases? The empty list is flattened to itself. A type
necessary: predicate is necessary for the remaining case. The result of flattening a
constant is a list containing the constant:
c o n s t a n t (XI -- atomic ()o
f l a t t e n ( X , [XI - constant (10, X # C I .
To illustrate the use of type predicates, the query i n t e g e r ( 3 ) ? would
The condition c o n s t a n t (X) is necessary to prevent the rule being used
succeed, but the query atom(3)? would fail. One might expect that a
when X is a list. The complete program for f l a t t e n is given as Pro-
call to a type predicate with a variable argument, such as i n t e g e r (X)?,
gram 9.la.
would generate different integers on backtracking. This is not practical
Program 9,la, although very clear declaratively, is not the most effi-
for implementation, however, and we would prefer that such a call re-
cient way of flattening a list. In the worst case, whlch is a left-linear tree,
port an error condition. In fact, Standard Prolog specifies that the call
the program would require a number of reductions whose order is qua-
i n t e g e r (XI? should fail.
dratic in the number of elements in the flattened list.
The only terms not covered by the predicates in Figure 9.1 are vari-
A program for f l a t t e n that constructs the flattened list top-down is a
ables. Prolog does provide system predicates relating to variables. The
little more involved than the doubly recursive version. It uses an auxiliary
use of such predicates, however, is conceptually very different from the
predicate f l a t t e n ( X s , S t a c k , Ys), where Y s is a flattened list containing
use of structure inspection predicates described in thls chapter. Meta-
the elements in Xs and a stack Stack to keep track of what needs to be
logical predicates (their technical name) are the subject of Chapter 10.
flattened. The stack is represented as a list.
We give an example of the use of a type predicate as part of a pro-
The call of f l a t t e n / 3 by f l a t t e n / 2 initializes the stack to the empty
gram for flattening a list of lists. The relation f l a t t e n ( X s , Y s ) is true if
list. We discuss the cases covered by f l a t t e n / % The general case is
Y s is the list of elements occurring in the list of lists Xs. The elements
flattening a list [XIXsl, where X is itself a list. In this case X s is pushed
of X s can themselves be lists or elements, so elements can be arbitrarily
onto the stack, and X is recursively flattened. The predicate l i s t (X) is
deeply nested. An example of a goal in the meaning of f l a t t e n is f l a t -
used to recognize a list. It is defined by the fact l i s t ( [XIXsl ) :
t e n ( [ [ a l , [b, [ c , d l l , e l , [ a , b , c , d , e l ) .
Structure Inspection
Chapter 9

flatten(Xs,Ys) -
Ys is a list of the elements of Xs.
9.1.1 Exercise for Section 9.1

(i) Rewrite Program 9.la for f l a t t e n ( X s , Y s ) to use an accumulator


f l a t t e n ( [ X I X s l ,S,Ys) -
l i s t (X) , f l a t t e n ( X , [XSIS] ,Ys).
instead of the call to append, keeping it doubly recursive.

flatten(CXIXs1 , S , [XIYsl) -
c o n s t a n t (XI, ~f [ I , f l a t t e n ( X s , S ,Ys) .
f l a t t e n ( [ 1 , [XIS] ,Ys) -
flatten(X,S,Ys). 9.2 Accessing Compound Terms
flatten([ I , [ I , [ I ) .
l i s t ( [XI Xsl ) Recognizing a term as compound is one aspect of structure inspection.
Program 9.lb Flattening a list using a stack Another aspect is providing access to the functor name, arity, and argu-
ments of a compound term. One system predicate for delving into com-
flatten( [XI XS] ,S ,Ys) - l i s t (X) , f l a t t e n ( X , [ X s IS1 yYs)
pound terms is f u n c t o r (Term, F , A r i t y ) . T h s predicate is true if Term is
a term whose principal functor has name F and arity A r i t y . For example,
When the head of the list is a constant other then the empty list, it is f u n c t o r (f a t h e r ( h a r a n , l o t ) , f a t h e r , 2 ) ? succeeds.
added to the output, and the tail of the list is flattened recursively: The functor predicate can be defined, analogously to the type pred-

f l a t t e n ( [ X I X s l , S , [XIYsl) -
c o n s t a n t (X) ,Xf [ 1 , f l a t t e n ( X s , S ,Ys) .
icates, by a table of facts of the form f u n c t o r (f (X1,. . . ,XN) ,f ,N) for
each functor f of arity N, for example, f u n c t o r (f a t h e r (X , Y ,f a t h e r ,
2), f u n c t o r (son (X, Y) ,s o n , 2), . . . . Standard Prolog considers constants
When the end of the list is reached, there are two possibilities, depending to be functors of arity 0, with the appropriate extension to the functor
on the state of the stack. If the stack is nonempty, the top element is table.
popped, and the flattening continues: Calls to f u n c t o r can fail for various reasons. A goal such as func-
t o r (f a t h e r (X ,Y) ,s o n , 2) does not unify with an appropriate fact in
the table. Also, there are type restrictions on the arguments of func-
If the stack is empty, the computation terminates: t o r goals. For example, the third argument of f u n c t o r , the arity of the
term, cannot be an atom or a compound term. If these restrictions are
violated, the goal fails. A distinction can be made between calls that fail
The complete program is given as Program 9.lb. and calls that should give an error because there are infinitely many so-
A general technique of using a stack is demonstrated in Program 9.lb. lutions, such as f u n c t o r (X,Y, 2 ) ? .
The predicate f u n c t o r is commonly used in two ways, term decompo-
The stack is managed by unification. Items are pushed onto the stack
sition and creation. The first use finds the functor name and arity of a
by recursive calls to a consed list. Items are popped by unifylng with
the head of the list and recursive calls to the tail. Another application given term. For example, the query f u n c t o r ( f a t h e r ( h a r a n , l o t ,X ,Y) ?
has the solution {X=father,Y=2}.The second use builds a term with a
of stacks appears in Programs 17.3 and 17.4 simulating pushdown au-
tomata. particular functor name and arity. A sample query is f u n c t o r (T,f a t h e r ,
2)? with solution T=f a t h e r (X ,Y) .
Note that the stack parameter is an example of an accumulator.
The reader can verify that the revised program requires a number of The companion system predicate to f u n c t o r is a r g (N ,Term, Arg) ,
whlch accesses the arguments of a term rather than the functor name.
reductions linear in the size of the flattened list.
Chapter 9 Structure Inspection

subterm(Sub,T e r m ) -
Sub is a subterm of the ground term
is found and used as a loop counter by the auxiliary subterm/3, whch
Term. iteratively tests all the arguments.
The first clause of subterm/3 decrements the counter and recursively
subterm(Term,Term).
subterm(Sub,Term) -
compound(Term) , functor ( T e r m , F , N ), subterm(N, Sub ,Term) .
calls subterm. The second clause covers the case when Sub is a subterm
of the Nth argument of the term.
subterm(N,S u b ,Term) -
N > 1 , N1 is N-1, subterm(Nl,Sub,Term)
The subterm procedure can be used in two ways: to test whether the
subterm(N,Sub,Term) -
arg(N ,Term,Arg) , subterm(Sub ,Arg).
first argument is indeed a subterm of the second; and to generate sub-
terms of a given term. Note that the clause order determines the order
in which subterms are generated. The order in Program 9.2 gives sub-
Program 9.2 Finding subterms of a term terms of the first argument before subterms of the second argument,
and so on. Swapping the order of the clauses changes the order of solu-
tions.
The goal arg (N ,Term,Arg) is true if Arg is the Nth argument of Term. For Consider the query subterm(a,f (X,Y) I?, where the second argument
example, arg ( 1,father (haran,lot) ,haran) is true. is not ground. Eventually the subgoal subterm(a,X) is reached. Thls suc-
Like functor/3, arg/3 is commonly used in two ways. The term de- ceeds by the first subterm rule, instantiating X to a. The subgoal also
composition use finds a particular argument of a compound term. A matches the second subterm rule, involung the goal compound(X), which
query exemplifying t h s use is arg (2,father (haran,lot) ,X) ? with so- generates an error. This is undesirable behavior.
lution X=lot. The term creation use instantiates a variable argument of a We defer the issues arising when performing structure inspection on
term. For example, the query arg(1, father (X,lot) ,haran) ? succeeds, nonground terms to Chapter 10, where meta-logical predicates with suit-
instantiating X to haran. able expressive power are introduced. For the rest of thls chapter, all
The predicate arg is also defined as if there is an infinite table of facts. programs are assumed to take only ground arguments unless otherwise
A fragment of the table is stated.
Program 9.2 is typical code for programs that perform structure in-
spection. We look at another example, substituting for a subterm in a
term.
The relation scheme for a general program for substituting subterms
Calls to arg fail if the goal does not unify with the appropriate fact in the is substitute (Old,New, OldTerm,NewTerm), where NewTerm is the result
table, for example, arg (1,father (haran,lot) ,abraham) . They also fail of replacing all occurrences of Old in OldTerm by New. Program 9.3 imple-
if the type restrictions are violated, for example, if the first argument is menting the relation generalizes substituting for elements in a list, posed
an atom. An error is reported with a goal such as arg(1, X, Y) . as Exercise 3.3(i) and the logic program (Program 3.26) substituting for
Let us consider an example of using functor and arg to inspect terms. elements in binary trees.
Program 9.2 axiomatizes a relation subterm (TI,T2), whch is true if TI is Program 9.3 is a little more complicated than Program 9.2 for sub-
a subterm of T2. For reasons that will become apparent later, we restrict term but conforms to the same basic pattern. The clauses for substi-
TI and T2 to be ground. tute/4 cover three different cases. The last, handling compound terms,
The first clause of Program 9.2 defining subterm/:! states that any term calls an auxiliary predicate substitute/5, whch iteratively substitutes
is a subterm of itself. The second clause states that Sub is a subterm of in the subterms. The arity of the principal functor of the term is used
a compound term Term if it is a subterm of one of the arguments. The as the initial value of a loop counter that is successively decremented
number of arguments, i.e., the arity of the principal functor of the term, to control the iteration. We present a particular example to illustrate
Chapter 9 Structure Inspection

substitute( Old,New,OldTerm,NewTerm) -
NewTerm is the result of replacing all occurrences of Old
substitute(cat ,dog,owns(jane ,cat) ,XI
f
X=owns (jane ,
cat)
constant (owns(jane ,cat))
in O l d T e r m by N e w . substitute(cat ,dog,owns(jane ,cat),x)
compound(owns(jane,cat))
substitute(Old,New,Old,New).
substitute(0ld,New,Term,Term)
constant (Term), Term # Old.
- functor(owns(jane, cat) ,F,N)
functor(X,owns,2)
F=owns,N=2
X=owns(Xl,X2)
substitute(0ld,New,Term,Terml)
compound(Term),
- substitute(2,cat,dog,owns(jane,cat),o~ns(~1,~2))
2 > 0
functor(Term,F,N), arg (2,owns (jane , cat ,Arg) Arg=cat
substitute (cat ,dog,cat , ~ r g l ) Argl=dog
functor (Terml ,F,N) ,
arg(2,owns(X1 ,X2),dog) X2=dog
substitute(N,Old,New,Term,Terml).
substitute(N,Old,New,Term,Terml)
N > 0,
- N1 is 2-1 N1=l
substitute(1 ,cat ,dog,owns(jane,cat), owns(~1,dog))
1 > 0
arg(N ,Term, Arg) , arg(l,owns(jane,cat) ,Arg2) Arg2= jane
substitute(Old,New,Arg,Argl) , substitute(cat,dog,jane,Arg3) Arg3= j ane
arg(N,Terml,Argl), constant (jane)
N1 is N-1, jane f cat
substitute(Nl,Old,New,Term,Terml). arg(1 ,owns(X1 ,dog),jane) XI= j ane
substitute(0,0ld,New,Term,Terml). N2 is 1-1 N2=0
substitute(0,cat,dog,owns(jane,cat),owns(jane,dog))
Program 9.3 A program for substituting in a term
o > o f
substitute(0,cat ,dog,owns(jane,cat) ,owns(jane ,dog))
true
the interesting points lurking in the code. A trace of the query substi- Output : (X=owns(jane ,dog))
tute (cat,dog, owns ( jane, cat) ,X)? is given in Figure 9.2.
The query fails to unify with the fact in Program 9.3. The second rule Figure 9.2 Tracing the substitute predicate
is also not applicable because owns ( jane ,cat) is not a constant.
The t h r d substitute rule is applicable to the query. The second call
of functor is interesting. Name and Arity have been instantiated to owns
and 2, respectively, in the previous call of functor, so thls call builds a in Prolog. Program 9.3 typifies how Prolog handles changing data struc-
term that serves as the answer template to be filled in as the computation tures. The new term is recursively built as the old term is being traversed,
progresses. T h s explicit term building has been acheved by implicit uni- by logically relating the corresponding subterms of the terms.
fication in previous Prolog programs. The call to substitute/5 succes- Note that the order of the second arg goal and the recursive call to
sively instantiates the arguments of Terml. In our example, the second substitute/5 can be swapped. The modified clause for substitute/5
argument of owns (XI,X2) is instantiated to dog, and then XI is instanti- is logically equivalent to the previous one and gives the same result
ated to jane. in the context of Program 9.3. Procedurally, however, they are radically
The two calls to arg serve different tasks in substitute/5. The first different.
call selects an argument, whle the second call of arg instantiates an Another system predicate for structure inspection is a binary operator
= . . , called, for historical reasons, univ. The goal Term =.. List succeeds
argument.
Substitution in a term is typically done by destructive assignment in if List is a list whose head is the functor name of the term Term and
conventional languages. Destructive assignment is not possible directly whose tail is the list of arguments of Term. For example, the query (fa-
ther (haran,lot) =. . [father,haran,lot] ) ? succeeds.
Chapter 9 Structure Inspection

s u b t e r m ( S u b ,T e r m ) -
Sub is a subterm of the ground term Term.
T e r m =.. List - containing the functor of
List is a list Term followed
by the arguments of T e r m .
subterm(Term,Term).
subterm(Sub,Term) - =. .
Term =. . [F I Argsl -
functor(Term,F ,N) , args ( 0 , ~ , ~ e r m , ~ r. g s )
compound(Term), Term [F I Args] , subterm-list ( ~ u b , ~ r g.s )
subterm-list(Sub,[ArglArgsl) - args ( I ,N ,Term,[Arg 1 Argsl ) -
I < N , I1 is I+1, arg(Il,Term,Arg), a r g s ( l l , ~ , ~ e r m , A r g s ) .
subterm(Sub,Arg).
subterm-list ( S u b ,[Arg I Args] ) - args(N,N,Term,[ I ) .
subterm-list(Sub,Args).
Program 9.5a Constructing a list corresponding to a term
Program 9.4 Subterm defined using univ

tive of F with respect to its argument is recursively calculated, as is the


derivative of G-X. These are combined to give the solution.
Like functor and arg, univ has two uses. Either it builds a term Univ can be defined in terms of functor and arg. Two different def-
given a list, for example, (X =. . [father,haran, lotl ) ? with solution initions are necessary, however, to cover both building lists from terms
X=f ather (haran,lot), or it builds a list given a term, for example, (fa- and building terms from lists. One definition does not suffice, because of
ther (haran,lot) =. . Xs)? with solution Xs= [father,haran, lotl. errors caused by uninstantiated variables. Other system predicates are
In general, programs written using functor and arg can also be written similarly precluded from flexible use.
with univ. Program 9.4 is an alternative definition of subterm,equivalent Program 9.5a behaves correctly for building a list from a term. The
to Program 9.2. As in Program 9.2, an auxiliary predicate investigates the functor F is found by the call to functor, and the arguments are re-
arguments; here it is subterm-list. Univ is used to access the list of cursively found by the predicate args. The first argument of args is a
arguments, Args, of whch subterms are recursively found by subterm- counter that counts up, so that the arguments will appear in order in the
list. final list. If Program 9.5a is called with Term uninstantiated, an error will
Programs using univ to inspect structures are usually simpler. How- be generated because of an incorrect call of functor.
ever, programs written with functor and arg are in general more effi- Program 9.5b behaves correctly for constructing a term from a list. The
cient than those using univ, since they avoid building intermediate struc- length of the list is used to determine the number of arguments. The
tures. term template is built by the call to functor, and a different variant of
A neat use of univ is formulating the chain rule for symbolic differ- args is used to fill in the arguments. Program 9.5b results in an error
entiation. The chain rule states that d/dx{f(g(x)} = d/dg(x){f(g(x)l x if used to build a list, because of the goal length(Args ,N) being called
d/dx{g(x)}.In Section 3.5, we noted that t h s rule could not be expressed with uninstantiated arguments.
as a single clause of a logic program as part of Program 3.30. A Prolog
rule encapsulating the chain rule is 9.2.1 Exercises for Section 9.2
derivative (F-G-X,X,DF*DG) - (i) Define a predicate occurrences (Sub,Term,N), true if N is the num-
F-G-X =. . [F,G-XI,
ber of occurrences of subterm Sub in Term. Assume that Term is
derivative(F-G-X,G-X,DF),
ground.
derivative(G-X,X,DG).
(ii) Define a predicate position(Subterm,Term,Position), where Po-
The function F-G-X is split up by univ into its function F and argument sition is a list of argument positions identifying Subterm withn
G-X, checking that F is a function of arity 1 at the same time. The deriva- Term. For example, the position of X in 2.sin(X) is [2,1l, since
Chapter 9

Tenn =.. List -


The functor of Term is the first element of the list List,
and its arguments are the rest of List's elements. Meta-Logical Predicates
Term = . . [F I Args]-
length(Args ,N), functor (Term,F,N) , args(Args ,Term,1)
-
args ( [Arg l Args] ,Term,N)
arg(N,Term,Arg), N1 is N+1, args(Args,Term,Nl).
args([ 1 ,Term,N).
length(Xs,N) - See Program 8.11.
Program 9.5b Constructing a term corresponding to a list

sin(X) is the second argument of the binary operator ".", and X A useful extension to the expressive power of logic programs is provided
is the first argument of sin(X). (Hint: Add an extra argument for by the meta-logical predicates. These predicates are outside the scope of
Program 9.2 for subterm,and build the position list top-down.) first-order logic, because they query the state of the proof, treat variables
(rather than the terms they denote) as objects of the language, and allow
(iii) Rewrite Program 9.5a so that it counts down. (Hint: Use an accumu- the conversion of data structures to goals.
lator.) Meta-logical predicates allow us to overcome two difficulties involving
the use of variables encountered in previous chapters. The first difficulty
(iv) Define functor and arg in terms of univ. How can the programs be
used? is the behavior of variables in system predicates. For example, evaluating
an arithmetic expression with variables gives an error. So does calling
(v) Rewrite Program 9.3 for substitute SO that it uses univ. type predicates with variable arguments. A consequence of this behavior
is to restrict Prolog programs to have a single use in contrast to the
multiple uses of the equivalent logic programs.
9.3 Background The second difficulty is the accidental instantiation of variables during
structure inspection. Variables need to be considered as specific objects
Prolog does not distinguish between object-level and meta-level type rather than standing for an arbitrary unspecified term. In Chapter 9 we
predicates. We have taken a different approach, by defining the type test handled the difficulty by restricting inspection to ground terms only.
predicates to work only on instantiated terms and by treating the meta- T h s chapter has four sections, each for a different class of meta-logical
logical test predicates (e.g., var/1, discussed in Section 10.1) separately. predicates. The first section discusses type predicates that determine
The predicates for accessing and constructing terms, functor, arg, and whether a term is a variable. The second section discusses term com-
=. . , originate from the Edinburgh family. The origin of =. . is in the old parison. The next sections describe predicates enabling variables to be
Prolog-10 syntax for lists, whch used the operator , . . instead of the manipulated as objects. Finally, a facility is described for converting data
current I in lists, e.g., [a,b, c, . .Xs] instead of [a,b,clXsl . The . . on into executable goals.
the right-hand side suggested or reminded that the right-hand side of
the equality is a list.
Several of the examples in this section were adapted from O'Keefe
(1983).
Exercises 9.2(i)and 9.2(ii) are used in the equation solver in Chapter 23.
1 76 Chapter 10 me fa-Logical Predicates

length(Xs,N) -
10.1 Meta-Logical Type Predicates The list X s has length N.

The basic meta-logical type predicate is var (Term), which tests whether
length(Xs ,N)
length(Xs,N)
-- nonvar(Xs) , lengthl (Xs,N).
var (Xs) , nonvar (N) , lengthZ(Xs ,N).
a given term is at present an uninstantiated variable. Its behavior is simi- lengthl (XS , N ) - See Program 8.11.
lar to the type predicates discussed in Section 9.1. The query var (Term)? lengthZ(Xs,N) - See Program 8.10.
succeeds if Term is a variable and fails if Term is not a variable. For exam-
Program 10.2 A multipurpose length program
ple, v a r (X)? succeeds, whereas both var ( a ) ? and v a r ( [XIXsl ) ? fail.
The predicate var is an extension to pure Prolog programs. A table
cannot be used to give all the variable names. A fact var (X) means that
example. To partition a number involves generating numbers, for which
all instances of X are variables rather than that the letter X denotes a
a different program is needed. This is posed as Exercise (ii) at the end of
variable. Being able to refer to a variable name is outside the scope of
this section.
first-order logic in general or pure Prolog in particular.
Meta-logical goals placed initially in the body of a clause to decide
The predicate nonvar(Term) has the opposite behavior to var. The
which clause in a procedure should be used are called meta-logical tests.
query nonvar (Term) ? succeeds if Term is not a variable and fails if Term
Program 10.1 for p l u s is controlled by meta-logical tests. These tests re-
is a variable.
fer to the current state of the computation. Knowledge of the operational
The meta-logical type predicates can be used to restore some flexibility
semantics of Prolog is required to understand them.
to programs using system predicates and also to control goal order. We
Standard Prolog in fact endows the type predicates with a meta-logical
demonstrate this by revising some programs from earlier chapters.
ability. For example, if X is a variable the goal i n t e g e r (XI fails, rather
Consider the relation p l u s (X ,Y ,Z) . Program 10.1 is a version of p l u s
than giving an error. T h s enables the rules from Program 10.1 to be writ-
that can be used for subtraction as well as addition. The idea is to check
ten using the system predicate i n t e g e r rather than nonvar, for example,
which arguments are instantiated before calling the arithmetic evaluator.
For example, the second rule says that if the first and third arguments,
X and Z,are not variables, the second argument, Y, can be determined as
p l u s (X,Y, Z) - i n t e g e r (X) , i n t e g e r ( Y ) , Z i s X+Y

their difference. Note that if the arguments are not integers, the evalua- We feel it is preferable to separate type checlung, whlch is a perfectly le-
tion will fail, the desired behavior. gitimate first-order operation, from meta-logical tests, which are a much
The behavior of Program 10.1 resembles that of Program 3.3, the logic stronger tool.
program for p l u s . Further, it does not generate any errors. Nonetheless, Another relation that can have multiple uses restored is length(Xs , N )
it does not have the full flexibility of the recursive logic program: it determining the length N of a list Xs. Separate Prolog programs (8.10 and
cannot be used to partition a number into tw7o smaller numbers, for 8.11) are needed to find the length of a given list and to generate an
arbitrary list of a given length, despite the fact that one logic program
(3.17)performs both functions. Program 10.2 uses meta-logical tests to
plus(X,Y,Z) -- define a single l e n g t h relation. The program has an added virtue over
The sum of the numbers X and Y is Z. Programs 8 . 1 0 and 8.11. It avoids the non-terminating behavior present
plus(X,Y,Z) - nonvar(X1, nonvar(~), Z is X+Y. in both, when both arguments are uninstantiated.
plus(X,Y,Z) - nonvar(X), nonvar(z), Y is Z-X.
plus(X,Y,Z) - nonvar(Y), nonvar(z), X is Z-Y. Meta-logical tests can also be used to make the best choice of the goal
order of clauses in a program. Section 7.3 discusses the definition of
Program 10.1 Multiple uses for plus grandparent:
C h a p t e r 10 Meta-Logical Predicates

g r a n d p a r e n t (X,Z)
X is the grandparent
- of Z.
It calls an auxiliary predicate ground/2, which iteratively checks that all
the arguments of the structure are ground.
g r a n d p a r e n t (X, Z) -- nonvar (X) , p a r e n t ( X,Y) , p a r e n t (Y ,Z) .
nonvar (Z) , p a r e n t (Y, Z) , p a r e n t (X,Y).
We look at a more elaborate example of using meta-logical type predi-
g r a n d p a r e n t (X,Z) cates; writing a unification algorithm. The necessity of Prolog to support
unification for matchng goals with clause heads means that explicit uni-
Program 10.3 A more efficient version of grandparent
fication is readily available. Prolog's underlying unification can be used
to give a trivial definition
ground( Term) -
Term is a ground term. u n i f y (X ,X) .
ground(Term1 - whch is the definition of the system predicate =/2, namely, X=X.
nonvar(Term), c o n s t a n t ( T e r m ) .
ground(Term)
nonvar(Term),
- Note that t h s definition depends on Prolog's underlying mechanism
for unification, and hence does not enforce the occurs check.
compound(Term), A more explicit definition of Prolog's unification is possible using meta-
f u n c t o r (Term, F , N) , logical type predicates. Although more cumbersome and less efficient,
ground(N,Term). t h s definition is useful as a basis for more elaborate unification algo-
rithms. One example is unification with the occurs check, described in
Section 10.2. Another example is unification in other logic programming
languages that can be embedded in Prolog, such as read-only unification
of Concurrent Prolog.
Program 10.5 is an explicit definition of unification. The relation
u n i f y (Terml ,Term2) is true if Terml unifies with Term2. The clauses
of u n i f y outline the possible cases. The first clause of the program says
Program 10.4 Testing if a term is ground
that two variables unify. The next clause is an encapsulation of the rule
for unification that if X is a variable, then X unifies with Y.
grandparent (X,Z) - p a r e n t (X,Y) , p a r e n t (Y ,Z) . The other case bearing discussion in Program 10.5 is unifying two com-
pound terms, as given in the predicate term-unif y (X ,Y) . This predicate
The optimum goal order changes depending on whether you are search- checks that the two terms X and Y have the same principal functor and
ing for the grandchildren of a given grandparent or the grandparents of arity, and then checks that all the arguments unify, using unif y-args, in
a given grandchld. Program 10.3 is a version of grandparent that will a way similar to the structure inspection programs shown before.
search more efficiently.
The basic meta-logical type predicates can be used to define more in- 10.1.1 Exercises for Section 10.1
volved meta-logical procedures. Consider a relation ground(Term) , whch
is true if Term is ground. Program 10.4 gives a definition. Write a version of Program 8.12 for range that can be used in mul-
(i)
The program is in the style of the programs for structure inspection tiple ways.
given in Section 9.2, in particular Program 9.3 for s u b s t i t u t e . The two
clauses for ground/l are straightforward. In both cases, a meta-logical (ii) Write a version of Program 10.1 for p l u s that partitions a number
test is used to ensure that no error is generated. The first clause says as well as performing addition and subtraction. (Hint: Use between
that constant terms are ground. The second clause deals with structures. to generate numbers.)
Chapter 10 Meta-LogicalPredicates

unify(Term1 ,Term2) - unify( Term1,TermZ) -


Terml and T e r m 2 are unified with the occurs check.
Terml and T e r m 2 are unified, ignoring the occurs check.
unify(X,Y) - unify(X,Y) -
var(X), var(Y), X=Y.
var (X) , var (Y) , X=Y.
unify(X,Y) - unify(X,Y) -
var(X), nonvar(Y1, not-occurs-in(X,Y), X=Y.
var(X), nonvar(Y1, X=Y.
unify(X,Y) - unify(X,Y) -
var(Y), nonvar(X1, not-occurs-in(Y,X), Y=X.
var (Y) , nonvar (X) , Y=X .
unify(X,Y) - unify(X,Y) -
nonvar(X) , nonvar(Y1, constant(x) , constant(Y) , X=Y.
nonvar (X) , nonvar (Y) , constant(x) , constant(Y) , X=Y .
unify(X,Y) - unify(X,Y) -
nonvar (X) , nonvar (Y) , compound(X) , compound(^) , term-unif (x,Y)
nonvar (X) , nonvar (Y) , compound(X), compound(^) , term-unify(x ,Y).
term-unify(X,Y) - not-occurs-in(X,Term) -
The variable X does not occur in Term
functor(X,F,N), functor(Y,F,N), unify-args(N,X,Y).
unify-args(N,X,Y) -
N > 0 , unify-arg(N,X,Y), N1 is N-1, unify-args(N1,x,Y).
unify-args(O,X,Y).
unify-arg(N,X,Y) -
arg(N ,X,ArgX) , arg(N ,Y,ArgY), unify .

Program 10.5 Unification algorithm

10.2 Comparing Nonground Terms term-unify(X,Y) - See Program 10.5.


Program 10.6 Unification with the occurs check
Consider the problem of extending the explicit unification program, Pro-
gram 10.5, to handle the occurs check. Recall that the occurs check is
part of the formal definition of unification, whch requires that a variable The predicate \== can be used to define a predicate not-occurs-
not be unified with a term containing this variable. In order to implement in(Sub,Term), which is true if Sub does not occur in Term,the relation
it in Prolog, we need to check whether two variables are identical (not just that is needed in the unification algorithm with the occurs check. not-
unifiable, as any two variables are). This is a meta-logical test. occurs-in(Sub,Term) is a meta-logical structure inspection predicate. It
Standard Prolog provides a system predicate, ==/2, for this purpose. is used in Program 10.6, a variant of Program 10.5, to implement unifica-
The query X == Y? succeeds if X and Y are identical constants, identical tion with the occurs check.
variables, or both structures whose principal functors have the same Note that the definition of not-occurs-in is not restricted to ground
name and arity, and recursively xi == Yi?succeeds for all corresponding terms. Lifting the restriction on Program 9.2 for subterm is not as
arguments Xiand Yiof X and Y. The goal fails otherwise. For example, X easy. Consider the query subterm(X ,Y) ?. This would succeed using Pro-
== 5? fails (in contrast to X = 5?). gram 9.2, instantiating X to Y.
There is also a system predicate that has the opposite behavior to ==. We define a meta-logical predicate occurs-in(Sub,Term) that has the
The query X \== Y? succeeds unless X and Y are identical terms. desired behavior.
Chapter 10 Meta-Logical Predicates

occurs-in (Sub,Term) - covers t h s behavior. However, the goal would fail because a variable is
Sub is a subterm of the (possiblynonground) term Term. not a constant.
a: Using == We can prevent the first (logical) solution by using a meta-logical test
occurs-in(X,Term) -
subterm(Sub,Term), X == Sub.
to ensure that the term being substituted in is ground. The unification
implicit in the head of the clause is then only performed if the test
b: Using freeze succeeds, and so must be made explicit. The base fact becomes the rule
occurs-in(X,Term) - substitute(Old,New,Term,New) -- ground(Term), Old = Term.
freeze (X ,Xf ) , freeze (Term,Termf , subterm(Xf, Termf ) .
subterm(X,Term) - See Program 9.2. Treating a variable as different from a constant is handled by a special
rule, again relying on a meta-logical test:
Program 10.7 Occurs in

The predicate == allows a definition of occurs-in based on Pro- Adding the two preceding clauses to Program 9.3 for substitute and
gram 9.2 for subterm. All the subterms of the given term are generated adding other meta-logical tests allows the program to handle nonground
on backtracking and tested to see if they are identical to the variable. terms. However, the resultant program is inelegant. It is a mixture of
The code is given in Program 10.7a. procedural and declarative styles, and it demands of the reader an under-
As defined, subterm works properly only for ground terms. However, standing of Prolog's control flow. To make a medical analogy, the syrnp-
by adding meta-logical type tests, as in the definition of not-occurs-in toms have been treated (undesirable instantiation of variables), but not
in Program 10.6, this problem is easily rectified. the disease (inability to refer to variables as objects). Additional meta-
logical primitives are necessary to cure the problem.
The difficulty of mixing object-level and meta-level manipulation of
terms stems from a theoretical problem. Strictly spealung, meta-level
10.3 Variables as Objects programs should view object-level variables as constants and be able to
refer to them by name.
The delicate handling of variables needed to define occurs-in in Sec-
We suggest two system predicates, freeze (Term,Frozen) and melt
tion 10.2 highlights a deficiency in the expressive power of Prolog. Vari-
(Frozen,Thawed), to allow explicit manipulation of variables. Freezing a
ables are not easily manipulated. When trying to inspect, create, and
term Term makes a copy of the term, Frozen, where all the uninstantiated
reason about terms, variables can be unwittingly instantiated.
variables in the term become unique constants. A frozen term looks like,
A similar concern occurs with Program 9.3 for substitute. Consider
and can be manipulated as, a ground term.
the goal substitute(a,b,X,Y, substituting a for b in a variable X to
Frozen variables are regarded as ground atoms during unification. Two
give Y. There are two plausible behaviors for substitute in t h s case.
frozen variables unify if and only if they are identical. Similarly, if a
Logically there is a solution when X is a and Y is b. T h s is the solution
frozen term and an uninstantiated variable are unified, they become an
actually given by Program 9.3, acheved by unification with the base fact
identical frozen term. The behavior of frozen variables in system predi-
substitute(Old,New,Old,New).
cates is the behavior of the constants. For example, arithmetic evaluation
In practice, another behavior is usually preferred. The two terms X and
involving a frozen variable will fail.
a should be considered different, and Y should be instantiated to X. The
The predicate freeze is meta-logical in a simdar sense to var. It en-
other base case from Program 9.3,
ables the state of a term during the computation to be manipulated di-
substitute (Old,New ,Term,Term) - constant (Term) , Term f Old. rectly.
Chapter 10 Meta-Logical Predicates

The predicate freeze allows an alternative definition of occurs-in numbewars(Term,Nl,NZ) -


The variables in Term are numbered from N1 to N 2 - 1.
from the one given in Section 10.2. The idea is to freeze the term so that
variables become ground objects. T h s makes Program 9.2 for subterm, numbervars('$VAR'(N),N,NI)
which works correctly for ground terms, applicable. The definition is
given as Program 10.7b.
N1 is N+1.
numbervars(Term,N,N) -
Freezing gives the ability to tell whether two terms are identical. Two
frozen terms, X and Y, unify if and only if their unfrozen versions are
numbervars(Term,Nl ,N2) -
nonvar(Term), constant(Term1.

nonvar (Term) , compound(Term) ,


identical, that is, X == Y. This property is essential to the correct behav- functor (Term,Name, N) ,
ior of Program 10.7b. numbervars(O,N,Term,Nl,N2).
The difference between a frozen term and a ground term is that the
frozen term can be "melted back" into a nonground term. The compan- I
numbervars(N,N,Term,N1,NI).
numbervars(I,N,Term,Nl,N3)
I < N
-
ion predicate to freeze is melt (Frozen,Thawed). The goal melt (X ,Y) I1 is I+1,
produces a copy Y of the term X where frozen variables become regular
Prolog variables. Any instantiations to the variables in X during the time 1I arg(I1 ,Term,Arg) ,
numbervars (Arg,Nl,N2) ,
when X has been frozen are taken into account when melting Y. numbervars(Il,N,Term,N2,N3).
The combination of freeze and melt allows us to write a variant of I
Program 10.8 Numbering the variables in a term
substitute, non-ground-substitute, where variables are not acciden-
tally instantiated. The procedural view of non-ground-substitute is as
follows. The term is frozen before substitution; the substitution is per- will be useful nonetheless in expressing and explaining the behavior of
formed on the frozen term using the version of substitute,which works extra-logical predicates, discussed in Chapter 12.
correctly on ground terms; and then the new term is melted: A useful approximation to freeze is the predicate numbervars (Term,
non-ground-substitute(X,Y,Old,New) - Nl ,N2), which is provided in many Edinburgh Prolog libraries. A call to
the predicate is true if the variables appearing in Term can be numbered
freeze(Old,Oldl), substitute(X,Y ,Old1 ,old21 ,
from Nl to N2-1. The effect of the call is to replace each variable in the
melt (0ld2,New) .
term by a term of the form '$VARJ (N) where N lies between Nl and N2.
The frozen term can also be used as a template for making copies. For example, the goal numbervars (append( [XIXsl ,Ys, [XIZsl , I,N) suc-
The system predicate melt-new (Frozen,Term) makes a copy Term of the ceeds with the substitution {X='$VAR(I) ' , Xs='$VAR' (2) , Ys='$VAR7
term Frozen,where frozen variables are replaced by new variables. (3), Zs='$VAR1 ( 4 ) , N=5}. Code implementing numbervars is given as
One use of melt-new is to copy a term. The predicate copy (Term,Copy) Program 10.8. It is in the same style as the structure inspection utilities
produces a new copy of a term. It can be defined in a single rule: given in Chapter 9.

copy (Term,Copy) - freeze ( ~ e r mFrozen)


, , melt-new(~roze~9

Standard Prolog provides the predicate copy-term(Term1 ,Term2) for 10.4 The Meta-Variable Facility
copying terms. It is true if and only if Term2 unifies with a term T that is
a copy of Terml except that all the variables of Terml have been replaced A feature of Prolog is the equivalence of programs and data - both
by fresh variables. can be represented as logical terms. In order for this to be exploited,
Unfortunately, the predicates freeze/2, melt/2, and rnelt_new/2 as programs need to be treated as data, and data must be transformed into
described here are not present in existing Prolog implementations. They programs. In thls section, we mention a facility that allows a term to be
Chapter 10
Meta-Logical Predicates

X;Y -
X or Y . unfortunate, as it has been suggested for other additions to pure Prolog.
X ; Y - X . Most notable is Colmerauer's geler (Colmerauer, 1982a), whlch allows
X ; Y - Y . the suspension of a goal and gives the programmer more control over
goal order. Tlvs predicate is provided by Sicstus Prolog as freeze. The
Program 10.9 Logical disjunction discussion of Nakashima and colleagues, although publicized in the first
editon of thls book, was largely ignored, to be revived by Barklund (1989)
converted into a goal. The predicate call (X) calls the goal X for Prolog musing over "What is a variable in Prolog?" and by attempts to do meta-
to solve. programming in constraint logic programming languages, for example,
In practice, most Prolog implementations relax the restriction we have Heintze et al. (1989) and Lim and Stuckey (1990).
imposed on logic programs, that the goals in the body of a clause must The Godel project (Hill and Lloyd, 1993) has advocated replacing Pro-
be nonvariable terms. The meta-variable facility allows a variable to ap- log by a language that facilitates explicit manipulation of variables at a
pear as a goal in a conjunctive goal or in the body of the clause. During meta-level. In Lloyd and Hill (1989), the terms ground and nonground
the computation, by the time it is called, the variable must be instan- representation are used. Prolog uses a nonground representation, and
tiated to a term. It will then be treated as usual. If the variable is not adding freeze and numbervars allows a ground representation.
instantiated when it comes to be called, an error is reported. The meta-
variable facility is a syntactic convenience for the system predicate call.
The meta-variable facility greatly facilitates meta-programming, in par-
ticular the construction of meta-interpreters and shells. Two important
examples to be discussed in later chapters are Program 12.6, a simple
shell, and Program 17.5, a meta-interpreter. It is also essential for defin-
ing negation (Program 11.6) and allowing the definition of hlgher-order
predicates to be described in Section 16.3.
We give an example of using the meta-variable facility with a definition
of logical disjunction, denoted by the binary infix operator ";". The goal
(X;Y) is true if X or Y is true. The definition is given as Program 10.9.

10.5 Background

An excellent discussion of meta-logical system predicates in DEC-10 Pro-


log, and how they are used, can be found in O'Keefe (1983).
The unification procedure for Concurrent Prolog, written in Prolog, is
in Shapiro (1983b).
The difficulty in correctly manipulating object-level variables in Prolog
at the meta-level has been raised by several people. The discussion first
extensive discussion is in Nakashima et al. (1984), where the predicates
freeze,melt, and melt-new are introduced. The name freeze was a little
Cuts and Negation

Prolog provides a single system predicate, called cut, for affecting the
procedural behavior of programs. Its main function is to reduce the
search space of Prolog computations by dynamically pruning the search
tree. The cut can be used to prevent Prolog from following fruitless com-
putation paths that the programmer knows could not produce solutions.
The cut can also be used, inadvertently or purposefully, to prune com-
putation paths that do contain solutions. By doing so, a weak form of
negation can be effected.
The use of cut is controversial. Many of its uses can only be inter-
preted procedurally, in contrast to the declarative style of programming
we encourage. Used sparingly, however, it can improve the efficiency of
programs without compromising their clarity.

1 1.1 Green Cuts: Expressing Determinism

Consider the program merge (Xs ,Ys ,Zs) (Program 11.1),whch merges
two sorted lists of numbers Xs and Ys into the combined sorted list Zs.
Merging two lists of sorted numbers is a deterministic operation. Only
one of the five merge clauses applies for each nontrivial goal in a given
computation. To be more specific, when comparing two numbers X and
Y,for example, only one of the three tests X < Y,X = : = Y,and X > Y can
be true. Once a test succeeds, there is no possibility that any other test
will succeed.
Cuts and Negation
Chapter 11

merge(Xs,Ys,Zs) -
Zs is an ordered list of integers obtained from merging
the ordered lists of integers Xs and Ys.

merge([XIXsl,[YIYsl,CYlZs])
merge (Xs, C I ,Xs).
- X > Y, merge(CXIXsl,Ys,Zs).

merge([ I ,Ys,Ys).

Program 11.1 Merging ordered lists

The cut, denoted ! , can be used to express the mutually exclusive


nature of the tests. It is placed after the arithmetic tests. For example,
the first merge clause is written
Figure 11.1 The effect of cut

Operationally, the cut is handled as follows. from the last alternative prior to the choice of the clause containing the
The goal succeeds and commits Prolog to all the choices made since the cut.
parent goal was unified with the head of the clause the cut occurs in.
Although t h s definition is complete and precise, its ramifications and Let us consider a fragment of the search tree of the query merge ( [ I , 3 ,
implications are not always intuitively clear or apparent. 51 , [2,3] ,Xs) ? with respect to Program 11.2, a version of merge with
Misunderstandings concerning the effects of a cut are a major source cuts added. The fragment is given as Figure 11.1. The query is first re-
for bugs for experienced and inexperienced Prolog programmers alike. duced to the conjunctive query 1 < 2 , ! ,merge ( [3,51 , [ 2 , 3 1 , Xs 1)?; the
The misunderstandings fall into two categories: assuming that the cut goal 1 < 2 is successfully solved, reachng the node marked (*) in the
prunes computation paths it does not, and assuming that it does not search tree. The effect of executing the cut is to prune the branches
prune solutions where it actually does. marked (a) and (b).
The following implications may help clarify the foregoing terse defini- Continuing discussion of Program 11.2, the placement of the cuts in
tion: the three recursive clauses of merge is after the test.' The two base cases
of merge are also deterministic. The correct clause is chosen by unifica-
First, a cut prunes all clauses below it. A goal p unified with a clause tion, and thus a cut is placed as the first goal (and in fact the only goal) in
containing a cut that succeeded would not be able to produce solutions the body of the rule. Note that the cuts eliminate the redundant solution
using clauses that occur below that clause. to the goal merge ( [ ] , [ I ,Xs) . Previously, t h s was accomplished more
Second, a cut prunes all alternative solutions to the conjunction of awkwardly, by specifying that Xs (or Ys) had at least one element.
goals that appear to its left in the clause. For example, a conjunctive
goal followed by a cut will produce at most one solution.
1. The cut after the third merge clause is unnecessary in any practical sense. Proce-
On the other hand, the cut does not affect the goals to its right in durally, it will not cause any reduction of search. But it makes the program more
the clause. They can produce more than one solution in the event of symmetric, and like the old joke says about chicken soup, it doesn't hurt.
backtraclung. However, once t h s conjunction fails, the search proceeds
C h a p t e r 11
Cuts and Negation

merge(Xs,Ys,Zs) -
Z s is an ordered list of integers obtained from merging
minimum(X,Y,Min) -
Min is the minimum of the numbers X and Y.
the ordered lists of integers Xs and Ys.

Program 11.3 minimum with cuts

-
polynomial ( T e r m , X )
T e r m is a polynomial in X .

Program 11.2 Merging with cuts


polynomial(X,X) --
polynomial (Term,X)
!.

constant (Term), ! .
polynomial(~erml+Term2,X) -
-
We restate the effect of a cut in a general clause C = A B1, . . . ,Bk, !, ! , polynomial (Term1 ,X) , polynomial (Term2,X) .
polynomial (Terml-Term2, X) -
Bk+*,. . . ,B, in a procedure defining A. If the current goal G unifies with
! , polynomial(Terml,X), polynomial(Term2,X).
the head of C, and BI,. . .,Bk further succeed, the cut has the following
effect. The program is committed to the choice of C for reducing G; any
polynomial(Terml*Term2,X) -
! , polynomial(Terml,X), polynomial(Term2,X).
alternative clauses for A that might unify with G are ignored. Further,
should B, fail for i > k + 1, backtracking goes back only as far as the !.
polynomial(Terml/Term2,X) -
! , polynomial(Terml,X), constant(Term2).
Other choices remaining in the computation of B,, i I k, are pruned from polynomial(TermTN,X) -
! , integer(N), N 2 0, polynomial(Term,X).
the search tree. If backtracking actually reaches the cut, then the cut fails,
and the search proceeds from the last choice made before the choice of Program 11.4 Recognizing polynomials
G to reduce C.
The cuts used in the merge program express that merge is determinis-
tic. That is, only one of the clauses can be used successfully for proving is no possibility for the other test succeeding. Program 11.3 is the appro-
an applicable goal. The cut commits the computation to a single clause, priately modified version of minimum.
once the computation has progressed enough to determine that this is A more substantial example where cuts can be added to indicate that
the only clause to be used. a program is deterministic is provided by Program 3.29. The program
The information conveyed by the cut prunes the search tree, and hence defines the relation polynomial(Term,X) for recognizing if Term is a
shortens the path traversed by Prolog, which reduces the computation polynomial in X. A typical rule is
time. In practice, using cuts in a program is even more important for
saving space. Intuitively, knowing that a computation is deterministic
means that less information needs to be kept for use in the event of
backtracking. This can be exploited by Prolog implementations with tail Once the term being tested has been recognized as a sum (by unifying
recursion optimization, discussed in Section 11.2. with the head of the rule), it is known that none of the other polynomial
Let us consider some other examples. Cuts can be added to the pro- rules will be applicable. Program 11.4 gives the complete polynomial
gram for computing the minimum of two numbers (Program 3.7) in pre- program with cuts added. The result is a deterministic program that has
cisely the same way as for merge. Once an arithmetic test succeeds, there a mixture of cuts after conditions and cuts after unification.
Cuts and Negation
Chapter I I

When discussing the Prolog programs for arithmetic, whch use the un-
sort (Xs,Ys) -
Ys is an ordered permutation of the list of integers Xs.
derlylng arithmetic capabilities of the computer rather than a recursive
logic program, we argued that the increased efficiency is often acheved
sort (Xs,Ys) -
append(As, [X ,YI BSI ,Xs),
at the price of flexibility. The logic programs lost their multiple uses X > Y,
when expressed as Prolog programs. Prolog programs with cuts also have ,
append(As, [Y ,XIBsl ,Xsl),
less flexibility than their cut-free equivalents. T h s is not a problem if the
sort (Xsl ,Ys).
intended use of a program is one-way to begin with, as is often the case.
The examples so far have demonstrated pruning useless alternatives
sort (Xs ,Xs) -
ordered(Xs1,
for the parent goal. We give an example where cuts greatly aid efficiency !.
by removing redundant computations of sibling goals. Consider the re- ordered(Xs) - See Program 3.20.
cursive clause of an interchange sort program:
sort (Xs ,Ys) - Program 1 1.5 Interchange sort

append(As, [X,Y I Bsl ,Xs),


X > Y,
prune only computation paths that do not lead to new solutions. Cuts
append(As, [Y,XIBs] ,Xsl),
that are not green are red.
sort (Xsl ,Ys) .
The cut interacts with system predicates such as call and ;, intro-
The program searches for a pair of adjacent elements that are out duced in Chapter 10, and with predicates such as not,introduced later in
of order, swaps them, and continues until the list is ordered. The base t h s chapter. The question is what scope should cut have, that is, whch
clause is choice points should be affected. Since such tricky uses of cut are not
presented or advocated in t h s book, we defer discussion of the scope of
cut until Chapter 17 on interpreters.
Consider a goal sort ( [3,2,11 ,Xs). T h s is sorted by swapping 3 and
2, then 3 and 1, and finally 2 and 1 to produce the ordered list [I,2,31.
Exercises for Section 11.1
It could also be sorted by first swapping 2 and 1, then swapping 3 and
1, and finally swapping 3 and 2, to arrive at the same solution. We know
(i) Add cuts to the partition program from quicksort, Program 3.22.
there is only one sorted list. Consequently there is no point in searchng
for another alternative once an interchange is made. T h s can be indi- (ii) Add cuts to the differentiation program, Program 3.30.
cated by placing the cut after the test X > Y. T h s is the earliest it is
(iii) Add cuts to the insertion sort program, Program 3.21.
known that an interchange is necessary. The interchange sort program
with cut is given as Program 11.5.
The addition of cuts to the programs described in t h s section does not
alter their declarative meaning; all solutions to a given query are found. 11.2 Tail Recursion Optimization
Conversely, removing the cuts should similarly not affect the meaning of
the program. Unfortunately, t h s is not always the case. A distinction has As noted in Section 8.3, the main difference from a performance point
been made in the literature between green cuts and red cuts. Green cuts of view between recursion and iteration is that recursion requires, in
have been considered in t h s section. The addition and removal of green general, space linear in the number of recursive calls to execute, whereas
cuts from a program do not affect the program's meaning. Green cuts
Chapter 1 1 Cuts and Negation

iteration can be executed in constant space, independent of the number If it is used to append two complete lists, then by the time the recursive
of iterations performed. append goal is executed, the preconditions for tail recursion optimiza-
Recursive programs defined free of side effects might be considered tion hold. No other clause is applicable to the parent goal (if the first
more elegant and pleasing than their iterative counterparts defined in argument unifies with [XIXsl,it certady won't unify with [ 1, since we
terms of iteration and local variables. However, an order of magnitude assumed that the first argument is a complete list). There are no other
in space complexity seems an unacceptable price for such aesthetic plea- goals in the body besides append, so the second precondition holds vac-
sures. Fortunately, there is a class of recursive programs, precisely those uously.
that can be translated directly into iterative ones, that can be executed in However, for the implementation to know that the optimization ap-
constant space. plies, it needs to know that the second clause, although not tried yet,
The implementation techmque that achieves t h s space saving is called is not applicable. Here indexing comes into play. By analyzing the first
tail recursion optimization, or more precisely, last call optimization. Intu- argument of append, it is possible to know that the second clause would
itively, the idea of tail recursion optimization is to execute a recursive fail even before trying it, and to apply the optimization in the recursive
program as if it were an iterative one. call to append.
Consider the reduction of a goal A using the clause Not all implementations provide indexing, and not all cases of deter-
minism can be detected by the indexing mechanisms available. Therefore
it is in the interest of the programmer to help an implementation that
with most general unifier 0. The optimization is potentially applicable to supports tail recursion optimization to recognize that the preconditions
the last call in the body of a clause, B,. It reuses the area allocated for for applying it hold.
the parent goal A for the new goal B,. There is a sledgehammer techmque for doing so: Add a cut before the
The key precondition for t h s optimization to apply is that there be last goal of a clause, in which tail recursion optimization should always
no choice points left from the time the parent goal A reduced to this apply, as in
clause to the time the last goal B, is reduced. In other words, A has no
alternative clauses for reduction left, and there are no choice points left
in the computation of goals to the left of B,, namely, the computation of
the conjunctive goal (B1,B2,.. .rBn-l)O, was deterministic. T h s cut prunes both alternative clauses left for the parent goal A, and
Most implementations of tail recursion optimization can recognize to any alternatives left for the computation of (B1,B2,.. .,B,-l)O.
a limited extent at runtime whether t h s condition occurs, by comparing In general, it is not possible to answer if such a cut is green or red, and
backtracking-related information associated with the goals Bn and A. An- the programmer's judgment should be applied.
other implementation technique, clause indexing, also interacts closely It should be noted that the effect of tail recursion optimization is en-
with tail recursion optimization and enhances the ability of the imple- hanced greatly when accompanied with a good garbage collector. Stated
mentation to detect that t h s precondition occurs. Indexing performs negatively, the optimization is not very significant without garbage col-
some analysis of the goal, to detect which clauses are applicable for lection. The reason is that most tail recursive programs generate some
reduction, before actually attempting to do the unifications. Typically, data structures on each iteration. Most of these structures are tempo-
indexing is done on the type and value of the first argument of the goal. rary and can be reclaimed (see, for instance, the editor in Program 12.5).
Consider the append program: Together with a garbage collector, such programs can run, in principle,
forever. Without it, although the stack space they consume would remain
constant, the space allocated to the uncollected temporary data stmc-
tures would overflow.
Chapter 1 I
Cuts and Negation
notX -
X is not provable. married(abraham, sarah) .
not X - X, !, fail. married(X,Y) -
married(Y,X).
not X.
The query not married(abraham, sarah)? terminates (with failure) even
Program 11.6 Negation as failure though married (abraham ,sarah) ? does not terminate.
Program 11.6 is incomplete as an implementation of negation by fail-
ure. The incompleteness arises from Prolog's incompleteness in realizing
the computation model of logic programs. The definition of negation as
1 1.3 Negation failure for logic programs is in terms of a finitely failed search tree. A
Prolog computation is not guaranteed to find one, even if it exists. There
The cut can be used to implement a version of negation as failure. Pro- are goals that could fail by negation as failure, that do not terminate un-
gram 11.6 defines a predicate not (Goal), whch succeeds if Goal fails. As der Prolog's computation rule. For example, the query not (p(X) ,q(X) ) ?
well as using cut, the program uses the meta-variable facility described in does not terminate with respect to the program
Chapter 10, and a system predicate fail that always fails.
Standard Prolog provides a predicate fail-if (Goal), whlch has the
same behavior as not/l. Other Prologs provide the same predicate under
the name \+/I. The rationale for not calling the system predicate not
The query would succeed if the q(X) goal were selected first, since that
is that the predicate does not implement true logical negation, and it
gives a finitely failed search tree.
is misleading to label it as such. We believe that the user easily learns
The incorrectness of Program 11.6 stems from the order of traver-
how the predicate differs from true negation, as we will explain, and
sal of the search tree and arises when not is used in conjunction with
programmers are helped rather than misled by the name.
other goals. Consider using not to define a relationshp unmarried-
Let us consider the behavior of Program 11.6 in answering the query
student(X) for someone who is both not married and a student, as
not G? The first rule applies, and G is called using the meta-variable
in the following program:
facility. If G succeeds, the cut is encountered. The computation is then
committed to the first rule, and not G fails. If the call to G fails, then the umarried-student (X) - not married(X), student (X)
second rule of Program 11.6 is used, which succeeds. Thus not G fails if student (bill) .
G succeeds and succeeds if G fails. married( joe) .
The rule order is essential for Program 11.6 to behave as intended. T h s
introduces a new, not entirely desirable, dimension to Prolog programs. The query unmarried-student (X)? fails with respect to the preceding
Previously, changing the rule order only changed the order of solutions. data, ignoring that X=bill is a solution logically implied by the rule and
Now the meaning of the program can change. Procedures where the rule two facts. The failure occurs in the goal not married(](), since there is a
order is critical in this sense must be considered as a single unit rather solution X=j oe. The problem can be avoided here by swapping the order
than as a collection of individual clauses. of the goals in the body of the rule.
The termination of a goal not G depends on the termination of G. If G A similar example is the query not (X=l), X=2?, whch fails although
terminates, so does not G. If G does not terminate, then not G may or there is a solution X=2.
may not terminate depending on whether a success node is found in the The implementation of negation as failure is not guaranteed to work
search tree before an infinite branch. Consider the following nonterrni- correctly for nonground goals, as the foregoing examples demonstrate.
nating program: In most implementations of Prolog, it is the responsibility of the pro-
grammer to ensure that negated goals are ground before they are solved.
Chapter 1 I Cuts and Negation

variants ( Terml,Term21 -
Terml and Term2 are variants.
X f Y -
X and Y are not unifiable.
variants(Terml,Term2) -
verify((numbervars(Terml,O,N),
X # X
x f Y.
- ! , fail.

numbervars(Term2,O,N),
Terml=Term2)). Program 11.8 Implementing f
verify ( Goal) -
Goal has a true instance. Verifying this is not done
constructively, so variables are not instantiated in the process. if they are instances of each other. T h s can be acheved with the follow-
verify(Goa1) - nothot Goal). ing trick, implemented in Program 11.7. Instantiate the variables using
numbervars (Term,N,N1) - See Program 10.8. numbervars, test whether the terms unify, and undo the instantiation.
The three forms of comparison =/2, variant/2, and ==/2 are pro-
Program 11.7 Testing if terms are variants gressively stronger, with unifiability being the weakest and most general.
Identical terms are variants, and variant terms are unifiable. The distinc-
Thls can be done either by a static analysis of the program or by a run- tion between the different comparisons vanishes for ground terms; for
time check, using the predicate ground defined in Program 10.4. ground terms all three comparisons return the same results.
The predicate not is very useful. It allows us to define interesting con- The conjunction of cut and fail used in the first clause of not in Pro-
cepts. For example, consider a predicate disjoint (Xs,Ys), true if two gram 11.6 is known as the cut-fail combination. The cut-fail combination
lists Xs and Ys have no elements in common. It can be defined as is a technique that can be used more generally. It allows early failure. A
clause with a cut-fail combination says that the search need not (and will
disjoint (Xs ,Ys) - not (member(Z,Xs) , member (z,Ys)). not) proceed.
Some cuts in a cut-fail combination are green cuts. That is, the program
Many other examples of using not will appear in the programs through-
has the same meaning if the clause containing the cut-fail combination
out this book.
is removed. For example, consider Program 10.4 defining the predicate
An interesting property of not (Goal) is that it never instantiates the
ground. An extra clause can be added, which can reduce the search with-
arguments in Goal. This is because of the explicit failure after the call
out affecting the meaning:
to Goal succeeds, which undoes any bindings made. This property can
be exploited to define a procedure verify(Goal1, given as part of Pro-
gram 11.7, whch determines whether a goal is true without affecting
ground(Term) - var (Term) , ! , fail
the current state of the variable bindings. Double negation provides the The use of cut in Program 11.6 implementing not is not green, but red.
means. The program does not behave as intended if the cut is removed.
We note in passing that negation as implemented in Prolog shares a The cut-fail combination is used to implement other system predi-
feature with negation in natural language. A doubly negated statement is cates involving negation. For example, the predicate # (written as \= in
not the same as the equivalent affirmative statement. Standard Prolog) can be simply implemented via unification and cut-fail,
The program for verify can be used in conjunction with Program 10.8 rather than via an infinite table, with Program 11.8. T h s program is also
for numbervars to define a notion of equality intermediate between unifi- only guaranteed to work correctly for ground goals.
ability provided by =/2 and syntactic equality provided by ==/2. The With ingenuity, and a good understanding of unification and the ex-
predicate variants(X,Y) defined in Program 11.7 is true if two terms ecution mechanism of Prolog, interesting definitions can be found for
X and Y are variants. Recall from Chapter 4 that two terms are variants many meta-logical predicates. A sense of the necessary contortions can
Cuts and Negation
Chapter 1 I

There is a severe flaw with t h s reasoning. The modified program has


be found in the program for same-var (X ,Y), whch succeeds if X and Y a different meaning from the standard program for minimum. It succeeds
are the same variable and otherwise fails: on the goal minimum (2,5,5). The modified program is a false logic pro-
same-var (foo,Y) -- var (Y) , ! , fail. gram.
same-var (X,Y) - var (X) , var (Y) . The incorrect minimum goal implied by the modified program can be
avoided. It is necessary to make explicit the unification between the first
The argument for its correctness follows: "If the arguments to same-var and t h r d arguments, whlch is implicit in the first rule. The modified rule
are the same variable, binding X to foo will bind the second argument is
as well, so the first clause will fail, and the second clause will succeed.
If either of the arguments is not a variable, both clauses will fail. If the
arguments are different variables, the first clause will fail, but the cut T h s techmque of using the cut to commit to a clause after part of the
stops the second clause from being considered." unification has been done is quite general. But for minimum the resultant
code is contrived. It is far better to simply write the correct logic pro-
Exercises for Section 1 1.3 gram, adding cuts if efficiency is important, as done in Program 11.3.
Using cut with the operational behavior of Prolog in mind is problem-
(i) Define the system predicate \== using == and the cut-fail combina- atic. It allows the writing of Prolog programs that are false when read
tion. as logic programs, that is, have false conclusions but behave correctly
(ii) Define nonvar using var and the cut-fail combination. because Prolog is unable to prove the false conclusions. For example, if
minimum goals are of the form minimum(X, Y,Z), where X and Y are instan-
tiated, but Z is not, the modified program behaves correctly.
The only effect of the green cuts presented in Section 11.1 is to prune
1 1.4 Red Cuts: Omitting Explicit Conditions
from the search tree branches that are known to be useless. Cuts whose
presence in a program changes the meaning of that program are called
Prolog's sequential choice of rules and its behavior in executing cut are
red cuts. The removal of a red cut from a program changes its meaning,
the key features necessary to compose the program for not. The pro-
i.e., the set of goals it can prove.
grammer can take into account that Prolog ulll only execute a part of
A standard Prolog programming techmque using red cuts is the omis-
the procedure if certain conditions hold. T h s suggests a new, and rnis-
sion of explicit conditions. Knowledge of the behavior of Prolog, specifi-
guided, style of programming in Prolog, where the explicit conditions
cally the order in which rules are used in a program, is relied on to omit
governing the use of a rule are omitted.
conditions that could be inferred to be true. T h s is sometimes essen-
The prototypical (bad) example in the literature is a modified version
tial in practical Prolog programming, since explicit conditions, especially
of Program 11.3 for minimum. The comparison in the second clause of
negative ones, are cumbersome to specify and inefficient to run. But mak-
the program can be discarded to give the program
ing such omissions is error-prone.
Omitting an explicit condition is possible if the failure of the previous
clauses implies the condition. For example, the failure of the comparison
XIY in the minimum code implies that X is greater than Y. Thus the test
The reasoning offered to justify the program is as follows: "If X is less X > Y can be omitted. In general, the explicit condition is effectively the
than or equal to Y, then the minimum is X. Otherwise the minimum
negation of the previous conditions. By using red cuts to omit conditions,
is Y, and another comparison between X and Y is unnecessary." Such a negation is being expressed implicitly.
comparison is performed, however, by Program 11.3.
Chapter 1 1 Cuts and Negation

delete(Xs,X,Ys) -
Ys is the result of deleting all occurrences of X from the list Xs.
if-then-else(P,Q,R) -
Either P and Q , or not P and R.

Program 11.10 If-then-else statement


Program 11.9a Deleting elements from a list

delete(Xs,X,Ys) -
Ys is the result of deleting all occurrences of X from the list Xs.
Let us investigate the use of cut to express the if-then-else control
structure. Program 11.10 defines the relation i f -then-else (P,Q,R ) .
Declaratively, the relation is true if P and Q are true, or not P and R are
true. Operationally, we prove P and, if successful, prove Q, else prove R.
The utility of a red cut to implement t h s solution is self-evident. The
alternative to using a cut is to make explicit the condition under whlch R
Program 11.9b Deleting elements from a list is run. The second clause would read

if-then-else(P,Q,R) - not P, R.
Consider Program 11.5 for interchange sort. The first (recursive) rule
applies whenever there is an adjacent pair of elements in the list that This could be expensive computationally. The goal P will have to be com-
are out of order. When the second sort rule is used, there are no such puted a second time in the determination of not.
pairs and the list must be sorted. Thus the condition ordered(Xs) can We have seen so far two h n d s of red cuts. One kind is built into the
be omitted, leaving the second rule as the fact sort (Xs ,Xs). As with program, as in the definitions of not and f . A second lund was a green
minimum, this is an incorrect logical statement. cut that became red when conditions in the programs were removed.
Once the ordered condition is removed from the program, the cut However, there is a t h r d kind of red cut. A cut that is introduced into
changes from green to red. Removing the cut from the variant without a program as a green cut that just improves efficiency can turn out to be
the ordered condition leaves a program that gives false solutions. a red cut that changes the program's meaning.
Let us consider another example of omitting an explicit condition. Con- For example, consider trying to write an efficient version of member
sider Program 3.18 for deleting elements in a list. The two recursive that does not succeed several times when there are multiple copies of
clauses cover distinct cases, corresponding to whether or not the head an element in a list. Taking a procedural view, one might use a cut to
of the list is the element to be deleted. The distinct nature of the cases avoid backtracking once an element is found to be a member of a list.
can be indicated with cuts, as shown in Program 11.9a. The corresponding code is
By reasoning that the failure of the first clause implies that the head
member (X, [X 1 Xsl) -- ! .
of the list is not the same as the element to be deleted, the explicit
inequality test can be omitted from the second clause. The modified
member (X , [Y I Ysl )- member (X ,Ys) .
program is given as Program 11.9b. The cuts in Program 11.9a are green Adding the cut indeed changes the behavior of the program. However,
in comparison to the red cut in the first clause of Program 11.9b. it is now not an efficient variant of member, since, for example, the
In general, omitting simple tests as in Program 11.9b is inadvisable. query member (X , [ I , 2 , 3 ] ) ? gives only one solution, X=l. It is a variant
The efficiency gain by their omission is minimal compared to the loss of of member-check, given as Program 7.3, with the explicit condition X #
readability and modifiability of the code. Y omitted, and hence the cut is red.
Chapter 11
Cuts a n d Negation

Exercises for Section 1 1.4

(i) Discuss where cuts could be placed in Program 9.3 for substi- T h s version is given as Program 11.1lb.
tute. Consider whether a cut-fail combination would be useful, and Program 11.1l b behaves correctly on queries to determine the pension
whether explicit conditions can be omitted. to whch people are entitled, for example, pension (mc-tavish,X) ?. The
(ii) Analyze the relation between Program 3.19 for select and the pro- program is not correct, though. The query pension (mc-t avish ,noth-
gram obtained by adding a single cut: ing)? succeeds, whch mc-tavish wouldn't be too happy about, and
pension(X,old-age-pension)? has the erroneous unique answer X=mc-
select (X, [XI Xsl ,XS) -- ! . tavish. The cuts prevent alternatives being found. Program 11.1l b only
- select (X,Ys,Zs).
select (X, [Y IYs] , [Y I Zs] ) works correctly to determine the pension to whch a given person is
(Hint: Consider variants of select.) entitled.
A better solution is to introduce a new relation entitlement (X,Y),
whch is true if X is entitled to Y. It is defined with two rules and uses
Program 11.1l a for pension:
11.5 Default Rules
entitlement (X,Y) -
pension(X,Y) .
Logic programs with red cuts essentially consist of a series of special entitlement (X ,nothing) -
not ~ension(X,Y).
cases and a default rule. For example, Program 11.6 for not had a special
case when the goal G succeeded and a default fact not G used otherwise. T h s program has all the advantages of Program 1 l . l l b and neither
The second rule for if-then-else in Program 11.10 is of the disadvantages mentioned before. It shows that making a person

pension (Person,Pension) -
It is used by default if P fails. Pension is the type of pension received by Person.
Using cuts to acheve default behavior is in the logic programming
folklore. We argue, using a simple example, that often it is better to
compose an alternative logical formulation than to use cuts for default
behavior.
Program 11.1l a is a naive program for determining social welfare pay-
ments. The relation pension(Person,Pension) determines which pen-
sion, Pension, a person, Person, is entitled to. The first pension rule
says that a person is entitled to an invalid's pension if he is an invalid. Program 11.1 l a Determining welfare payments
The second rule states that people over the age of 65 are entitled to an
old age pension if they have contributed to a suitable pension scheme
long enough, that is, they must be paid-up. People who are not paid up
pension (Person,Pension)
Pension is the type of
-pension rcccivcd by Person.
are still entitled to supplementary benefit if they are over 65.
Consider extending Program 11.1l a to include the rule that people re-
pension(X,invalid-pension)
pension(X,old-age-pension)
--
invalid()(), ! .
over-65 (X), paid-upO() , ! .
ceive nothng if they do not qualify for one of the pensions. The proce- pension(X,supplementary-benefit) -
over_65(X), ! .
dural "solution" is to add cuts after each of the three rules, and an extra ~ension(x,nothing).
default fact
Program 11.1 l b Determining welfare payments
Cuts and Negation
Chapter 11

In this book, we often use the first argument to differentiate between


entitled to nothing as the default rule is really a new concept and should
clauses. Indexing on the first argument is the most common among Pro-
be presented as such.
log implementations. For effective use, consult your Prolog manual.
Efficient use of space is determined primarily by controlling the growth
of the stack. Already we have discussed the advantages of iterative code
and last call optimization. Too many frames placed on the stack can
11.6 Cuts for Efficiency
cause computations to abort. In practice this is a major concern. Running
out of stack space is a common symptom of an infinite loop or running a
Earlier in this chapter, we claimed that the efficiency of some Prolog
highly recursive program. For example, Program 3.9 implementing Ack-
programs could be improved through sparing use of the cut. Thls sec-
ermann's function, when adapted for Prolog arithmetic, quickly exhausts
tion explores the claim. Two issues are addressed. The first is the mean-
an implementation's capacity.
ing of efficiency in the context of Prolog. The second is appropriate uses
Time complexity is approximated by number of reductions. Thus effi-
of cut.
cient use of time can be determined by analyzing the number of reduc-
Efficiency relates to utilization of resources. The resources used by
tions a program makes. In Part I, we analyzed different logic programs by
computations are space and time. To understand Prolog's use of space
the size of proof trees. In Prolog, size of search tree is a better measure,
and time, n7eneed to consider Prolog implementation technology.
but it becomes difficult to incorporate Prolog's nondeterminism.
The two major areas of memory manipulated during a Prolog computa-
Probably the most important approach to improving time performance
tion are the stack and the heap. The stack, called the local stack in many
is better algorithms. Although Prolog is a declarative language, the no-
Edinburgh Prolog implementations, is used to govern control flow. The
tion of an algorithm applies equally well to Prolog as to other languages.
heap, called the global stack in many Edinburgh Prolog implementations,
Examples of good and bad algorithms for the same problem, together
is used to construct data structures that are needed throughout the com-
with their Prolog implementations, have been given in previous chap-
putation.
ters. Linear reverse using accumulators (Program 3.16b) is clearly more
Let us relate stack management to the computation model of Prolog.
efficient than naive reverse (Program 3.16a). Quicksort (Program 3.22) is
Each time a goal is chosen for reduction, a stack frame is placed on the
better than permutation sort (Program 3.20).
stack. Pointers are used to specify subsequent flow of control once the
Besides coming up with better algorithms, several things can be done
goal succeeds or fails. The pointers depend on whether other clauses can
to influence the performance of Prolog programs. One is to choose a bet-
be used to reduce the chosen goal. Handling the stack frame is simplified
ter implementation. An efficient implementation is characterized by its
considerably if it is known that only one clause is applicable. Techrucally,
raw speed, its indexing capabilities, support for tail recursion optimiza-
a choice point needs to be put on the stack if more than one clause is
tion, and garbage collection. The speed of logic programming languages
applicable.
is usually measured in LIPS, or logical inferences per second. A logical
Experience has shown that avoiding placing choice points on the stack
inference corresponds to a reduction in a computation. Most Prolog im-
has a large impact on efficiency. Indeed, Prolog implementation tech-
plementations claim a LIPS rating. The standard benchmark, by no means
nology has advanced to the stage that deterministic code, i.e., without
ideal, is to time Program 3.16a, naive reverse, reversing a list. There are
choice points, can be made to run almost as efficiently as conventional
496 reductions for a list of 30 elements.
languages.
Once the implementation is fixed, the programs themselves can be
Cuts are one way that Prolog implementations know that only one
tuned by
clause is applicable. Another way is by the effective use of indexing.
Whether a cut is needed to tell a particular Prolog implementation that
Good goal ordering, where the rule is "fail as early as possible"
only one clause is applicable depends on the particular indexing scheme.
Chapter I 1
Cuts a n d Negation

Exploitation of the indexing facility, by ordering arguments appropri-


ately A common misconception is that a program can be lixed from giving
extraneous answers and behaving incorrectly by adding cuts. T h s is not
Elimination of nondeterminism using explicit conditions and cuts so. Prolog code should be debugged as declaratively as possible, a topic
Let us elaborate on the third item and discuss guidelines for using we discuss in Chapter 13. Only when the logic is correct should efficiency
cut. As discussed, Prolog implementations will perform more efficiently be addressed.
if they know a predicate is deterministic. The appropriate sparing use The final factor that we consider in evaluating the efficiency of Prolog
of cut is primarily for saying that predicates are deterministic, not for programs is the creation of intermediate data structures, which primarily
controlling backtraclung. affects use of the heap. Minimizing the number of data structures being
The two basic principles for using a cut are generated is a subject that has not received much attention in the Prolog
literature. We analyze two versions of the predicate sublist (Xs ,Ys) to
Make cuts as local as possible.
illustrate the type of reasoning possible.
Place a cut as soon as it is known that the correct clause has been The two versions of sublist that we consider involve Program 3.13
chosen. for calculating prefutes and suffixes of lists. We must also specify the
Let us illustrate the principles with the quicksort program, Program comparison with respect to a particular use. The one chosen for the
3.22. The recursive clause is as follows analysis is whether a given list is a sublist of a second given list. The
first clause that follows denotes a sublist as a prefix of a suffix, and the
quicksort ( [X I Xsl ,Ys)- second clause defines a sublist as a suffix of a prefix:
partition(~s, ~ , ~ i t t l,Bigs),
es quicksort(Littles3Ls) 9

quicksort (Bigs ,Bs) , a p p e n d b , [X I Bsl ,Ys) .


We know there is only one solution for the partition of the list. Rather
than place a cut in the clause for quicksort, the partition predicate Although both programs have the same meaning, there is a difference
should be made deterministic. Thls is in accordance with the first princi- in the performance of the two programs. If the two arguments to sub-
ple. list are complete lists, the first clause simply goes down the second list,
One of the partition clauses is returning a s u m , then goes down the first list, checlung if the suffix is a
prefix of the first list. T h s execution does not generate any new interme-
diate data structures. On the other hand, the second clause creates a new
list, which is a prefix of the second list, then checks if\this list is a suffix
If the clause succeeds, then no other will be applicable. But the cut of the first list. If the check fails, backtraclung occurs, and a new prefix
should be placed before the recursive call to partition rather than after, of the first list is created.
according to the second principle. Even though, on the average, the number of reductions performed by
Where and whether to place cuts can depend on the Prolog implemen- the two clauses is the same, they are different in their efficiency. The first
tation being used. Cuts are needed only if Prolog does not know the clause does not generate new structures (does not cons, in Lisp jargon).
determinism of a predicate. If, for example, indexing can determine that The second clause does. When analyzing Lisp programs, it is common to
only one predicate is applicable, no cuts are needed. In a system without examine the consing performance in great detail, and whether a program
indexing, cuts would be needed for the same program. conses or not is an important efficiency consideration. We feel that the
Having discussed appropriate use of cuts, we stress that adding cuts issue is important for Prolog programs, but perhaps the state of the art
to a program should typically be done after the program runs correctly. of studying the performance of large Prolog programs has not matured
enough to dictate such analyses.
212 Chapter 11 Cuts and Negation

The cut is asymmetric, because it eliminates alternative clauses below


1 1.7 Background the clause in whch it appears, but not above. Hence a cut in one clause
affects the meaning of other clauses. The commit, on the other hand, is
The cut was introduced in Marseilles Prolog (Colmerauer et al., 1973) symmetric and therefore cannot implement negation as failure; it does
and was perhaps one of the most influential design decisions in Pro- not destroy the modularity of clauses.
log. Colmerauer experimented with several other constructs, whch cor- The pioneering work on Prolog implementation technology was in
responded to special cases of the cut, before coming up with its full D.H.D. Warren's Ph.D. thesis (1977). Warren later added tail recursion
definition. optimization to h s original DEC-10 compiler (1986). Tail recursion op-
The terminology green cuts and red cuts was introduced by van Emden timization was implemented concurrently by Bruynooghe (1982) in h s
(1982), in order to, try to distinguish between legitimate and illegitimate Prolog system. A motley collection of papers on Prolog implementations
uses of cuts. Alternative control structures, whlch are more structured can be found in Campbell (1984).
then the cut, are constantly being proposed, but the cut still remains the Most current compilers and implementation technology are based on
workhorse of the Prolog programmer. Some of the extensions are if-then- the WAM (Warren Abstract Machne), published as a somewhat cryptic
else constructs (O'Keefe, 1985) and notations for declaring that a relation techmcal report (Warren, 1983). Readers seriously interested in program
is functional, or deterministic, as well as "weak-cuts," "snips," remote- efficiency need to understand the WAM. The best places to start reading
cuts (Chikayama, 1984), and not itself, whlch, as currently implemented, about the WAM are Maier and Warren (1988) and Ait-~aci(1991).
can be viewed as a structured application of the cut. References to negation in logic programming can be found in Sec-
The controversial nature of cut has not been emphasized in this book. tion 5.6. Implementations of a sound negation as failure rule in dialects
A good starting place to read about some of cut's problems, and the of Prolog can be found in Prolog-I1(van Caneghem, 1982) and MU-Prolog
variation in its implementation, is Moss (1986). Many of the difficulties (Naish, 1985a).
arise from the scope of the cut, and how cuts interact with the system The program for same-var and its argument for correctness are due to
predicates for control such as conjunction, disjunction, and the meta- O'Keefe (1983).
variable facility. For example, two versions of call have been suggested, Program 1 l . l l b for pension is a variant of an example due to Sam
one that blocks the cut and one that does not. Further discussion of cut Steel for a Prolog course at the University of Edinburgh - hence the
can be found in O'Keefe (1990), including an exposition on when cut Scottish flavor. Needless to say, t h s is not intended as, nor is it an
should be used. accurate expression, of the Scottish or British social welfare system.

R and an abridged if-then form P - -


Some Prologs provide i f -then-else ( P , 4 , R ) under the syntax P 9;
9. Whether to include if-then-else
in Standard Prolog has been a controversial issue. The trade-off is con-
venience for some programming tasks versus thorny semantic anoma-
lies. T h s issue has been raised several times on the USENET newsgroup
comp.lang.prolog. Relevant comments were collected in the May 1991 is-
sue of the Newsletter of the Association for Logic Programming, Volume
4, No. 2.
The cut is also the ancestor of the commit operator of concurrent
logic languages, whch was first introduced by Clark and Gregory (1981)
in their Relational Language. The commit cleans up one of the major
drawbacks of the cut, whch is destroying the modularity of clauses.
Extra-Logical Predicates

There is a class of predicates in Prolog that lie outside the logic program-
ming model, and are called extra-logical predicates. These predicates
acheve a side effect in the course of being satisfied as a logical goal.
There are basically three types of extra-logical system predicates: pred-
icates concerned with I/O, predicates for accessing and manipulating the
program, and predicates for interfacing with the underlying operating
system. Prolog 1/0 and program manipulation predicates are discussed
in t h s chapter. The interface to the operating system is too system-
dependent to be discussed in this book.

A very important class of predicates that produces side effects is that


concerned with I/O. Any practical programming language must have a
mechanism for both input and output. The execution model of Prolog,
however, precludes the expression of 1/0 withn the pure component of
the language.
The basic predicate for input is reado[). T h s goal reads a term from
the current input stream, usually from the terminal. The term that has
been read is unified with X, and r e a d succeeds or fails depending on the
result of unification.
The basic predicate for output is w r i t e (X). T h s goal writes the term
X on the current output stream, as defined by the underlying operating
system, usually to the terminal. Neither r e a d nor w r i t e give alternative
solutions on backtraclung.
Chapter 12 Extra-Logical Predicates

writeln( [XI Xsl )- write (XI, writeln(Xs) -


writeln( [ 1 )- nl.
read-word-list ( Words)
Words is a list of words read from the input stream via side effects.
Program 12.1 Writing a list of terms read-word-list(Words)
get-char(FirstChar1,
-
read-words(FirstChar,Words).

The normal use of read is with a variable argument X, whch acquires


read-words (Char, [Word l Words] )
word-char(Char1,
-
the value of the first term in the current input stream. The instantiation read-word(Char,Word,NextChar),
of X to somethng outside the program lies outside the logical model, read-words(NextChar,Words).
since each time the procedure is called, reado() succeeds with a (pos-
sibly) different value for X.
read-words(Char,Words)
f ill-char (Char),
-
Read attempts to parse the next term on the input stream. If it fails, it get-char(NextChar),
prints an error message on the terminal. read-words(NextChar,Words).
There is an asymmetry between the extra-logical nature of read and read-words(Char,[ 1 ) -
end-of-words-char(Char-1.
write. If all calls to write were replaced with the goal true, whch always
succeeded once, the semantics of the program would be unaffected. That read-word(Char,Word,NextChar)
word-chars(Char,Chars,NextChar),
-
is not true for read.
atom-list(Word,Chars).
Early Prolog implementations did not concentrate on input and output
facilities, providing the basic predicates read and write, or their equiva- word-chars(Char,[CharlChars],FinalChar) +

word-char(Char), ! ,
lents, and little else. More recent Prolog implementations have a wider
get-char(NextChar1,
range of formatted 1/0 options, some of which have been adopted in word-chars(NextChar,Chars,FinalChar).
Standard Prolog. In t h s book, the emphasis is not on I/O, and so we re- word-chars(Char, [ ],Char) -
strict outselves to basic predicates and some simple utilities described not word-char(Chax-).
in the rest of t h s section. For more elaborate I/O, consult your particular
Program 12.2 Reading in a list of words
Prolog manual.
A useful utility is a predicate writeln(Xs), analogous to the Pascal
command, whch writes the list of terms Xs as a line of output on the cur-
rent output stream. It is defined in Program 12.1. The predicate writeln
uses the builtin predicate nl, whch causes the next output character to which outputs the character Char on the current output stream. Stan-
be on a new line. As an example of its use, executing the conjunctive goal dard Prolog allows you to specify the output stream, but we do not give
(X=3, writeln( ['The value of X is ' ,XI ) produces the output examples here. The basic input predicate at the character level is get-
char (Char), whch reads a character C from the current input stream
The value of X is 3 and then unifies C with Char.
Program 12.2 defines read-word-list (Words), a utility predicate for
Note the use of the quoted atom 'The value of X is '. Both read and reading in a list of words, Words, from the current input, terminated
write operate at the term level. A lower level for I/O is the character by an end-of-words character, for example a period. Specific definitions
level. Edinburgh Prolog assumed that characters were represented by of the predicates word-char/l, f ill-char/l, and end-of -words-char/l
ASCII codes. Standard Prolog takes a broader perspective to support such need to be added. It can be used to allow freer form input. In Pro-
character sets as Kanji. The basic output predicate is put-char(Char), gram 12.2, words can be separated by arbitrarily many fill characters.
Chapter 12 219 Extra-Logical Predicates

The predicate read-word-list reads a character, FirstChar, and calls


12.2 Program Access and Manipulation
read-words (FirstChar,Words). T h s predicate does one of three ac-
tions, depending on what FirstChar is. If FirstChar is a word character,
So far programs have been assumed to be resident in computer memory,
then the next word is found. Word characters in Standard Prolog are up-
without discussion of how they are represented or how they got there.
percase and lowercase letters, underscores, and digits. The second action
Many applications depend on accessing the clauses in the program. Fur-
is to ignore filling characters, and so the next character is read, and the
thermore, if programs are to be modified at runtime, there must be a way
program continues recursively. Finally, if the character denoting the end
of adding (and deleting) clauses.
of the words is reached, the program terminates and returns the list of
The first Prologs, implemented as simple interpreted systems, classi-
words.
fied predicates as'builtin and static or user-defined and dynamic. The
It is important that the program must always read a character ahead
subsequent development of compilers and libraries require a more so-
and then test what it should do. If the character is useful, for example, a
phsticated classification.
word character, it must be passed down to be part of the word. Otherwise
Each user-defined predicate is either dynamic or static. The procedure
characters can get lost when backtracking. Consider the following read
of a dynamic predicate can be altered, whereas the procedure of a static
and process loop:
predicate cannot. Builtin predicates are assumed to be static. The system
process ( [ I ) -
get-char (C) , end-of -words-char(C) .
predicates introduced in t h s section apply only to dynamic predicates
and will probably cause error messages if applied to static predicates.
process ( [W I Words1 - In this book, we assume all predicates are dynamic unless otherwise
specified. In many Prologs, declarations are needed to make a predicate
get-char (C) , word-char (C) , get-word(C ,W) process (Words) '
dynamic.
If the first character in a word is not an end-of-words-char, the first The system predicate for accessing a program is clause (Head,Body).
clause will fail, and the second clause will cause the reading of the next The goal clause (Head,Body) must be called with Head instantiated. The
character. program is searched for the first clause whose head unifies with Head.
Returning to Program 12.2, the predicate read-word(Char ,Word, The head and body of this clause are then unified with Head and Body.
NextChar) reads a word Word given the current character Char and re- On backtraclung, the goal succeeds once for each unifiable clause in the
turns the next character after the word, NextChar. The list of characters procedure. Note that clauses in the program cannot be accessed via their
composing the word is found by word_chars/3 (with the same argu- body.
ments as read-word). The word is created from the list of characters Facts have the atom true as their body. Conjunctive goals are repre-
using the system predicate atom_list/2. In word-chars there is the sented using the binary functor , . The actual representations can be
same property of loolung ahead one character, so that no character is easily abstracted away, however.
lost. Consider Program 3.12 for member:
Predicates such as f ill-char/l and word-char/l exemplify data ab-
member (X, [X I Xsl ) .
straction in Prolog.
member (X , [Y I Ys] ) - member (X ,Ys)
Exercise for Section 12.1 The goal clause (member (X ,Ys) ,Body) has two solutions: {YS= [X/Xs],
Body=true) and {Ys= [YJYsll,Body=member (X,Ysl) 1. Note that a fresh
(i) Extend Program 12.2 to handle a wider range of inputs, for example, copy of the variables appearing in the clause is made each time a unifi-
numbers. cation is performed. In terms of the meta-logical primitives freeze and
Chapter 12 Extra-Logical Predicates

melt, the clause is stored in frozen form in the program. Each call to ple, if the clause already logically follows from the program. In such a
c l a u s e causes a new melt of the frozen clause. Thls is the logical coun- case, adding it will not affect the meaning of the program, since no new
terpart of the classic notion of reentrant code. consequences can be derived. Perhaps program efficiency will improve,
System predicates are provided both to add clauses to the program as some consequences could be derived faster. T h s use is exemplified in
and to remove clauses. The basic predicate for adding clauses is as- the lemma construct, introduced in Section 12.3.
s e r t z (Clause), which adds Clause as the last clause of the correspond- Similarly, retracting a clause is justified if the clause is logically re-
ing procedure. For example, a s s e r t z (f a t h e r (haran, l o t ) ) ? adds the dundant. In t h s case, retracting constitutes a lund of logical garbage
f a t h e r fact to the program. When describing rules an extra level of collection, whose purpose is to reduce the size of the program.
brackets is needed for technical reasons concerning the precedence of
terms. For example, a s s e r t z ( (parent (X, Y) - f a t h e r (X ,Y)) ) is the
correct syntax.
There is a variant of a s s e r t z , a s s e r t a , that adds the clause at the 12.3 Memo-Functions
beginning of a procedure.
If Clause is uninstantiated (or if Clause has the form H-B with H Memo-functions save the results of subcomputations to be used later in
uninstantiated), an error condition occurs. a computation. Remembering partial results is impossible withn pure
The predicate r e t r a c t (C) removes from the program the first clause Prolog, so memo-functions are implemented using side effects to the
in the program unifying with C. Note that to retract a clause such as program. Programming in this way can be considered bottom-up pro-
a - b , c , d, you need to specify r e t r a c t ( ( a- C) ). A call to r e t r a c t gramming.
may only mark a clause for removal, rather than physically removing it, The prototypical memo-function is lemma(Goa1). Operationally, it at-
and the actual removal would occur only when Prolog's top-level query is tempts to prove the goal Goal and, if successful, stores the result of the
solved. This is for implementation reasons, but may lead to anomalous proof as a lemma. It is implemented as
behavior in some Prologs.
Asserting a clause freezes the terms appearing in the clause. Retracting
the same clause melts a new copy of the terms. In many Prologs this
is exploited to be the easiest way of copying a term. Standard Prolog, The next time the goal P is attempted, the new solution will be used,
however, provides a builtin predicate copy_term/2 for this purpose. and there will be no unnecessary recomputation. The cut is present to
The predicates a s s e r t and r e t r a c t introduce to Prolog the possibil- prevent the more general program being used. Its use is justified only if
ity of programming with side effects. Code depending on side effects for P does not have multiple solutions.
its successful execution is hard to read, hard to debug, and hard to rea- Using lemmas is demonstrated with Program 12.3 for solving the Tow-
son about formally. Hence these predicates are somewhat controversial, ers of Hanoi problem. The performance of Program 3.31 in solving the
and using them is sometimes a result of intellectual laziness or incompe- problem is dramatically improved. It is well known that the solution of
tence. They should be used as little as possible when programming. Many the Towers of Hanoi with N disks requires ZN - 1 moves. For example,
of the programs to be given in this book can be written using a s s e r t and ten disks require 1,023 moves, or in terms of Program 3.31, 1,023 calls
r e t r a c t , but the results are less clean and less efficient. Further, as Pro- of hanoi ( I , A , B ,C ,Xs) . The overall number of general calls of hanoi/5
log compiler technology advances, the inefficiency in using a s s e r t and is significantly more.
r e t r a c t will become more apparent. The solution to the Towers of Hanoi repeatedly solves subproblems
It is possible, however, to give logical justification for some limited moving the identical number of disks. A memo-function can be used to
uses of a s s e r t and r e t r a c t . Asserting a clause is justified, for exam- recall the moves made in solving each subproblem of moving a smaller
Chapter 12 223 Extra-LogicalPredicates

hanoi(N,A,B,C,Moves) -
M o v e s is the sequence of moves required to move N disks 12.4 Interactive Programs
from peg A to peg B using peg C as an intermediary
according to the rules of the Towers of Hanoi puzzle. A common form of a program requiring side effects is an interactive loop.
hanoi(l,A,B,C, [A to B]) . A command is read from the terminal, responded to, and the next com-
hanoi(N,A,B,C,Moves)
N > 1,
- mand read. Interactive loops are implemented typically by while loops in
conventional languages. Program 12.4 gives the basic skeleton of such
N1 is N-1,
lernma(hanoi(Nl,A,C,B,Msl)),
programs, where a command is read, then echoed by being written on
hanoi(Nl,C,B,A,Ms2), the screen.
append(Ms1, [A to B IMs21 ,Moves) . The read/echo loop is invoked by the goal echo. The heart of the pro-
lemma(P) - P, asserta((P - !I). gram is the relation echo (X), where X is the term to be echoed. The pro-
gram assumes a user-defined predicate last-input/l, which succeeds if
Testing
test-hanoi(N,Pegs,Moves) -
hanoi (N,A ,B ,C,Moves) , Pegs = [A ,B ,CI .
the argument satisfies the termination condition for input. If the terrni-
nation condition is satisfied by the input, the loop terminates; otherwise
the term is written and a new term is read.
Program 12.3 Towers of Hanoi using a memo-function Note that the testing of the term is separate from its reading. This
is necessary to avoid losing a term: terms cannot be reread. The same
phenomenon occurred in Program 12.2 for processing characters. The
character was read and then separately processed.
number of disks. Later attempts to solve the subproblem can use the Program 12.4 is iterative and deterministic. It can be run efficiently on
computed sequence of moves rather than recomputing them. a system with tail recursion optimization, always using the same small
The idea is seen with the recursive clause of hanoi in Program 12.3. amount of space.
The first call to solve hanoi with N - 1 disks is remembered, and can be We give two examples of programs using the basic cycle of reading
used by the second call to hanoi with N - 1 disks. a term, and then processing it. The first is a line editor. The second
The program is tested with the prebcate test-hanoi (N, Pegs ,Moves). interactive program is a shell for Prolog commands, which is essentially
N is the number of disks, Pegs is a list of the three peg names, and a top-level interpreter for Prolog in Prolog.
Moves is the list of moves that must be made. Note that in order to take The first decision in writing a simple line editor in Prolog is how to
advantage of the memo-functions, a general problem is solved first. Only represent the file. Each line in the file must be accessible, together with
when the solution is complete, and all memo-functions have recorded the cursor position, that is the current position within the file. We use a
their results, are the peg names instantiated. structure file (Before,Af ter) , where Before is a list of lines before the
cursor, and After is a list of lines after the cursor. The cursor position is
Exercise for Section 12.3
echo - reado() , echo ()o.
(i) Two players take turns to say a number between 1 and 3 inclusive.
A sum is kept of the numbers, and the player who brings the sum
echo(X)
echo (x)
-- last-input ()o, ! .
write(X) , nl, read(Y) , ! , echo(y)
to 20 wins. Write a program to play the game to win, using memo-
functions. Program 12.4 Basic interactive loop
Chapter 12 Extra-Logical Predicates

edit - edit(file([ I,[ I)). after the command has been applied. The editing continues by calling
edit(Fi1e) -
write-prompt, read(Command), edit(File,Command).
edit/l on Filel. The t h r d edit/2 clause handles the case when no
command is applicable, indicated by the failure of apply. In thls case,
edit(File,exit)
edit (File ,Command)
!.-- an appropriate message is printed on the screen and the editing contin-
ues. The editing session is terminated by the command exit, whch is
apply(Command,File,Filel), ! , edit(File1). separately tested for by edit/2.
edit(File,Command) -
writeln( [Command, ' is not applicable']), ! , edit(Fi1e).
Let us look at a couple of apply clauses, to give the flavor of how
commands are specified. Particularly simple are commands for moving
apply(up,file(CXIXsl ,Ys),file(Xs, [XIYsl)).
apply(up(N),file(Xs,Ys),file(Xsl,Ysl))
N > 0, up(N,Xs,Ys,Xsl,Ysl).
- the cursor. The .clause

apply(down,file(Xs, CYlYsl) , f i l e ( [ ~ I ~ s,Ys)).


]
apply(insert(Line),file(Xs,Ys),file(~s,[Line~Ysl)). says that we move the cursor up by moving the line immediately above
the cursor to be immediately below the cursor. The command fails if the
apply(delete,file(Xs,[YIYsl),file(Xs,Ys)).
apply(print,file(~XIXsl,Ys),file(~XIXsl,Ys))
write(X) , nl.
- cursor is at the top of the file. The command for moving the cursor down,
apply(print(*) ,file(Xs,Ys) ,file(Xs,Ys)) - also shown in Program 12.5, is analogous to moving the cursor up.
Moving the cursor up N lines rather than a single line involves using an
reverse(Xs,Xsl), write-file(Xsl), write-file(Ys).
auxiliary predicate up/5 to change the cursor position in the file. Issues
up(N, [ 1 ,Ys,[ 1 ,Ys).
up(0,Xs,Ys,Xs,Ys).
of robustness surface in its definition. Note that apply tests that the
up(N, [XIXsl ,Ys,Xsl,Ysl) - argument to up is sensible, i.e., a positive number of lines, before up
is invoked. The predicate up itself handles the case when the number
N > 0 , N1 is N-1, u p ( ~ l , X s ,[XIYs] ,Xsl,Ysl).
write-f ile( [XlXsl) - of lines to be moved up is greater than the number of lines in the file.
The command succeeds with the cursor placed at the top of the file.
write(X), nl, write-file(Xs).
write-file([ 1). Extending the editor program to move a cursor down N lines is posed
write-prompt - write(' >> '1, nl. as an exercise at the end of this section.
Other commands given in Program 12.5 insert and delete lines. The
Program 12.5 A line editor
command for insert, insert (Line), contains an argument, namely the
line to be inserted. The command for delete is straightforward. It fails
restricted to be at the end of some line. The lines before the cursor will if the cursor is at the bottom of the screen. Also in the editor are com-
be in reverse order to give easier access to the lines nearer the cursor. mands for printing the line above the cursor, print, and for printing the
The basic loop accepts a command from the keyboard and applies it to whole file, print (*I.
produce a new version of the file. Program 12.5 is the editor. The editor commands are mutually exclusive. Only one apply clause is
An editing session is invoked by edit, which initializes the file be- applicable for any command. As soon as an apply goal succeeds, there
ing processed to the empty file, file ( [ I , [ I ) ) . The interactive loop are no other possible alternatives. Prolog implementations that support
is controlled by edit (File). It writes a prompt on the screen, using indexing would find the correct clause immediately and leave no choice
write-prompt, then reads and processes a command. The process- points. Imposing determinism via exploitation of indexing is a little dif-
ing uses the basic predicate edit (File,Command), whch applies the ferent than adding explicit cuts, as described in Section 11.1, where the
command to the file. The application is performed by the goal ap- cuts would have been applied directly to the apply facts themselves. The
ply(Command,File,Filel), where Filel is the new version of the file difference between the two approaches is merely cosmetic. Note that a
Chapter 12 Extra-Logical Predicates

shell - Both shell-solve-ground and shell-solve use the meta-variable fa-


shell-prompt, read(Goal1, shell(Goa1). cility to call the goal to be solved. The success or failure of the goal
shell(exit1
shell (Goal)
--
!. determines the output message. These predicates are the simplest exam-
ples of meta-interpreters, a subject discussed in Chapter 17.
ground(Goal) , ! , shell-solve-ground(Goal), shell.
shell(Goa1) - The shell-solve procedure shows an interesting solve-write-failcom-
bination, whch is useful to elicit all solutions to a goal by forced back-
shell-solve(Goal), shell.
shell-solve(Goa1) - traclung. Since we do not wish the shell to fail, an alternative clause is
provided, which succeeds when all solutions to the goal are exhausted. It
shell-solve(Goa1) -
Goal, write(Goal), nl, fail.

write('No (more) solutions'), nl.


is interesting to note that it is not possible to collect all solutions to goals
in a straightforward way without using some sort of side effect. T h s is
shell-solve-ground(Goa1) - explained further in Chapter 16 on second-order programming.
The shell can be used as a basis for a logging facility to keep a record
Goal, ! , write('Yes'),
shell-solve-ground(Goa1)
write('NoJ), nl.
- nl.
of a session with Prolog. Such a facility is given as Program 12.7. T h s
new shell is invoked by log, which calls the basic interactive predicate
shell-prompt - write('Next command? '1. shell(F1ag) with Flag initialized to log. The flag takes one of two val-
Program 12.6 An interactive shell ues, log or nolog, and indicates whether the output is currently being
logged.
The logging facility is an extension of Program 12.6. The principal
predicates take an extra argument, whch indicates the current state of
cut is still needed in the second edit clause to indicate that successful logging. Two extra commands are added, log and nolog, to turn logging
execution of a command and reporting of an error message are mutually on and off.
exclusive. The flag is used by the predicates concerned with I/O. Each message
A possible extension to the editor is to allow each command to handle written on the screen must also be written in the logging file. Also, each
its own error message. For example, suppose you wanted a more helpful goal read is inserted in the log to increase the log's readability. Thus calls
message than "Command not applicable" when trying to move up when to read in Program 12.6 are replaced by a call to shell-read, and calls
at the top of the file. T h s would be handled by extending the apply to write replaced by calls to shell-write.
clause for moving up in the file. The definition of shell-write specifies what must be done:
We shift from editors to shells. A shell accepts commands from a
terminal and executes them. We illustrate with an example of a shell for
shell-write (X ,nolog) -
write (XI .
shell-write (x,log) -- write (x), file-write ( [XI , 'prolog.log' ) .
answering Prolog goals. T h s is presented as Program 12.6.
The shell is invoked by shell. The code is similar to the editor. The If the flag is currently nolog, the output is written normally to the screen.
shell gives a prompt, using shell-prompt, then reads a goal and tries If the flag is log, an extra copy is written to the file prolog. log. The
to solve it using shell(Goa1). A distinction is made between solving predicate f ile-write(X,File) writes the line X to file File.
ground goals, where a yes/no answer is given, and solving nonground The remaining two predicates in Program 12.7, f ile_write/2 and
goals, where the answer is the appropriately instantiated goal. These two close-logging-f ile, involve interacting with the underlying file sys-
cases are handled by shell-solve-ground and shell-solve, respec- tem. Appropriate commands from Standard Prolog are given, and the
tively. The shell is terminated by the goal exit. reader is referred to a Prolog manual for more information.
Chapter 12 Extra-Logical Predicates

log - shell(1og) . Exercises for Section 12.4


shell(F1ag) -
shell-prompt, shell-read(Goal,Flag), shell(~oa1,Flag). (i) Extend Program 12.5, the editor, to handle the following com-
shell (exit ,Flag) -
! , close-logging-file.
mands:

shell(nolog,Flag) +-
(a) Move the cursor down N lines,
! , shell(no1og).
shell(log,Flag)
! , shell(1og).
- (b) Delete N lines,

shell (Goal ,Flag) -


ground(Goa1) , ! , shell-solve-ground(Goa1 ,Flag) , shell (Flag)
(c) Move to a line containing a given term,

shell (Goal ,Flag) -


shell-solve(Goal,Flag), shell(Flag1.
(d) Replace one term by another,
(e) Any command of your choice.
shell~solve(Goal,Flag) +-

(ii) Modify the logging facility, Program 12.7, so that the user can spec-
shell~solve(Goal,Flag) -
Goal, shell-write(Goal,Flag), nl, fail.

shell-write('No (more) solutions',~lag), nl.


ify the destination file of the logged output.

shell-solve-ground(Goa1 ,Flag)
Goal, ! , shell-write('Yes',Flag),
- -- -- - -
- - -- - -

shell~solve~ground(Goal,Flag)
shell-write('NoJ,Flag), nl.
- nl.
12.5 Failure-DrivenLoops

shell-prompt - write('Next command? '1. The interactive programs in the previous section were all based on tail re-
shell-read(X, log) -
read(X) ,
file-write(['Next command? ',~],'prolog.log').
cursive loops. There is an alternative way of writing loops in Prolog that
are analogous to repeat loops in conventional languages. These loops
shell-read(X,nolog) read(X). - are driven by failure and are called failure-driven loops. These loops are
shell-write(X,nolog)
shell-write(X,log) -- write(X).
write(X), file-write(~,'prolog.log').
useful only when used in conjunction with extra-logical predicates that
cause side effects. Their behavior can be understood only from an opera-
file-write(X,File) - write-term(File,Term,[ 1). tional point of view.
close-logging-file - close('prolog.log'). A simple example of a failure-driven loop is a query Goal, w r i t e
(Goal) , n l , f a i l ? , which causes all solutions to a goal to be written on
Program 12.7 Logging a session the screen. Such a loop is used in the shells of Programs 12.6 and 12.7.
A failure-driven loop can be used to define the system predicate
tab(N) for printing N blanks on the screen. It uses Program 8.5 for be-
tween:

tab(N) - between(1 , N 91) 9 ') , fail

Each of the interactive programs in the previous section can be rewrit-


ten using a failure-driven loop. The new version of the basic interactive
loop is given as Program 12.8. It is based on a nonterrninating system
Chapter 12 Extra-Logical Predicates

echo - repeat, reado(), echo(X), !. Tail recursive loops are preferable to repeat loops because the latter
echo(X)
echo(X)
-- last-input (XI, ! .
write(X), nl, fail.
have no logical meaning. In practice, repeat loops are often necessary
to run large computations, especially on Prolog implementations without
repeat.
tail recursion optimization or garbage collection. Explicit failure typically
repeat - repeat. initiates some implementation-dependent reclamation of space.

Program 12.8 Basic interactive repeat loop


Exercise for Section 12.5

consult (File) -
The clauses of the program in the file File are read and asserted.
(i) Write your own version of the builtin predicate a b o l i s h ( F , N) that
retracts all the clauses for the procedure F of arity N.
consult (File) open(Fi1e ,read,DD) , consult-loop(DD), close(DD) .
-
+

consult-loop(DD) repeat, read(C1ause) , process (clause ,DD) , ! .


- at-end-of-stream(DD. 12.6 Background
process(Clause,DD)
process(Clause,DD) - assertz(Clause), fail.

Program 12.9 Consulting a file 1/0 has never really blended well with the rest of the language of Pro-
log. Its standard implementation, with side effects, relies solely on the
procedural semantics of Prolog and has no connection to the underlying
predicate r e p e a t , which can be defined by the minimal recursive proce- logic programming model. For example, if an output is issued on a fail-
dure in Program 12.8. Unlike the Program 12.4 goal, the goal echo(X) ing branch of a computation, it is not undone upon backtraclung. If an
fails unless the termination condition is satisfied. The failure causes input term is read, it is lost on backtraclung, as the input stream is not
backtracking to the r e p e a t goal, whch succeeds, and the next term is backtrackable.
read and echoed. The cut in the definition of echo ensures that the repeat Concurrent logic languages attempt to remedy the problem and to in-
loop is not reentered later. tegrate 1/0 better with the logic programming model by identifying the
Failure-driven loops that use r e p e a t are called repeat loops and are 1/0 streams of devices with the logical streams in the language (Shapiro,
the analogue of repeat loops from conventional languages. Repeat loops 1986).Perpetual recursive processes can produce or consume incremen-
are useful in Prolog for interacting with the outside system to repeatedly tally those potentially unbounded streams.
read and/or write. Repeat loops require a predicate that is guaranteed Self-modifying programs are a bygone concept in computer science.
to fail, causing the iteration to continue, unless the loop should be ter- Modern programming languages preclude this ability, and good assem-
minated. The goal echo(X) in Program 12.8 serves that function, only bly language practice also avoids such programming tricks. It is ironic
succeeding when the last input is reached. A useful heuristic for building that a programming language attempting to open a new era in computer
repeat loops is that there should be a cut in the body of the clause with programming opens the front door to such arcane techmques, using the
the r e p e a t goal, whch prevents a nonterminating computation were the predicates a s s e r t and r e t r a c t .
loop to be reentered via backtraclung. These program manipulation predicates of Prolog were devised ini-
We use a repeat loop to define the system predicate c o n s u l t ( F i l e ) tially as a low-level mechanism for loading and reloading programs, im-
for reading in a file of clauses and asserting them. Program 12.9 contains plemented in DEC-10 Prolog by the c o n s u l t and r e c o n s u l t predicates.
its definition. The system predicates o p e d 3 and c l o s e / l are used for However, like any other feature of a language, they ended up being used
opening and closing an input file, respectively. for tasks that, we believe, were not intended by their original designers.
Chapter 12

Reluctantly, we must acknowledge that a s s e r t and r e t r a c t are part


of Prolog, and clarify the anomalies. Attempts have been made in t h s
direction. Inconsistencies between hfferent Prolog implementations are Program Development
discussed in Moss (1986). The best way of handling retracts seems to be
the logical update view presented in Lindholm and O'Keefe (1987).
The discussion of static and dynamic predicates comes from the Stan-
dard Prolog draft (Scowen, 1991).
The program for the Towers of Hanoi was shown to us by Shmuel Safra.
Memo-functions in the context of artificial intelligence were proposed by
Donald Michie (1968).
The line editor is originally due to Warren (1982b).
Software engineering considerations are as relevant for programming
in logic programming languages as in procedural languages. Prolog is
no different from any other language in its need for a methodology to
build and maintain large programs. A good programming style is im-
portant, as is a good program development methodology. This chapter
discusses programming style and layout and program development, and
introduces a method called stepwise enhancement for systematic con-
struction of Prolog programs.

---

1 3.1 Programming Style and Layout

One basic concern in composing the programs in this book has been to
make them as declarative as possible to increase program clarity and
readability. A program must be considered as a whole. Its readability is
determined by its physical layout and by the choice of names appear-
ing in it. T h s section discusses the guidelines we use when composing
programs.
An important influence in making programs easy to read is the naming
of the various objects in the program. The choice of all predicate names,
variable names, constants, and structures appearing in the program af-
fect readability. The aim is to emphasize the declarative reading of the
program.
We choose predicate names to be a word (or several words) that names
relations between objects in the program rather than describing what the
Chapter 1 3 Program Development

program is doing. Coining a good declarative name for a procedure does The heads of all clauses are aligned, the goals in the body of a clause
not come easily. are indented and occupy a separate line each. A blank line is inserted
The activity of programming is procedural. It is often easier to name between procedures, but there is no space between individual clauses of
procedurally than declaratively (and programs with procedural names a procedure.
usually run faster :-). Once the program works, however, we often revise Layout in a book and the typography used are not entirely consistent
the predicate names to be declarative. Composing a program is a cyclic with actual programs. If all the goals in the body of a clause are short,
activity in whch names are constantly being reworked to reflect our then have them on one line. Occasionally we have tables of facts with
improved understanding of our creation, and to enhance readability by more than one fact per line.
us and others. A program can be self-documenting if sufficient care is taken with
Mnemonic variable names also have an effect on program readability. these two factors and the program is sufficiently simple. Given the nat-
A name can be a meaningful word (or words) or a standard variable form ural aversion of programmers to comments and documentation, this is
such as Xs for lists. very desirable.
Variables that appear only once in a clause can be handled separately. In practice, code is rarely self-documenting and comments are needed.
They are in effect anonymous, and from an implementation viewpoint One important part of the documentation is the relation scheme, which
need not be named. Standard Prolog supports a special syntactic con- can be presented before the clauses defining that relation, augmented
vention, a single underscore, for referring to anonymous variables. Using with further explanations if necessary. The explanations used in t h s
this convention, Program 3.12 for member would be written book define the relation a procedure computes. It is not always easy to
come up with a precise, declarative, natural language description of a
member (X, [X I -1 ) . relation computed by a logic program. However, the inability to do so
member (X, [- 1 Ysl ) - member (X,Ys) usually indicates that the programmer does not fully understand the
creation, even if the creation actually works. Hence we encourage the use
The advantage of the convention is to highlight the significant variables of the declarative documentation conventions adopted in thls book. They
for unification. The disadvantage is related; the reading of clauses be- are a good means of communicating to others what a program defines as
comes procedural rather than declarative. well as a discipline of thought, enabling programmers to t h n k about and
We use different syntactic conventions for separating multiple words reflect on their own creations.
in variable names and predicate functors. For variables, composite words
are run together, each new word starting with a capital letter. Multiple
words in predicate names are linked with underscores. Syntactic conven-
tions are a matter of taste, but it is preferable to have a consistent style. 13.2 Reflections on Program Development
The layout of individual clauses also has an effect on how easily pro-
grams can be understood. We have found the most helpful style to be Since programming in pure Prolog is as close to writing specifications
as any practical programming language has gotten, one might hope that
f oo ( (Arguments)) + pure Prolog programs would be bug-free. Ths, of course, is not the case.
barl ( (Argumentsl)), Even when axiomatizing one's concepts and algorithms, a wide spectrum
bar2 ( (Arguments2)), of bugs, quite similar to ones found in conventional languages, can be
encountered.
Stating it differently, for any formalism there are sufficiently com-
bar, ((Arguments,) ) . plex problems for whch there are no self-evidently correct formulations
Chapter 13 Program Development

of solutions. The difference between low-level and hgh-level languages, thoughts and concepts. For experienced Prolog programmers, Prolog is
then, is only the threshold after whch simple examination of the pro- not just a formalism for coding a computer, but also a formalism in
gram is insufficient to determine its correctness. whch ideas can be expressed and evaluated - a tool for thinking.
There are two schools of thought on what to do on such an occasion. A t h r d answer is that the properties of the hgh-level formalism of
The "verification" school suggests that such complex programs be ver- logic may eventually lead to practical program development tools that
ified by proving that they behave correctly with respect to an abstract are an order of magnitude more powerful then the tools used today.
specification. It is not clear how to apply t h s approach to logic programs, Examples of such tools are automatic program transformers, partial-
since the distance between the abstract specification and the program is evaluators, type inference programs, and algorithrmc debuggers. The lat-
much smaller then in other languages. If the Prolog axiomatization is not ter are addressed in Section 17.3, where program diagnosis algorithms
self-evident, there is very little hope that the specification, no matter in and their implementation in Prolog are described.
what language it is written, would be. Unfortunately, practical Prolog programming environments incorpo-
One might suggest using full first-order logic as a specification formal- rating these novel ideas are not yet widely available. In the meantime,
ism for Prolog. It is the authors' experience that very rarely is a specifi- a simple tracer, such as explained in Section 17.2, is most of what one
cation in full first-order logic shorter, simpler, or more readable then the can expect. Nevertheless, large and sophisticated Prolog programs can
simplest Prolog program defining the relation. be developed even using the current Prolog environments, perhaps with
Given t h s situation, there are weaker alternatives. One is to prove greater ease than in other available languages.
that one Prolog program, perhaps more efficient though more complex, The current tools and systems do not dictate or support a specific
is equivalent to a simpler Prolog program, whch, though less efficient, program development methodology. However, as with other symbolic
could serve as a specification for the first. Another is to prove that a pro- programming languages, rapid prototyping is perhaps the most natural
gram satisfies some constraint, such as a "loop invariant," whch, though development strategy. In t h s strategy, one has an evolving, usable pro-
not guaranteeing the program's correctness, increases our confidence in totype of the system in most stages of the development. Development
it. proceeds by either rewriting the prototype program or extending it. An-
In some sense, Prolog programs are executable specifications. The al- other alternative, or complementary, approach to program development
ternative to staring at them, trying to convince ourselves that they are is "think top-down, implement bottom-up." Although the design of a sys-
correct, is to execute them, and see if they behave in the way we want. tem should be top-down and goal-driven, its implementation proceeds
T h s is the standard testing and debugging activity, carried out in pro- best if done bottom-up. In bottom-up programming each piece of code
gram development in any other programming language. All the classical written can be debugged immediately. Global decisions, such as repre-
methods, approaches, and common wisdom concerning program testing sentation, can be tested in practice on small sections of the system, and
and debugging apply equally well to Prolog. cleaned up and made more robust before most of the programming has
What is the difference, then, between program development in conven- been done. Also, experience with one subsystem may lead to changes in
tional, even symbolic languages and Prolog? the design of other subsystems.
One answer is that although Prolog programming is "just" program- The size of the chunks of code that should be written and debugged as
ming, there is some improvement in ease of expression and speed of de- a whole varies and grows as the experience of the programmer grows. Ex-
bugging compared to other lower-level formalisms - we hope the reader perienced Prolog programmers can write programs consisting of several
has already had a glimpse of it. pages of code, knowing that what is left after writing is done is mostly
Another answer is that declarative programming clears your mind. Said simple and mundane debugging. Less experienced programmers might
less dramatically, programming one's ideas in general, and program- find it hard to grasp the functionality and interaction of more then a few
ming in a declarative and high-level language in particular, clarifies one's procedures at a time.
Chapter 13 Program Development

We would like to conclude t h s section with a few moralistic state- and goals is called an enhancement of the skeleton. Building an enhance-
ments. For every programming language, no matter how clean, elegant, ment from a skeleton will be called applying a technique.
and hgh-level, one can find programmers who will use it to write dirty, For example, consider Program 8.6a for summing a list of numbers,
contorted, and unreadable programs. Prolog is no exception. However, reproduced here:
we feel that for most problems that have an elegant solution, there is an
elegant expression of that solution in Prolog. It is a goal of this book to
sumlist ( [XI XS],Sum) - sumlist (Xs ,XsSm), Sum is X+XsSum.
convey both this belief and the tools to realize it in concrete cases, by
sumlist ( [ I ,0>.
showing that aesthetics and practicality are not necessarily opposed or The control flow embodied in the sumlist program is traversing the
conflicting goals. Put even more strongly, elegance is not optional. list of numbers. The skeleton is obtained by dropping the second ar-
gument completely, restricting to a predicate with one argument, and
removing goals that only pertain to the second argument. T h s gives the
following program, which should be identifiable as Program 3.1 1 defining
13.3 Systematizing Program Construction a list.
list ( [XI Xsl ) -- list (Xs) .
The pedagogic style of this book is to present well-constructed programs
list([ I).
illustrating the important Prolog programming techniques. The examples
are explained in sufficient detail so that readers can apply the techniques The extra argument of the sumlist program calculates the sum of
to construct similar programs to meet their own programming needs. the numbers in the list. Thls form of calculation is very common and
Implicitly, we are saying that Prolog programming is a skill that can be appeared in several of the examples in Chapter 8.
learned by observing good examples and abstracting the principles. Another enhancement of the list program is Program 8.11 calculating
Learning by apprenticeshp, observing other programs, is not the only the length of a list. There is a clear similarity between the programs
way. As experience with programming in Prolog accumulates, more sys- for length and sumlist. Both use a similar technique for calculating a
tematic methods of teaching Prolog programming are emerging. The number, in one case the sum of the numbers in the list, in the second the
emergence of systematic methods is analogous to the emergence of length of the list.
structured programming and stepwise refinement in the early 1970s af-
ter sufficient experience had accumulated in writing programs in the
length([X I Xs] ,N) - length(Xs,Nl), N is N1+1
length( [ I ,0) .
computer languages of the 1950s and 1960s.
In this section, w7e sketch a method to develop Prolog programs. The Multiple techniques can be applied to a skeleton. For example, we can
reader is invited to reconstruct for herself how t h s method could be ap- apply both summing elements and counting elements in one pass to get
plied to develop the programs in Parts 111 and IV of t h s book. Underlying the program sum-length:
the method is a desire to provide more structure to Prolog programs so
that software components can be reused and large applications can be
sum-length( [X I Xsl ,Sum,N) -
sumlist(Xs,XsSum,Nl), Sum is X+XsSum, N is Nl+1
routinely maintained and extended.
sum-length( [ 1 , 0 , 0 ) .
Central to the method is identifying the essential flow of control of a
program. A program embodying a control flow is called a skeleton. Extra Intuitively, it is straightforward to create the sum-length program
goals and arguments can be attached to a skeleton. The extra goals and from the programs for sumlist and length. The arguments are taken
arguments are entwined around the central flow of control and perform directly and combined to give a new program. We call t h s operation com-
additional computations. The program containing the extra arguments position. In Chapter 18, a program for composition is presented.
Chapter 13 Program Development

Another example of a techmque is adding a pair of arguments as an union(Xs,Ys,Us) -


accumulator and a final result. The techmque is informally described in U s is the union of the elements in Xs and Ys.
Section 7.5. Applylng the appropriate version of the techmque to the
list skeleton can generate Program 8.6b for sumlist or the iterative
union([XIXs] ,Ys,Us)
union([XIXs],Ys,[XIUsl)
union([ 1 ,Ys,Ys).
-
member(X,Ys), union(Xs,Ys,Us).
+

nonmember(X,Ys), u n i o n ( X s , Y s , ~ s ) .
version of length,whch is the solution to Exercise 8.3(vii).
Identifying control flows of programs may seem contradictory to the Program 13.1 Finding the union of two lists
ideal of declarative programming espoused in the previous section. How-
ever, at some level programming is a procedural activity, and describ-
ing well-written chunks of code is fine. It is our belief that recognizing intersect (Xs,Ys,Is) -
patterns of programs makes it easier for people to develop good style. Is is the intersection of the elements in X s and Ys.
Declarativeness is preserved by ensuring wherever possible that each en-
hancement produced be given a declarative reading.
intersect( CXIXsl ,Ys,[XI Is1 )
intersect([XIXs],Ys,Is)
intersect([ l , Y s , [ I).
- -member(X,Ys) , intersect ( X S , Y ~ , I S )
nonmember(X,Ys), i n t e r s e c t ( X s , ~ s , ~ s ) .
The programming method called stepwise e n h a n c e m e n t consists of
three steps: Program 13.2 Finding the intersection of two lists

1. Identify the skeleton program constituting the control flow.


2. Create enhancements using standard programming techniques. union-intersect (Xs,Ys,Us,Is) -
3. Compose the separate enhancements to give the final program. U s and Is are the union and intersection, respectively, of the
elements in X s and Ys.
We illustrate stepwise enhancement for a simple example - calculat- union-intersect ( [X IXsl ,Ys,Us,[XI Is1 ) -
member(X,Ys), union~intersect(Xs,Ys,Us,Is).
ing the union and intersection of two lists of elements. For simplicity we
assume that there are no duplicate elements in the two lists and that we
union-intersect ( [XIXs] , Y s ,[X [ U s ],Is) -
nonmember(X,Ys), union-intersect(Xs,Ys,Us,Is).
do not care about the order of elements in the answer. union-intersect ( [ I ,Ys,Ys,[ I).
A skeleton for this program follows. The appropriate control flow is to
traverse the first list, checking whether each element is a member or not Program 13.3 Finding the union and intersection of two lists
of the second list. There will be two cases:

skel([XIXs] ,Ys) - member(X,Ys), skel(Xs,Ys). The intersection, given as Program 13.2, is determined with a similar
skel ( [XI Xs] ,Ys) - nonmember (X,Ys) , skel (Xs ,Ys) . technique. We again consider each clause in turn. When an element in the
first list is a member of the second list, it is included in the intersection.
skel( [ 1 ,Ys).
When an element in the first list is not a member of the second list, it
To calculate the union, we need a thlrd argument, whch can be built is not included in the intersection. When the first list is empty, so is the
top-down in the style discussed in Section 7.5. We consider each clause intersection.
in turn. When an element in the first list is a member of the second list, Calculating both the union and the intersection can be determined in a
it is not included in the union. When an element in the first list is not single traversal of the first list by composing the two enhancements. This
a member of the second list, it is included in the union. When the first program is given as Program 13.3.
list is empty, the union is the second list. The enhancement for union is Developing a program is typically straightforward once the skeleton
given as Program 13.1. has been decided. Knowing what skeleton to use is less straightforward
Chapter 13 Program Development

and is learned by experience. Experience is necessary for any design task. procedure p(T1 ,Tr , . . . ,T,)
By splitting up the program development into three steps, however, the Types: TI: type 1
design process is simplified and given structure. T2:type 2
A motivation behnd giving programs structure, as is done by stepwise
enhancement, is to facilitate program maintenance. It is easy to extend T,: type n
a program by adding new techniques to a skeleton, and it is possible to
Relation scheme:
improve programs by changing skeletons while maintaining techniques.
Further, the structure makes it easy to explain a program. Modes of use:
Skeletons and techtuques can be considered as constituting reusable Multiplicities of solution:
software components. T h s will be illustrated in Chapter 17, where the Figure 13.1 Template for a specification
same skeleton meta-interpreter is useful both for program debugging
and for expert system shells.
Having raised software engineering issues such as maintainability and Prolog programs ~nheritfrom logic programs the possibility of be-
reusability, we conclude this chapter by examining two other issues that ing multi-use. In practice, multi-use is rare. A specification should state
must be addressed if Prolog is to be routinely used for large software whch uses are guaranteed to be correct. That is the purpose of the
projects. The place of specifications should be clarified, and modules are modes of use component in Figure 13.1. Modes of use are specified by
necessary if code is to be developed in pieces. the instantiation state of arguments before and after calls to the predi-
It is clear from the previous section that we do not advocate using cate.
first-order logic as a specification language. Still, it is necessary to have For example, the most common mode of use of Program 3.15 for ap-
a specification, that is, a document explaining the behavior of a program pend(Xs ,Ys ,Zs) for concatenating two lists X s and Y s to produce a list
sufficiently so that the program can be used without the code having to Z s is as follows. X s and Y s are instantiated at the time of call, whereas
be read. We believe that a specification should be the primary form of Z s is not, and all three arguments are instantiated after the goal suc-
documentation and be given for each procedure in a program. ceeds. Calling append/3 with all three arguments instantiated is a dif-
A suggested form for a specification is given in Figure 13.1. It consists ferent mode of use. A common convention, taken from DEC-10 Prolog
of a procedure declaration, effectively giving the name and arity of the is to use + for an instantiated argument, - for an uninstantiated argu-
predicate; a series of type declarations about the arguments; a relation ment, and ? for either. The modes for the preceding use of append are
scheme; and other important information such as modes of use of the append ( +, +, - ) before the call and append (+, +, +) after the call.
predicate and multiplicities of solutions in each mode of use. We discuss More precise statements can be made by combining modes with types.
each component in turn. The mode of use of the current example becomes the following: Before
Types are emerging as important in Prolog programs. An untyped lan- the call the first two arguments are complete lists and the third a vari-
guage facilitates rapid prototyping and interactive development, but for able; after the call all three arguments are complete lists.
more systematic projects, imposing types is probably worthwhle. Multiplicities are the number of solutions of the predicate, and should
The relation scheme is a precise statement in English that explains the be specified for each mode of use of the program. It is useful to give
relation computed by the program. All the programs in this book have both the minimum and maximum number of solutions of a predicate.
a relation scheme. It should be stressed that relation schemes must be The multiplicities can be used to reason about properties of the program.
precise statements. We believe that proving properties of programs will Modules are primarily needed to allow several people to work on a
proceed in the way of mathematics, where proofs are given by precise project. Several programmers should be able to develop separate compo-
statements in an informal language. nents of a large system without worrying about undesirable interactions
Chapter 13 Program Development

such as conflict of predicate names. What is needed is a mechanism for Underlying theory is given in Power and Sterling (1990) and Kirschen-
specifying what is local to a module and whch predicates are imported baum, Sterling, and Jain (1993). An application of structuring Prolog
and exported. programs using skeletons and techmques to the inductive inference of
Current Prolog systems provide primitive facilities for handling mod- Prolog programs can be found in Kirschenbaum and Sterling (1991).
ules. The current systems are either atom-based or predicate-based, de- Automatic incorporation of t e c h q u e s into skeletons via partial evalu-
pending on what is made local to the module. Directives are provided for ation has been described in Lakhotia (1989).
specifying imports and exports. Experience is growing in using existing The discussion on specifications for Prolog programs is strongly influ-
module facilities, which will be translated into standards for modules enced by Deville (1990).
that will ultimately be incorporated into Standard Prolog. The current Exercise 13.3(ii) was suggested by Gilles Kahn. The example is orig-
draft on modules in Standard Prolog is in too much flux to describe here. inally due to Bird. Exercise 13.3(iii) emerged through interaction with
The user needing modules should consult the relevant Prolog manual. Marc Kirschenbaum. Solutions to both exercises are given in Deville, Ster-
ling, and Deransart (1991).
Exercises for Section 13.3

(i) Enhance Program 13.3 to build the list of elements contained in the
first list but not in the second list.
(ii) Write a program to solve the following problem. Given a binary tree
T with positive integers as values, build a tree that has the same
structure as T but with every node replaced by the maximum value
in the tree. It can be accomplished with one traversal of the tree.
(Hint: Use Program 3.23 as a skeleton.)
(iii) Write a program to calculate the mean and mode of an ordered list
of numbers in one pass of the list.

1 3.4 Background

Commenting on Prolog programming style has become more prevalent in


recent Prolog textbooks. There are useful discussions in both Ross (1989)
and O'Keefe (1990). The latter book also introduces program schemas,
which have parallels with skeletons and techniques.
Stepwise enhancement has emerged from ongoing work at Case West-
ern Reserve University, first in the COMPOSERS group and more recently
in the ProSE group. Examples of decomposing Prolog programs into
skeletons and techniques are given in Sterling and Kirschenbaum (1993)
and presented in tutorial form in Deville, Sterling, and Deransart (1991).
111 Advanced Prolog Programming Techniques

The expressive power and hlgh-level nature of logic programming can


be exploited to write programs that are not easily expressed in conven-
tional programming languages. Different problem-solving paradigms can
be supported, and alternative data construction and access mechanisms
can be used.
The simple Prolog programs of the previous part are examples of the
use of basic programming techmques, reinterpreted in the context of
logic programming. T h s part collects more advanced techmques that
have evolved in the logic programming community and exploit the spe-
cial features of logic programs. We show how they can be used to advan-
tage.

Leonardo Da Vinci. Study of a Woman's Hands folded over her Breast. Silver-
point on pink prepared paper, heightened with white. About 1478. Windsor
Castle, Royal Library.
Nondeterministic Programming

One feature of the logic programming computation model laclung in con-


ventional programming models is nondeterminism. Nondeterminism is
a technical concept used to define, in a concise way, abstract computa-
tion models. However, in addition to being a powerful theoretical con-
cept, nondeterminism is also useful for defining and implementing algo-
rithms. Thls chapter shows how, by thinlung nondeterministically, one
can construct concise and efficient programs.
Intuitively, a nondeterministic machne can choose its next operation
correctly when faced with several alternatives. True nondeterministic
machines cannot be realized but can be simulated or approximated. In
particular, the Prolog interpreter approximates the nondeterministic be-
havior of the abstract interpreter of logic programs by sequential search
and backtraclung, as explained in Chapter 6. However, the fact that non-
determinism is only simulated without being "really present" can be ab-
stracted away in many cases in favor of nondeterministic thlnking in
much the same way as pointer manipulation details involved in unifica-
tion can be abstracted away in favor of symbolic thmlung.

Generate-and-test is a common technique in algorithm design and pro-


gramming. Here is how generate-and-test works for problem solving. One
process or routine generates candidate solutions to the problem, and an-
other process or routine tests the candidates, trying to find one or all
candidates that actually solve the problem.
Chapter 14 Nondeterministic Programming

It is easy to write logic programs that, under the execution model of verb (Sentence,Verb) -
Verb is a verb in the list of words Sentence.
Prolog, implement the generate-and-test techmque. Such programs typi-
cally have a conjunction of two goals, in whch one acts as the generator
and the other tests whether the solution is acceptable, as in the following
clause:
Vocabulary
find (X) - generate (X) , test ()o noun(man1.
article(a).
noun(woman).
verb(1oves).
T h s Prolog program would actually behave like a conventional, procedu- mernber(X,Xs) - see Program 3.12.
ral, generate-and-testprogram. When called with f ind(X)?, generate (X) Program 14.1 Finding parts of speech in a sentence
succeeds, returning some X,with whch test (X) is called. If the test goal
fails, execution backtracks to generate (X), which generates the next
element. T h s continues iteratively until the tester successfully finds a Another simple example is testing whether two lists have an element
solution with the distinguishng property or until the generator has ex- in common. Consider the predicate intersect (Xs,Ys), whch is true if
hausted all alternative solutions. Xs and Ys have an element in common:
The programmer, however, need not be concerned with the generate-
and-test cycle and can view this techmque more abstractly, as an instance
intersect (Xs ,Ys) - member(X,Xs) , member(X,Ys).

of nondeterministic programming. In this nondeterministic program the The first member goal in the body of the clause generates members
generator guesses correctly an element in the domain of possible solu- of the first list, which are then tested to see whether they are in the
tions, and the tester simply verifies that the guess of the generator is second list by the second member goal. Thnlung nondeterrninistically, the
correct. first goal guesses an X in Xs, and the second verifies that the guess is a
A good example of a program with multiple solutions and com- member of Ys.
monly used as a generator is Program 3.12 for member. The query mem- Note that when executed as a Prolog program, t h s clause effectively
ber (X , [a,b ,cl ) ? will yleld the solutions X=a, X=b, and X=c successively implements two nested loops. The outer loop iterates over the elements
as required. Thus member can be used to nondeterministically choose the of the first list, and the inner loop checks whether the chosen element is
correct element of a list in a generate-and-test program. a member of the second list. Hence t h s nondeterministic logic program
Program 14.1 is a simple example of generate-and-test using mem- acheves, under the execution model of Prolog, a behavior very similar to
ber as a generator. The program identifies parts of speech of a sen- the standard solution one would compose for this problem in Fortran,
tence. We assume that a sentence is represented as a list of words Pascal, or Lisp.
and that there is a database of facts giving the parts of speech of The definition of member in terms of append,
particular words. Each part of speech is a unary predicate whose
argument is a word, for example, noun(man) indicates that man is a
member (X ,Xs) - append (As, [X I Bs] Xs) .
9

noun. The relation verb(Sentence,Word) is true if Word is a verb in is itself essentially a generate-and-test program. The two stages, how-
sentence Sentence. The analogous meanings are intended for noun/2 ever, are amalgamated b y the use of unification. The append goal gen-
and article/2. The query verb( [a,man,loves,a,woman1 ,V)? finds erates splits of the list, and immediately a test is made whether the first
the verb V=loves in the sentence using generate-and-test. Words element of the second list is X.
in the sentence are generated by member and tested to see if they are Typically, generate-and-test programs are easier to construct than pro-
verbs. grams that compute the solution directly, but they are also less efficient.
Chapter 14 Nondeterministic P r o g r a m m i n g

A standard technique for optimizing generate-and-test programs is to


"push" the tester inside the generator as deeply as possible. Ultimately,
the tester is completely intertwined with the generator, and only correct
solutions are generated.
Let us consider optimizing generate-and-test programs by puslung the
tester into the generator. Program 3.20 for permutation sort is another
example of a generate-and-test program. The top level is as follows: Figure 14.1 A solution to the 4 queens problem
sort (XS ,YS) - permutation (Xs,Ys) , ordered(Ys)
Abstractly, this program guesses nondeterministically the correct permu-
tation via permutation (Xs ,Ys), and ordered checks that the permuta-
queens (N,Queens)
Queens is a placement
- that solves the N queens problem,
tion is actually ordered. represented as a permutation of the list of numbers [l,2,. . . , N].
Operationally, the behavior is as follows. A query involving sort is re- queens(N,Qs) -
range(1 ,N,Ns), ~ermutation(Ns,Qs), saf e(Qs).
duced to a query involving permutation and ordered. A failure-driven
loop ensues. A permutation of the list is generated by permutation and
tested by ordered. If the permuted list is not ordered, the execution
safe(Qs) -
The placement Qs is safe.
backtracks to the permutation goal, which generates another permuta-
tion to be tested. Eventually an ordered permutation is generated and
safe( [Q 1 Qs] )
safe([ 1 ) .
- saf e(Qs), not attack(Q ,Qs).

the computation terminates. attack(X,Xs) - attack(X, 1 ,Xs).


Permutation sort is a highly inefficient sorting algorithm, requiring
time super-exponential in the size of the list to be sorted. Puslung the
attack(X,N, [YIYs])
attack(X,N, [YIYs])
-- X
X is Y+N ; is Y-N.
N1 is N+1, attack(X,NI,Ys)
tester into the generator, however, leads to a reasonable algorithm. The permutation(Xs ,YS) - See Program 3.20.
generator for permutation sort, permutation, selects an arbitrary ele-
ment and recursively permutes the rest of the list. The tester, ordered,
range(M,N,Ns) - See Program 8.12.
verifies that the first two elements of the permutation are in order, then Program 14.2 Naive generate-and-testprogram solving N queens
recursively checks the rest. If we view the combined recursive permuta-
tion and ordered goals as a recursive sorting process, we have the basis
for insertion sort, Program 3.21. To sort a list, sort the tail of the list and The program has been well studied in the recreational mathematics lit-
insert the head of the list into its correct place in the order. The arbitrary erature. There is no solution for N = 2 and N = 3, and a unique solution
selection of an element has been replaced by choosing the first element. up to reflection for N = 4, shown in Figure 14.1. There are 88 solutions
Another example of the advantage of intertwining generating and test- for N = 8, or 92, depending on strictness with symmetries.
ing can be seen with programs solving the N queens problem. Program 14.2 is a simplistic program solving the N queens problem.
The N queens problem requires the placement of N pieces on an N - The relation queen(N, Qs) is true if Qs is a solution to the N queens prob-
by-N rectangular board so that no two pieces are on the same line: hori- lem. Solutions are specified as a permutation of the list of the numbers 1
zontal, vertical, or diagonal. The original formulation called for 8 queens to N. The first element of the list is the row number to place the queen in
to be placed on a chessboard, and the criterion of not being on the same the first column, the second element indicates the row number to place
line corresponds to two queens not attaclung each other under the rules the queen in the second column, etc. Figure 14.1 indicates the solution
of chess. Hence the problem's name. [2,4,1,3] to the 4 queens problem. T h s specification of solutions, and
Chapter 14 Nondeterministic P r o g r a m m i n g

the program generating them, has implicitly incorporated the observa- queens (N,Queens)
Queens is a placement
-
that solves the N queens problem,
tion that any solution to the N queens problem will have a queen on each
row and a queen on each column. represented as a permutation of the list of numbers [I,2 , . . . ,N ] .
The program behaves as follows. The predicate range creates a list queens(N,Qs) range(l,N,Ns), queens(Ns,C 1,Qs).
-
+

Ns of the numbers from 1 to N. Then a generate-and-test cycle begins. queens(UnplacedQs,SafeQs,Qs)


The permutation predicate generates a permutation Qs of Ns, whch is select (Q,UnplacedQs,UnplacedQsl),
not attack(Q,Saf eQs) ,
tested to see whether it is a solution to the problem with the predi-
queens(UnplacedQsl,[QISafeQs1,Qs).
cate saf e(Qs). This predicate is true if Qs is a correct placement of the queens( [ I ,Qs,Qs).
queens. Since two queens are not placed on the same row or column, the
- See Program 3.19.
select ( X , X S , Y S )
predicate need only check whether two queens attack each other along a
diagonal. Safe is defined recursively. A list of queens is safe if the queens attack(X,Xs) - See Program 14.2.
represented by the tail of the list are safe and the queen represented by Program 14.3 Placing one queen at a time
the head of the list does not attack any of the other queens. The def-
inition of attack(Q,Qs) uses a neat encapsulation of the interaction of
diagonals. A queen is on the same diagonal as a second queen N columns
away if the second queen's row number is N units greater than, or N
units less than, the first queen's row number. T h s is expressed by the
first clause of attack/3 in Program 14.2. The meaning of attack(Q, 9s)
is that queen Q attacks some queen in qs. The diagonals are tested itera-
tively until the end of the board is reached.
Program 14.2 cannot recognize when solutions are symmetric. The Figure 14.2 A map requiring four colors
program gives two solutions to the query queens (4,Qs) ?, namely
Qs=[2,4,1,31 andQs=[3,1,4,21.
Although it is a well-written logic program, Program 14.2 behaves inef- tions to the query queens (4,Qs)? are given in the opposite order to the
ficiently. Many permutations are generated that have no chance of being solutions given by Program 14.2.
solutions. As with permutation sort, we improve the program by pushng The next problem is to color a planar map so that no two adjoining re-
the tester, in this case safe,into the generator. gions have the same color. A famous conjecture, an open question for a
Instead of testing the complete permutation, that is, placing all the hundred years, was proved in 1976, showing that four colors are suffi-
queens, each queen can be checked as it is being placed. Program 14.3 cient to color any planar map. Figure 14.2 gives a simple map requiring
computes solutions to the N queens problem by placing the queens one four colors to be colored correctly. Thls can be proved by enumeration of
at a time. It also proceeds by generating and testing, in contrast to inser- the possibilities. Hence four colors are both necessary and sufficient.
tion sort, which became a deterministic algorithm by the transformation. Program 14.4, whlch solves the map-coloring problem, uses the
The generator in the program is select and the tester is attack, or more generate-and-test programming t e c h q u e extensively. The program im-
precisely its negation. plements the following nondeterministic iterative algorithm:
The positions of the previously placed queens are necessary to test
whether a new queen is safe. Therefore the final solution is built upward For each region of the map,
using an accumulator. This is an application of the basic t e c h q u e de- choose a color,
scribed in Section 7.5. A consequence of using an accumulator is that the choose (or verify) colors for the neighboring regions from the
queens are placed on the right-hand edge of the board. The two solu- remaining colors.
Chapter 14 Nondeterministic P r o g r a m m i n g

color-map (Map,Colors) - Test data


Map is colored with CO/O~S so that no two neighbors have the same
color. The map is represented as an adjacency-list of regions
test-color(Name ,Map) -
map(Name,Map),
region(Name,Color,Neighbors), where Name is the name of the
colors (Name,Colors) ,
region, Color is its color, and Neighbors are the colors of its
color-map(Map,Colors).
neighbors.

::z-
map(west~europe,[region(portugal,P,~El), region(spain,E,[F,P]),
UL;:
C, ,-. -!-

-
L
region(f rance ,F,[E,I,S ,B,WG ,L]) , region(belgium,B, [F ,H,L,WG] ) , l: 2 :?
color-region (Region,Colors) J
!;

region(holland,H, [B,WG]) , region(west-germany ,WG, [F,A,S,H,B,L]) , 4 Q 3 !;:


Region and its neighbors are colored using Colors so that the
region(luxembourg,L,[F,B,WG]), region(italy,I,[F,A,S]),
5 r- 7 IS
w .-.!--
region's color is different from the color of any of its neighbors.
region(switzerland,S, [F,I,A,WGI ) , region(austria,A, [I,S,WG])I). 2 6 5 :..'
12

5 < '.
5 -.
A.

Program 14.5 Test data for map coloring


select (X,Xs ,Ys) - See Program 3.19.
members(Xs ,YS) - See Program 7.6.
Program 14.4 Map coloring Both the select and members goals can act as generators or testers,
depending on whether their arguments are instantiated.
Overall, the effect of the program is to instantiate a data structure, the
A data structure is needed to support the algorithm. The map is repre- map. The calls to select and members can be viewed as specifying local
sented as a list of regions. Each region has a name, a color, and a list of constraints. The predicates either generate by instantiating arguments in
colors of the adjoining regions. The map in Figure 14.2, for example, is the structure or test whether instantiated values satisfy local constraints.
represented as Program 14.5 tests the map coloring solution.
Instantiating a data structure designed especially for a problem is a
particularly effective means of implementing generate-and-test solutions.
Unification and failure to unify control the building of the final solution
structure, avoiding creation of unnecessary intermediate data structures.
Since unification is supported well by Prolog implementations, solutions
The sharing of variables is used to ensure that the same region is not are found quickly. Exercise 14.l(iv)assigns the task of designing a data
colored with two different colors by different iterations of the algorithm. structure that can be instantiated to solve the N queens problem. The
The top-level relation is color-map(Map, Colors), where Map is repre- resulting program solves the N queens problem much more quickly than
sented as before, and Colors is a list of colors used to color the map. Program 14.3.
Our colors are red, yellow, blue, and whlte. The heart of the algorithm is Our final example is solving a logic puzzle. The behavior of the pro-
the definition of color-region(Region, Colors) : gram is similar to the map-coloring program. The logic puzzle consists

color-region(region(Name ,color,Neighbors) ,Colors) - of some facts about some small number of objects that have various at-
tributes. The minimum number of facts is given about the objects and
select (Color,Colors, Colorsl) , members ( ~ e i ~ h b o ,Colorsl)
rs . attributes, to yleld a unique way of assigning attributes to objects.
C h a p t e r 14 Nondeterministic P r o g r a m m i n g

Here is an example that we use to describe the technique of solving solve-puzzle (Puzzle,Solution) -
logic puzzles. Solution is a solution of Puzzle,
where Puzzle is puzzle( Clues,Queries,Solution) .
Three friends came first, second, and thlrd in a programming competi-
tion. Each of the three has a different first name, likes a different sport, solve~puzzle(puzzle(Clues,~ueries,~olution~,Solution~
solve (Clues) ,
-
and has a different nationality. solve(Queries).
Michael likes basketball and did better than the American. Simon, the
Israeli, did better than the tennis player. The cricket player came first.
solve ( [Clue l Clues] ) -
C l u e , solve(C1ues).
Who is the Australian? What sport does Richard play? solve([ 1 ) .
Logic puzzles such as t h s one are elegantly solved by instantiating
the values of a suitable data structure and extracting the solution val- Program 14.6 A p u z ~ l esolver
ues. Each clue is translated into a fact about the data structure. This can
be done before the exact form of the data structure is determined using
Each person has three attributes and can be represented by the structure
data abstraction. Let us analyze the first clue: "Michael likes basketball
friend (Name,Country, Sport). There are three friends whose order in
and did better than the American." Two distinct people are referred to.
the programming competition is significant. This suggests an ordered
One is named Michael, whose sport is basketball, and the other is Amer-
sequence of three elements as the structure for the problem, i.e., the list
ican. Further, Michael did better than the American. If we assume the
structure to be instantiated is Friends,then the clue is expressed as the
conjunction of goals
The programs defining the conditions did-better, f irst-name, na-
did-better (Manl ,Man2,Friends) , f irst-name(~an1,michael) ,
tionality, sport, and first are straightforward, and are given in
sport (Manl,basketball) , nationality(Man2, american) , Program 14.7.
Similarly, the second clue can be translated to the conditions The combination of Programs 14.6 and 14.7 works as a giant generate-
and-test. Each of the did-better and member goals access people, and
did-better (Manl ,Man2, Friends) , f irst-name (Man1 ,Simon) ,
the remaining goals access attributes of the people. Whether they are
nationality(Man1, israeli) , sport (~an2,tennis),
generators or testers depends on whether the arguments are instanti-
and the third clue to the conditions ated or not. The answer to the complete puzzle, for the curious, is that
Michael is the Australian, and Richard plays tennis.
The puzzle given in Program 14.7 is simple. An interesting question is
A framework for solving puzzles is given as Program 14.6. The rela- how well does the framework of Program 14.6 scale. A good example of a
tion computed is solve-puzzle (Puzzle,Solution), where Solution is larger puzzle is given in Exercise 14.l(vi).Is the framework adequate for
the solution to Puzzle. The puzzle is represented by the structure puz- such a puzzle?
zle (Clues,Queries,Solution), where the data structure being instan- The short answer is yes. Prolog is an excellent language for solving
tiated is incorporated into the clues and queries, and the values to be logic puzzles. However, care must be taken when formulating the clues
extracted are given by Solution. and queries. For example, the predicate member is often essential to spec-
The code for solve-puzzle is trivial. All it does is successively solve ify individuals, as is done to formulate the query in Program 14.7. It may
each clue and query, whch are expressed as Prolog goals and are exe- be tempting to become systematic and begin the puzzle solution by spec-
cuted with the meta-variable facility. ifying all individuals by member goals. This can lead to very inefficient
The clues and queries for our example puzzle are given in Program programs because too many choice-points are set up. In general, implicit
14.7. We describe the structure assumed by the clues to solve the puzzle. checking of a condition is usually more efficient. Another observation is
Chapter 14 Nondeterministic Programming

Test data that the order of the goals in the queries can significantly affect run-
test-puzzle(Name,Solution) -
structure(Name,Structure),
ning time. It is best to worry about this once the problem formulation
is correct. Determining appropriate goal order is a skill easily learned by
clues(Name,Structure,Clues), experience.
queries(Name,Structure,Queries,Solution), Another tip concerns negative clues, such as "John is not the tailor."
solve-puzzle (puzzle (Clues,Queries, Solution) ,solution)
These clues are best regarded as specifying two separate individuals,
John and the tailor, rather than as setting up a negative condition about
clues(test,Friends, one individual. The predicate select can be used instead of member to
[(did-better(ManlCluel,Man2Cluel,Friends), % Clue 1 guarantee that individuals are different.
f irst-name(ManlClue1 ,michael) , sport (ManlCluel ,basketball),
nationality(Man2Cluel,american)),
(did-better(ManlClue2,Man2Clue2,Friends), % Clue 2 Exercises for Section 14.1
first-name(ManlClue2,simon), nationality(~anl~lue2,israeli),
sport(Man2Clue2,tennis)), (i) Write a program to compute the integer square root of a natu-
(first (Friends ,Manclue31 , sport (ManClue3,cricket) ) % Clue 3 ral number N defined to be the number I such that 12I N , but
1). (I + 1 ) 2> N. Use the predicate between/3, Program 8.5, to generate
queries(test, Friends, successive natural numbers on backtraclung.
[ member(Ql,Friends),
f irst-name (Ql,Name), (ii) Write a program to solve the stable marriage problem (Sedgewick,
nationality(Q1 ,australian), % Query 1 1983), stated as follows:
member (Q2,Friends) ,
f irst-name(Q2,richard) , Suppose there are N men and N women who want to get married. Each
sport (Q2,Sport) % Query 2 man has a list of all the women in his preferred order, and each woman
I, has a list of all the men in her preferred order. The problem is to find a
[['The Australian is ' , Name], ['Richard plays ' , Sport]] set of marriages that is stable.
). A pair o f marriages is unstable if there are a man and woman who
did-better(A,B, [A,B,C]). prefer each other to their spouses. For example, consider the pair of
did-better(A,C, [A,B,CI). marriages where David is married to Paula, and Jeremy is married to
did-better(B,C, [A,B,CI). Judy. If David prefers Judy to Paula, and Judy prefers David to Jeremy,
the pair of marriages is unstable. This pair would also be unstable if
first-name(friend(~,~,C),A). Jeremy preferred Paula to Judy, and Paula preferred Jeremy to David.
nationality(friend(~,~,~),B). A set of marriages is stable if there is no pair of unstable marriages.
sport(friend(~,B,C),C).
Your program should have as input lists of preferences, and pro-
duce as output a stable set of marriages. It is a theorem from graph
Program 14.7 A description of a puzzle theory that thls is always possible. Test the program on the follow-
ing five men and five women with their associated preferences:
avraham: chana tamar zvia ruth sarah
binyamin: zvia chana ruth sarah tamar
chaim: chana ruth tamar sarah zvia
david: zvia ruth chana sarah tamar
elazar: tamar ruth chana zvia sarah
Chapter 14 Nondeterministic Programming

zvia: elazar avraham david binyamin chaim (e) The green house is immediately to the right (your right) of the
chana: david elazar binyamin avraham chaim ivory house.
ruth: avraham david binyamin chaim elazar (f) The Winston smoker owns snails.
sarah: chaim binyamin david avraham elazar
tamar: david binyamin chaim elazar avraham (g) Kools are smoked in the yellow house.

(iii) Use Program 14.4 to color the map of Western Europe. The coun- (h) Milk is drunk in the middle house.
tries are given in Program 14.5.
(i) The Norwegian lives in the first house on the left.
(iv) Design a data structure for solving the N queens problem by instan- 0) The man who smokes Chesterfields lives in the house next to
tiation. Write a program that solves the problem by instantiating the man with the fox.
the structure.
(k) Kools are smoked in the house next to the house where the
(v) Explain why the following program solves the N queens problem: horse is kept.

queens(N,Qs) - (1) The Lucky Strike smoker drinks orange juice.


(m)The Japanese smokes Parliaments.
gen-list (N,Qs) , place-queens (N,Qs,Ups,Downs).
gen-list(0, [ I ) . (n) The Norwegian lives next to the blue house.
gen-list(N, [QlL]) - N > 0, N1 is N-I, gen-list(N1,L)
place-queens (0,Qs ,Ups,Downs) . Who owns the Zebra? Who drinks water?
place-queens (I,Qs ,Ups,[D I Downs1 ) - (vii) Write a program to test whether a graph is planar using the algo-
I > 0, 11 is 1-1, rithm of Hopcroft and Tarjan (Deo, 1974; Even, 1979).
place-queens (11,Qs, [U I Upsl ,Downs) ,
place-queen(I,Qs,Ups,Downs).
place-queen(Q, [Q1 Qsl , [QI Upsl , [Q1 Downs1 ) .
place-queen (4, [QI 1 Qs1 , [U I Upsl , [D I Downs] - 14.2 Don't-Care and Don't-Know Nondeterminism
place-queen (Q,Qs ,Ups,Downs) .
Two forms of nondeterminism are distinguished in the logic program-
(vi) Write a program to solve the following logic puzzle. There are five ming literature. They differ in the nature of the choice that must be made
houses, each of a different color and inhabited by a man of a differ- among alternatives. For don't-care nondeterminism, the choice can be
ent nationality, with a different pet, drink, and brand of cigarettes. made arbitrarily. In terms of the logic programming computation model,
any goal reduction will lead to a solution, and it does not matter whch
particular solution is found. For don't-know nondeterminism, the choice
(a) The Englishman lives in the red house.
matters but the correct one is not known at the time the choice is made.
(b) The Spaniard owns the dog. Most examples of don't-care nondeterminism are not relevant for the
Prolog programmer. A prototypical example is the code for minimum.
( c ) Coffee is drunk in the green house. Program 3.7 is the standard, incorporating a limited amount of don't-care
(d) The Ukrainian drinks tea. nondeterrninism, namely, when X and Y are the same:
Chapter 14 Nondeterministic Programming

In Section 7.4, we termed this redundancy and advised against its use.
On the other hand, programs e h b i t i n g don't-know nondeterrninism
are common. Consider the program for testing whether two binary trees
are isomorphc (Program 3 . 2 5 , reproduced here). Each clause is indepen-
dently correct, but given two isomorphic binary trees, we don't know
which of the two recursive clauses should be used to prove the isomor-
phism. Operationally, only when the computation terminates success-
fully do we know the correct choice:
Figure 14.3 Directed graphs
isotree (void,void) .
isotree(tree(X,Ll ,R1) ,tree(~,~2,R2)) -
isotree(L1 ,L2) , isotree (R1 ,R2) .
isotree(tree(X,Ll,Rl), tree(X,~2,~2)) - connected ( X ,Y )-
Node X is connected to node Y,
isotree(L1 ,R2), isotree (L2 ,R1). given an edge/2 relation describing a DAG.
connected(X,X).
Composing Prolog programs exhibiting either form of nondeterrninism
can be indistinguishable from composing deterministic programs. Each
connected(X,Y) - edge(X,N), connected(N,Y) .
clause is written independently. Whether inputs match only one clause Data
or several is irrelevant to the programmer. Indeed t h s is seen from the edge(a,b). edge(a,c). edge(a,d). edge(a,e). edge(d,j).
edge(c,f). edge(c,g). edge(f,h). edge(e,k). edge(f,i).
multiple uses that can be made of Prolog programs. With arguments in-
edge(x,y). edge(y,z). edge(z,x). edge(y,u). edge(z,v).
stantiated in one way, the program is deterministic; with another pattern
of instantiation, the program is nondeterministic. For example, append/3 Program 14.8 Connectivity in a finite DAG
is deterministic if called with its first two arguments instantiated, whle
it is generally nondeterministic if called with the third argument instan-
tiated and the first two arguments uninstantiated. Our first program is a small modification of a logic program of Section
The behavior of Prolog programs seemingly having don't-know nonde- 2 . 3 . Program 14.8 defines the relation connected(X,Y), whch is true if
terminism such as isotree is known. A given logic program and a query two nodes in a graph, X and Y, are connected. Edges are directed; the fact
determine a search tree, as discussed in Chapter 5 , whch is searched edge(X,Y) states that a directed edge exists from X to Y. Declaratively
depth-first by Prolog. Writing a program possessing don't-know nonde- the program is a concise, recursive specification of what it means for
terminism is really specifying a depth-first search algorithm for solving nodes in a graph to be connected. Interpreted operationally as a Prolog
the problem. program, it is the implementation of an algorithm to find whether two
We consider this viewpoint in a little more detail with a particular nodes are connected using depth-first search.
example: finding whether two nodes in a graph are connected. Figure The solutions to the query connected(a,X)? using the data from the
14.3 contains two graphs that will be used to test our ideas. The left- left-hand graph in Figure 14.3 gives as values for X, a, b, c, f, h, i, g, d, j,
hand one is a tree, while the right-hand one is not, containing a cycle. e, k. Their order constitutes a depth-first traversal of the tree.
Trees, or more generally, directed acyclic graphs (DAGs),behave better Program 14.9 is an extension of t h s simple program that finds a path
than graphs with cycles, as we will see in our example programs. between two nodes. The predicate path(X,Y ,Path) is true if Path is
Chapter 14 Nondeterministic P r o g r a m m i n g

path(X,Y,Path)
Path is a path
- between two nodes X and Y
in the DAG defined by the relation edge/2.
path(X,X, [XI).
path(X,Y, [XIPI) - edge(X,N), path(N,Y,P).

Program 14.9 Finding a path by depth-first search Figure 14.4 Initial and final states of a blocks world problem

connected (X,Y) -
Node X is connected to node Y in the graph defined by edge/2. The program is not guaranteed to reach every node of an infinite graph.
connected(X,Y) - connectedo(,Y,[X]).
To do so, breadth-first search is necessary. T h s is discussed further in
Section 16.2.
connected(X,X,Visited).
connected(X,Y,Visited) -
edge(X,N), not member(N,Visited), connected(N,Y,[NIVisitedl).
T h s section is completed with a program for building simple plans
in the blocks world. The program is written nondeterministically, essen-
tially performing a depth-first search. It combines the two extensions
Program 14.10 Connectivityinagraph mentioned before - keeping an accumulator of what has been traversed,
and computing a path.
The problem is to form a plan in the blocks world, that is, to specify
a path from the node X to the node Y in a graph. Both endpoints are a sequence of actions for restacking blocks to achieve a particular con-
included in the path. The path is built downward, which fits well with the figuration. Figure 14.4 gives the initial state and the desired final state of
recursive specification of the connected relation. The ease of computing a blocks world problem. There are three blocks, a, b, and c, and three
the path is a direct consequence of the depth-first traversal. Extending places, p, q, and r. The actions allowed are moving a block from the top
a breadth-first traversal to find the path is much more difficult. Sections of a block to a place and moving a block from one block to another. For
16.2 and 20.1 show how it can be done. an action to succeed, the top of the moved block must be clear, and also
Depth-first search, dfs, correctly traverses any finite tree or DAG (di- the place or block to whch it is being moved must be clear.
rected acyclic graph). There is a problem, however, with traversing a The top-level procedure of Program 14.11 solving the problem is
graph with cycles. The computation can become lost in an infinite loop transf orm(State1, State2,Plan). A plan of actions, Plan, is produced
around one of the cycles. For example, the query connected(x ,Node)?, that transforms State1 into State2 when executed.
referring to the right-hand graph of Figure 14.3 gives solutions Node=y, States are represented by a list of relations of the form on(X,Y),
Node=z, and Node=x repeatedly without reaching u or v. where X is a block and Y is a block or place. They represent the
The problem is overcome by modifying connected. An extra argument facts that are true in the state. For example, the initial and final
is added that accumulates the nodes visited so far. A test is made to states in Figure 14.4 are, respectively, [on(a, b) ,on(b,p) ,on(c ,r)l and
avoid visiting the same node twice. This is shown in Program 14.10. [on(a,b) ,on(b,c) ,on(c ,r)l. The state descriptions are ordered in the
Program 14.10 successfully traverses a finite directed graph depth- sense that the on relation for a precedes that of b, whch precedes the
first. The pure Prolog program needed for searching finite DAGs must be on relation for c. The state descriptions allow easy testing of whether
extended by negation in order to work correctly. Adding an accumulator a block or place X is clear in a given state by checlung that there is no
of paths visited to avoid entering loops effectively breaks the cycles in relation of the form on (A, X). The predicates clear/2 and on/3 in Pro-
the graph by preventing traversal of an edge that would complete a cycle. gram 14.11 take advantage of this representation.
Chapter 14 Nondeterministic Programming

transform(Statel,State2,Plan) -
Plan is a plan of actions to transform State1 into State2.
[to-place(a,b,q) ,to-block(a,q,c) ,to-place(b,p,q) ,to-place(a,c,p),
to-block(a,p,b) ,to-place(c ,r,p) ,to-place(a,b,r), to-block(a,r, c) ,
transform(Statel,State2,Plan) -
transform(Statel.State2, [Statel] ,Plan).
to-place(b,q,r) ,to-place(a, c ,q) ,to-block(a,q,b) ,to-place(~,p,q),
to-place(a,b,p) ,to-block(a,p,c) ,to-place(b,r,p) ,to-place(a, c,r),
transform(State,State,Visited, C I ) . to-block(b,p, a) ,to-place (c,q,p) ,to-block(b,a, c) ,to-place(a,r,q),
transform(Statel,State2,Visited,[~ctionIActions]) - to-block(b, c, a), to-place(c ,p,r) ,to-block(b,a, c) ,to-place (a,q,p),
legal-action(Action,Statel), to-block(a,p,b)l .
update(Action,Statel,State),
not member (State ,Visited) , Block a is first moved to q, then to c. After that, block b is moved to q,
transform(State ,State2, [State IVisitedl ,Actions). block a is moved to p and b, and after 20 more random moves, the final
legal~action(to~place(Block,Y,Place).State +-

configuration is reached.
on(Block,Y,State), clear(Block,State), It is easy to incorporate a little more intelligence by first trying to
~lace(Place), clear(Place,State).
legal~action(to~block(Blockl,Y,Block2),State -
on(Block1 ,Y ,State), clear(Block1 ,State), block(~lock2),
acheve one of the goal states. The predicate legal-action can be re-
placed by a predicate choose~action(Action,Statel, State2). A sim-
Block1 # Block2, clear(Block2 ,State). ple definition suffices to produce intelligent behavior in our example
clear (X,State) - not member(on(A,X) ,State). problem:
on(X,Y,State) -member(on(X,Y) ,State). choose~action(Action,Statel, State2) -
update(to-block(X,Y ,Z) ,State,Statel) - suggest(Action,State2), legal-action(Action,Statel).
substitute(on(X,Y),on(X,Z),State,Statel).
update(to-place(X,Y,Z),State,Statel) - choose~action(Action,Statel,State2) -
legal-action(Action, Statel) .
substitute(on(X,Y) ,on(X,Z) ,State,Statel).
substitute(X,Y ,Xs ,Ys) - See Exercise 3.3(1). suggest ( t ~ - ~ l a c(x,Y,
e Z) ,State) -
member (on(X, Z) ,State), place(Z) .
Program 14.11 A depth-first planner suggest (to-block(X,Y,Z) ,State) -
member (on(X, Z) ,State) , block(Z) .

The nondeterministic algorithm used by the planner is given by the The first plan now produced is [to-place (a,b, q) ,to-block(b ,p,c) ,
recursive clause of transf orm/4 in the program: to-block(a, q,b)l .

Whle the desired state is not reached,


find a legal action, Testing and data
update the current state,
check that it has not been visited before.
initial-state(test , [on(a,b) ,on(b,p) .on(c,r)l)
There are two possible actions, moving to a block and moving to a place. final-state(test, [on(a,b) ,on(b,c) ,on(c.r)l).
For each, the conditions for whch it is legal must be specified, and a block(a) . block(b). block(c) .
method given for updating the state as a result of performing the action. place (p) . place (q) . place (r) .
Program 14.11 successfully solves the simple problem given as Pro-
gram 14.12. The first plan it produces is horrendous, however: Program 14.12 Testing the depth-first planner
Chapter 14 Nondeterministic Programming

Exercises for Section 14.2

(i) Apply Program 14.11 to solve another simple blocks world prob-
lem.

(ii) Modify Program 14.11 to solve the following planning problem.


Consider a simplified computer consisting of a single accumula-
tor and a large number of general purpose registers. There are four
instructions: load, store, add and subtract. From the initial state
where the accumulator is empty, register1 contains the value c l ,
register2 contains c2, register3 contains c3 and register4 contains
c4, achieve a final state where the accumulator contains
Figure 14.5 A geometric analogy problem
(a) (cl - c2) + (c3 - c4)
(b) (cl - c2) + (cl - c2)
cal problem. Diagrams A, B, and C are singled out from a list of possible
(c) c1, and register1 contains cl + (c2 - c3), and register2 contains answers and the following question is posed: "A is to B as C is to which
c2 - c3. one of the 'answer' diagrams?" Figure 14.5 gives a simple problem of t h s
type.
-- Here is an intuitive algorithm for solving the problem, where terms
14.3 Artificial Intelligence Classics: ANALOGY, ELIZA, and McSAM such as find, apply, and operation are left unspecified:

Find an operation that relates A to B.


"The best way to learn a subject is to teach it" is a cliche commonly
Apply the operation to C to give a diagram X.
repeated to new teachers. An appropriate analogue for new programmers
Find X, or its nearest equivalent, among the answers.
is that the best way to understand a program is to rewrite or extend it.
In t h s spirit, we present logical reconstructions of three A1 programs. In the problem in Figure 14.5, the positions of the square and triangle
Each is clear, understandable, and easily extended. The exercises at the are swapped (with appropriate scaling) between diagrams A and B. The
end of the section encourage the reader to add new facts and rules to the "obvious" answer is to swap the square and the circle in diagram C. The
programs. resultant diagram appears as no. 2 in the possible answers.
The three programs chosen are the ANALOGY program of Evans for Program 14.13 is a simple program for solving analogy problems. The
solving geometric analogy questions from intelligence tests; the ELIZA basic relation is analogy (Pair1 ,Pair2,Answers), where each Pair is of
program of Weizenbaum, whch simulates or rather parodies conversa- the form X is-to Y. To parse the program, is-to must be declared as
tion; and McSAM, a rnicroversion of SAM, a program for "understanding" an infm operator. The two elements in Pair1 bear the same relation as
stories from the Yale language group. Each logical reconstruction is ex- the two elements in Pair2, and the second element in Pair2 appears in
pressed very simply. The nondeterminism of Prolog allows the program- Answers. The definition of analogy implements the intuitive algorithm:
mer to ignore the issues of search.
Consider the task of solving the geometric analogy problems typically analogy(A is-to B,C is-to X,Answers) -- match(A,B,Operation),
used in intelligence tests. Several diagrams are presented in a prototypi- match (C,X, Operation) , member (X,Answers) .
Chapter 14 Nondeterministic Programming

analogy (Pairl,PairZ,Answers) - Testing and data


An analogy holds between the pairs of figures Pair1 and Pair2.
The second element of Pair2 is one of the possible Answers.
test-analogy(Name,X) -
f igures(Name ,A,B,C),
analogy(A is-to B,C is-to X,Answers) - answers (Name ,Answers),
analogy(A is-to B,C is-to X,Answers).
match(A,B, Operation) ,
match(C,X,Operation), figures (testl,inside (square,triangle), inside(triangle, square) ,
member(X,Answers). inside(circle,square)).
match(inside(Figure1 ,Figure2), inside(Figure2 , ~ i ~ u r e ,linvert).
) answers (test 1, [inside (circle,triangle), inside (square,circle),
match (above (Figure1 ,Figure2) ,above(Figure2 ,~igurel),invert) . inside(triangle,square)]).

Program 14.13 A program solving geometric analogies Program 14.14 Testing ANALOGY

A fundamental decision is how the figures in the problem should be


represented. In Program 14.13, figures are represented as Prolog terms. I am unhappy.
>> How long have you been unhappy?
Diagram A in Figure 14.5 is a square inside a triangle and is represented
Six months. Can you help me.
as the term inside (square,triangle). >> \+hat makes you think I help you'>
The relation between two diagrams is found by the predicate match You remind me of my father and brother.
(A,B ,Operat ion). This relation is true if Operat ion matches A and B. 2> Please tell me more about father.
The operation invert is used to solve our example problem. The behav- I like teasing father.
ior of invert is to swap its arguments. >> Does anyone else in your family llke teasing father?
The predicate match is used in two distinct ways in thls program. The No, only me.
first time it produces an operation matchlng two given diagrams. The >> Please go on.
second time it produces a second diagram given an operation and a di-
Figure 14.6 Sample conversation with ELIZA
agram. These details are, however, irrelevant from the nondeterministic
viewpoint. Finding the answer is implemented simply, checlung whether
a given diagram appears in the list of answers. Note that the second
match goal and the member goal can be swapped. The behavior then be- The next A1 classic considered is a simplified version of ELIZA. The pro-
comes guessing an answer using member as a generator, and verifying gram aims to simulate a conversation. A user types in a sentence, and
that the guess has the same relation to diagram C as A does to B. Pro- ELIZA responds with an appropriate question or comment. ELIZA does
gram 14.14 tests the analogy program. not understand in any real sense, responding to its input by recognizing
Exercise 14.3(i) poses three additional problems to be solved by anal- word patterns and replying by using a corresponding response pattern.
ogy, which can be expressed w i t h the framework of Program 14.13. The To make the patterns of responses more credible, a psychiatrist setting
representation of pictures by terms, and the description of operations is adopted. A sample interaction with ELIZA is given in Figure 14.6. Com-
becomes increasingly ad hoc. Indeed, much of the "intelligence" of the puter responses are preceded by > >.
program is embedded in the representation. The original ANALOGY pro- The heart of ELIZA is a procedure for matchlng the input sentence
gram did not assume as much knowledge. It took a line drawing and tried against a pattern. The resulting match is applied to another pattern to de-
to recognize the objects. Triangles, squares, and so on, were not assumed termine the program reply. The pair of patterns can be considered a stim-
to be the primitives. ulus/response pair, where the input is matched against the stimulus and
Chapter 14 Nondeterministic P r o g r a m m i n g

the output generated from the response. A typical stimulus/response eliza -


Simulates a conversation via side effects.
pair is
eliza - read-word-list(1nput) , eliza(Input), !.
I am (statement) How long have you been (statement)? eliza( [bye] ) -
reply(['Goodbye. I hope I have helped you'])
Using t h s pair, the response of the program to the input statement "I am
unhappy" will be the question "How long have you been unhappy?" The
eliza(1nput) -
pattern(Stimu1us ,Response),
(statement) can be viewed as a slot to be filled. match(Stimulus,Dictionary,Input),
Program 14.15 is a simple version of ELIZA. It implements the follow- match(Response,Dictionary,0utput),
reply(Output),
ing algorithm:
read-word-list (Inputl) ,
! , eliza(Input1).
Read the input.
Whle the input is not bye, match(Pattern,Dictionary,Words) -
Pattern matches the list of words Words, and matchmgs are
choose a stimulus/response pair,
recorded in the Dictionary.
match the input to the stimulus,
generate the reply from the response and the above match,
match( [N I Pattern] ,Dictionary ,Target) -
integer(N), lookup(N,Dictionary,LeftTarget),
output the response, append(LeftTarget,RightTarget,Target),
read the next input.

The stimulus/response pairs are represented as facts of the form pat-


match(Pattern,Dictionary,RightTarget).
match( [Word l Pattern] ,Dictionary, [Word 1 Target] )
atom(Word), match(Pattern,Dictionary,Target).
-
tern (Stimulus ,Response), where both Stimulus and Response are lists match( [ 1 ,Dictionary,[ 1 ) .
of words and slots. Slots in the patterns are represented by integers. The lookup(Key ,Dictionary, Value) - See Program 15.8.
predicate match(Pattern,Table ,Words) is used for both the second and
third steps of the algorithm. It expresses a relation between a pattern
pattern (Stimulus,Response) -
Response is an applicable response pattern to the pattern Stimulus.
Pattern,a list of words Words, and a table Table,where the table records pattern(~i,am,l],~'How',long,have,you,been,l,?]).
how the slots in the pattern are filled. A central part of the match proce- pattern( [l,you,2,mel,['What' ,makes,you,think,'IJ YOU,?]).
dure is played by a nondeterministic use of append to break up a list pattern( [i,like, 11, ['Does' ,anyone,else,in,your,family,like,1 , ? I ) .
of words. The table is represented by an incomplete data structure, dis-
cussed in more detail in Chapter 15. The missing procedure lookup/3 is
pattern([i,feel,ll, ['DoJ ,you,often,feel,that,way,?]).
-
pattern( [I , ~ , 2 ,1['please', tell ,me,more ,about ,X, .I)
important (X) .
given in Section 15.3. The reply is generated by reply (Words). whch is a pattern(C11, ['Please',go,on, . I ) .
modified version of Program 12.1 for writeln that leaves spaces between
important (father) . important (mother) . important (son) .
words. important(sister). important(br0ther). important(daughter).
The final program presented in t h s section is Micro SAM or McSAM. It
is a simplified version of the SAM (Script Applier Mechanism) program -
reply([HeadlTail])
reply([ 1 ) nl.
- write(Head), write(' '1, reply(Tai1).

developed in the natural language group at Yale University. The aim of


McSAM is to "understand" stories. Given a story, it finds a relevant script
read-word-list (Xs) - See Program 12.2.
and matches the individual events of the story against the patterns in the Program 14.15 ELIZA
script. In the process, events in the script not explicitly mentioned in the
story are filled in.
Chapter 14 Nondeterministic Programming

Input: John went to Leones, ate a hamburger, and left. mcsam (Story,Script)
Script describes Story.
-
Output: John went to Leones. He was shown from the door to a seat.
A waiter brought John a hamburger, which John ate by mouth.
The waiter brought John a check, and John left Leones for
another place.
Figure 14.7 A story filled in by McSAM

Both the story and the script are represented in terms of Schank's
theory of conceptual dependency. For example, consider the input story
in Figure 14.7, whlch is used as an example in our version of McSAM. The
match (Script,Story) -
Story is a subsequence of Script.
English version match(Script , [ 1 ) .
match( [Line I S c r i p t ] , [Line I Story] ) match(Script ,Story) .
"John went to Leones, ate a hamburger, and left" rnatch([LineIScriptl,Story) - +-

match(Script,Story).
is represented in the program as a list of lists: filler (Slot,Story) -
Slot is a word in Story.
[ [ p t r a n s , j o h n , j o h n , XI, l e o n e s ] ,
[ i n g e s t , X2, hamburger, X3] ,
filler(~lot,Story) -
member ( [Action1 Argsl ,Story) ,
[ p t r a n s , A c t o r , A c t o r , X4, X51 1 member ( S l o t ,Args) ,
nonvar(S1ot).
The first element in each list, p t r a n s and i n g e s t , for example, is a term
from conceptual dependency theory. The representation of the story as a
name-defaults (Defaults)
Unifies default pairs in Defaults.
-
list of lists is chosen as a tribute to the original Lisp version. name-defaults([:I ) .
Programming McSAM in Prolog is a triviality, as demonstrated by name-def a u l t s ( [ EN, N ] I L 1 ) a u l t s (L) .
Program 14.16. The top-level relation is mcsam ( S t o r y , S c r i p t ) , which
+

name-def a u l t s ( [ [ N l ,N21 I L 1 ) -name-def


N1 f N2, name-def a u l t s ( L ) .
expands a S t o r y into its "understood" equivalent according to a rele-
Program 14.16 McSAM
vant S c r i p t . The script is found by the predicate f i n d ( S t o r y , S c r i p t ,
D e f a u l t s ) . The story is searched for a nonvariable argument that trig-
gers the name of a script. In our example of John visiting Leones, the
atom l e o n e s triggers the r e s t a u r a n t script, indicated by the fact t r i g -
[ i n g e s t , j ohn, hamburger, [mouth, j ohn] 1
g e r ( l e o n e s , r e s t a u r a n t ) in Program 14.17.
[ a t r a n s , john, check, john, waiter]
The matching of the story to the script is done by m a t c h ( S c r i p t ,
[ p t r a n s , j o h n , j ohn, l e o n e s , p l a c e 2 1 .
S t o r y ) , which associates lines in the story with lines in the script. Re-
maining slots in the script are filled in by name-defaults(Defau1ts).
The "output" is Its translation to English is given in Figure 14.7.
The work done on the original McSAM was all in the searching and
[ p t r a n s , j o h n , j o h n , p l a c e l ,l e o n e s 1 pattern matching. This is accomplished in Prolog by nondeterministic
[ p t r a n s , john, john, door, s e a t ] programming and unification.
[mtrans,john,waiter,hamburger]
Chapter 14 Nondeterministic Programming

Testing and data


test-mcsam(Name,UnderstoodStory) - (i) Given: 0
s t o r y (Name, S t o r y ) , m c s a m ( ~ t o r y, ~ n d e r s t o o d S t o r y ).
s t o r y ( t e s t , [ [ p t r a n s , j o h n , john, XI, l e o n e s ] ,
[ i n g e s t , X2, hamburger, X3] ,
[ p t r a n s , A c t o r , A c t o r , X4, X51 I ) .
script (restaurant,
[ [ptrans, Actor, Actor, EarlierPlace, Restaurant] ,
[ p t r a n s , A c t o r , A c t o r , Door, S e a t ] ,
[mtrans, A c t o r , W a i t e r , Food],
[ i n g e s t , A c t o r , Food, [mouth, Actor] 1 ,
[ a t r a n s , A c t o r , Money, A c t o r , w a i t e r ] ,
(ii) Given: A // @
[ p t r a n s , A c t o r , A c t o r , R e s t a u r a n t , Gone] 1 ,
[ [Actor, customer] , [ E a r l i e r P l a c e , place11 ,
[ R e s t a u r a n t , r e s t a u r a n t ] , [Door, door] ,
[ S e a t , s e a t ] , [Food, meal] , [Waiter, w a i t e r ] ,
[Money, check] , [Gone, place21 1 1.

Program 14.17 Testing McSAM

Exercises for Section 14.3

(i) Extend ANALOGY, Program 14.13, to solve the three problems in


Figure 14.8.
(ii) Extend ELIZA, Program 14.15, by adding new stimulus/response
patterns. Figure 14.8 Three analogy problems
(iii) If the seventh statement in Figure 14.6 is changed to be "I like
teasing my father," ELIZA responds with "Does any one else in your
family like teasing my father." Modify Program 14.15 to "W t h s
behavior, changing references such as I, my, to you, your, etc.
(iv) Rewrite McSAM to use structures.
(v) Reconstruct another A1 classic. A good candidate is the general
problem solver GPS.
280 Chapter 14 Nondeterministic Programming

ANALOGY constituted the Ph.D. thesis of Thomas Evans at MIT in the


14.4 Background mid-1960s. A good description of the program appears in Semantic Infor-
mation Processing (Minsky, 1968). Evans's program tackled many aspects
Applylng Prolog to generate-and-test problems has been very common. of the problem that are made trivial by our choice of representation, for
Many researchers have discussed the behavior of Prolog in solving the example, identifying that there are triangles, squares, and circles in the
N queens problem and map coloring. A good discussion of how Prolog diagrams. Our version, Program 14.13, emerged from a discussion group
handles the N queens problem can be found in Elcock (1983). The N of Leon Sterling with a group of episternics students at the University of
queens program given in Exercise 14.l(v), the fastest of whch we are Edinburgh.
aware, is due to Thomas Fruewirth. A classification of generate-and-test ELIZA was originally presented in Weizenbaum (1966). Its performance
programs in Prolog is given in Bansal and Sterling (1989). led people to believe that a limited form of the Turing test had been
Several researchers have used Prolog's behavior on generate-and- passed. Weizenbaum, its author, was horrified by people's reactions to
test problems as a reason to investigate alternative control of logic the program and to A1 more generally, and he wrote an impassioned plea
programs. Suggestions for improvement include co-routining incorpo- against talung the program too seriously (Weizenbaum, 1976). Our ver-
rated in IC-Prolog (Clark and McCabe, 1979) and intelligent backtraclung sion, Program 14.15, is a slight variant of a teaching program attributed
(Bruynooghe and Pereira, 1984). Neitlier have been widely adopted into to Alan Bundy, hchard O'Keefe, and Henry Thompson, whch was used
Prolog. for A1 courses at the University of Edinburgh.
Other examples of solving puzzles by instantiating structures are given McSAM is a version of the SAM program, which was tailored for
in a book by Evan Tick (1991) comparing Prolog program performance teachmg A1 programming (Schank and hesbeck, 1981). Our version,
with concurrent logic programming languages. Program 14.16, is due to Ernie Davis and Ehud Shapiro. More informa-
The zebra puzzle, Exercise 14.l(iv)did the rounds on the Prolog Digest tion about conceptual dependency can be found in Schank and Abelson
in the early 1980s. It was used as an unofficial benchmark to test both (1977).
the speed of Prolog implementations and the ability of Prolog program- A rational reconstruction of GPS, suggested in Exercise 14.3(v), was
mers to write clear code. The description of clues given in Program 14.7 shown to us by George Ernst.
was influenced by one of the solutions. The framework of Program 14.6
was tested extensively by Steven Kaminslu in a course project at Case
Western Reserve University. He took the first 20 puzzles of an avail-
able puzzle book and solved them using the framework. Although very
much a Prolog novice, he was able to use Prolog fairly easily to find so-
lutions. His experience hghlighted some interesting points, namely, how
to handle negative information and the undesirability of too many choice
points with redundant calls to s e l e c t and member.
The definitive discussion of don't-care and don't-know nondetermin-
ism in logic programming appears in Kowalski (1979a).
Program 14.11 for planning is a variant of an example from Kowalski
(1979a).The original planning program in Prolog was WARPLAN (Warren,
1976), reproduced in Coelho et al. (1980). Exercise 14.2(ii) was adapted
from descriptions of WARPLAN1sabilities in Coelho and Cotta (1988).
Incomplete Data Structures

The programs presented so far have been discussed in terms of relations


between complete data structures. Powerful programming techniques
emerge from extending the discussion to incomplete data structures, as
demonstrated in this chapter.
The first section discusses difference-lists, an alternative data struc-
ture to lists for representing a sequence of elements. The) can be used
to simplify and increase the efficiency of list-processing programs. In
some respects, difference-lists generalize the concept of accumulators.
Data structures built from the difference of incomplete structures other
than lists are discussed in the second section. The third section shows
how tables and dictionaries, represented as incomplete structures, can
be built incrementally during a computation. The final section discusses
queues, an application of difference-lists.

15.1 Difference-Lists

Consider the sequence of elements 1,2,3. It can be represented as the


difference between pairs of lists. It is the difference between the lists
[1,2,3,4,5] and [4,5], the difference between the lists [1,2,3,8] and [8], and
the difference between [1,2,3] and [ 1. Each of these cases is an instance
of the difference between two incomplete lists [1,2,3IXs] and Xs.
We denote the difference between two lists as a structure As\Bs, which
is called a difference-list. As is the head of the difference-list and Bs the
Chapter 15 Incomplete Data Structures

tail. In thls example [1,2,3/Xs]\Xs is the most general difference-list repre-


senting the sequence 1,2,3,where [1,2,3IXs] is the head of the difference-
list and Xs the tail.
Logical expressions are unified, not evaluated. Consequently the bi-
nary functor used to denote difference-lists can be arbitrary. Of course,
the user must be consistent in using the same functor in any one pro-
gram. Another common choice of functor besides \ is -. The functor for
difference-lists can also be omitted entirely, the head and the tail of the
difference-list becoming separate arguments in a predicate. While t h s
last choice has advantages from a perspective of efficiency, we use the Figure 15.1 Concatenating difference-lists
functor \ throughout for clarity.
Lists and difference-lists are closely related. Both are used to repre-
sent sequences of elements. Any list L can be trivially represented as a append-dl(As,Bs,Cs) -
The difference-list Cs is the result of appending Bs to As,
difference-list L\[ 1. The empty list is represented by any difference-list
where As and Bs are compatible difference-lists.
whose head and tail are identical, the most general form being As\As.
Difference-lists are an established logic programming techmque. The append-dl(Xs\Ys, Ys\Zs, Xs\Zs).
use of difference-lists rather than lists can lead to more concise and Program 15.1 Concatenating difference-lists
efficient programs. The improvement occurs because of the combining
property of difference-lists. Two incomplete difference-lists can be con-
catenated to give a third difference-list in constant time. In contrast, lists (allocating new list-cells). There is a difference between the two: the for-
are concatenated using the standard append program in time linear in mer are free of side effects and can be discussed in terms of the abstract
the length of the first list. computation model, whereas rplacd is a destructive operation, whch
Consider Figure 15.1. The difference-list Xs\Zs is the result of append- can be described only by reference to the machine representation of S-
ing the difference-list Ys\Zs to the difference-list Xs\Ys. This can be expressions.
expressed as a single fact. Program 15.1 defines a predicate append- A good example of a program that can be improved by using differ-
dl (As ,Bs,Cs), which is true if the difference-list Cs is the result of ence-lists is Program 9.la for flattening a list. It uses double recursion to
appending the difference-list Bs to the difference-list As. We use the suf- flatten separately the head and tail of a list of lists, then concatenates
fix -dl to denote a variant of a predicate that uses difference-lists. the results. We adapt that program to compute the relation flatten-
A necessary and sufficient condition characterizing when two differ- dl (Xs ,Ys), where Ys is a difference-list representing the elements that
ence-lists As\Bs and Xs\Ys can be concatenated using Program 15.1 is appear in a list of lists Xs in correct order. The direct translation of
that Bs be unifiable with Xs. In that case, the two difference-lists are com- Program 9.la to use difference-lists follows:
patible. If the tail of a difference-list is uninstantiated, it is compatible
with any difference-list. Furthermore, in such a case Program 15.1 would f latten-dl( [X I Xsl ,Ys\Zs) -
concatenate it in constant time. For example, the result of the query f latten-dl (X,As\Bs) , f latten-dl (Xs,Cs\Ds) ,
append-dl([a,b,clXsl\Xs, [1,21\[ 1 ,Ys)? is (Xs=[1,21 ,Ys=[a,b,c, append-dl(As\Bs,Cs\Ds,Ys\Zs).
1,21\C I). f latten-dl(X, CX I Xsl \Xs) -
Difference-lists are the logic programming counterpart of Lisp's rplacd, constant (X) , Xf [ 1 .
which is also used to concatenate lists in constant time and save consing f latten-dl ( C I ,Xs\Xs) .
Chapter 15 Incomplete Data Structures

flatten (Xs,Ys) - flatten([[al, [b, [clll ,Xs)


f latten-dl( [[a] , [b, [clll ,Xs\ [ 1 )
Ys is a flattened list containing the elements in X s .
flatten(Xs,Ys) - flatten-dl(xs,~s\[ I). f latten-dl( [a] ,Xs\Xsl)

f latten-dl( [XI Xsl ,Ys\Zs) - f latten_dl(a,Xs\Xs2)


constant (a)
Xs = [a1 Xs21

f latten-dl(X,Ys\Ysl) , flatten-dl(Xs ,Ysl\Zs) .


f latten-dl(X, [XI Xsl \XS) - a # [I
f latten-dl( [I ,Xs2\Xs1) Xs2 = Xsl
constant (X) , X# [ I . f latten-dl( [ [b, Cclll ,Xsl\ [ 1 )
f latten-dl( [ 1 ,Xs\Xs). flatten-dl( [b, [ell ,Xsl\Xs3)
f latten-dl (b ,Xsl\Xs4) Xsl = CblXs41
Program 15.2 Flattening a list of lists using difference-lists constant (b)
b f [ I
f latten-dl( [ [ell ,Xs4\Xs3)
The doubly recursive clause can be simplified by unfolding the append- f latten-dl( [cl ,Xs4\Xs5)
dl goal with respect to its definition in Program 15.1. Unfolding is dis- flatten-dl(c,Xs4\Xs6) Xs4 = [C lXs61
cussed in more detail in Chapter 18 on program transformation. The constant (c)
result is c # [I
f latten-dl( [ 1 ,Xs6\Xs5)
f latten-dl( [X I XS] ,As\Ds) - f latten-dl( [ 1 ,Xs5\Xs3)
f latten-dl( [ 1 ,Xs3\ [ 1 )
Xs6
Xs5
Xs3
=
=
Xs5
Xs3
I
f latten-dl (X,As\Bs) , f latten-dl (Xs,Bs\Ds) . =
Output: Xs = [a,b,c]
The program for flatten-dl can be used to implement flatten by ex-
Figure 15.2 Tracing a computation using difference-lists
pressing the connection between the desired flattened list and the
difference-list computed by f latten-dl as follows:

The discrepancy between clear declarative understanding and difficult


Collecting the program and renaming variables yields Program 15.2. procedural understanding stems from the power of the logical variable.
Declaratively Program 15.2 is straightforward. The explicit call to ap- We can specify logical relations implicitly and leave their enforcement to
pend is made unnecessary by flattening the original list of lists into a Prolog. Here the concatenation of the difference-lists has been expressed
difference-list rather than a list. The resultant program is more efficient, implicitly, and it is mysterious when it happens in the program.
because the size of its proof tree is linear in the number of elements in Building structures with difference-lists is closely related to building
the list of lists rather than quadratic. structures with accumulators. Loosely, difference-lists build structures
The operational behavior of programs using difference-lists, such as top-down, whle accumulators build structures bottom-up. Exercise 9.l(i)
Program 15.2, is harder to understand. The flattened list seems to be asked for a doubly recursive version of flatten that avoided the call to
built by magic. append by using accumulators. A solution is the following program:
Let us investigate the program in action. Figure 15.2 is a trace of the
query flatten( [[a] , [b, [c]]] ,Xs)? with respect to Program 15.2.
The trace shows that the output, Xs, is built top-down (in the terminol- flatten( [XIXs] ,Zs,Ys) -
ogy of Section 7.5). The tail of the difference-list acts like a pointer to the flatten(Xs,Zs,Ysl) , flatten(X,Ysl,Ys) .
end of the incomplete structure. The pointer gets set by unification. By f latten(X,Xs, [XI Xsl -
using these "pointers" no intermediate structures are built, in contrast constant (XI, X# [ I .
to Program 9.la. flatten( [ I ,Xs,Xs) .
Chapter 15 Incomplete D a t a Structures

reverse(Xs,Ys) -
Ys is the reversal of the list Xs.
quicksort (List,SortedList) -
SortedList is an ordered permutation of List.
reverse (Xs,Ys) - reverse-dl ( X S,YS\ [ 1) . quicksort (Xs,Ys) - quicksort-dl(Xs,Ys\ [ 1) .
reverse-dl( [X IXS] ,YS\ZS) - quicksort-dl ( [X 1 X s ,Ys\Zs) -
reverse-dl ( X S,Ys\ [XI Zs] ) . partition(Xs ,X,Littles,Bigs),
reverse-dl( [ I ,~s\Xs). quicksort~dl(~ittles,~s\[X~Ysll),
quicksort~dl(Bigs,Ys1\Zs).
Program 15.3 Reverse with difference-lists quicksort-dl([ I,Xs\Xs).
partition(Xs,X,Ls,Bs) - See Program 3.22
Program 15.4 Quicksort using difference-lists
The similarity of this program to Program 15.2 is strilung. There are only
two differences between the programs. The first difference is syntactic.
The difference-list is represented as two arguments, but in reverse order, elements Ys and before the bigger elements Ysl in the call quicksort-
the tail preceding the head. The second difference is the goal order in dl (Littles ,Ys\[XJYsl]) .
the recursive clause of flatten. The net effect is that the flattened list is Program 15.4 is derived from Program 3.22 in exactly the same way
built bottom-up from its tail rather than top-down from its head. as Program 15.2 is derived from Program 9.la. Lists are replaced by
We give another example of the similarity between difference-lists difference-lists and the append-dl goal unfolded away. The initial call of
and accumulators. Program 15.3 is a translation of naive reverse (Pro- quicksort-dl by quicksort expresses the relation between the desired
gram 3.16a) where lists have been replaced by difference-lists, and the sorted list and the computed sorted difference-list.
append operation has been unfolded away. An outstanding example of using difference-lists to advantage is a solu-
When are difference-lists the appropriate data structure for Prolog pro- tion to a simplified version of Dijkstra's Dutch flag problem. The problem
grams? Programs with explicit calls to append can usually gain in effi- reads: "Given a list of elements colored red, white, or blue, reorder the
ciency by using difference-lists rather than lists. A typical example is a list so that all the red elements appear first, then all the white elements,
doubly recursive program where the final result is obtained by append- followed by the blue elements. This reordering should preserve the orig-
ing the outputs of the two recursive calls. More generally, a program that inal relative order of elements of the same color." For example, the list
independently builds different sections of a list to be later combined is a [red(l) ,white(2) ,blue(3) ,red(4) ,white(5)1 should be reordered to
good candidate for using difference-lists. [red(l) ,red(4) ,white(2) ,white(5) ,blue(3)].
The logic program for quicksort, Program 3.22, is an example of a Program 15.5 is a simple-minded solution to the problem that collects
doubly recursive program where the final result, a sorted list, is obtained the elements in three separate lists, then concatenates the lists. The basic
from concatenating two sorted sublists. It can be made more efficient by relation is dutch(&, Ys), where Xs is the original list of colored elements
using difference-lists. All the append operations involved in combining and Ys is the reordered list separated into colors.
partial results can be performed implicitly, as shown in Program 15.4. The heart of the program is the procedure distribute, which con-
The call of quicksort-dl by quicksort is an initializing call, as for structs three lists, one for each color. The lists are built top-down. The
flatten in Program 15.2. The recursive clause is the quicksort algorithm two calls to append can be removed by having distribute build three
interpreted for difference-lists where the final result is pieced together distinct difference-lists instead of three lists. Program 15.6 is an appro-
implicitly rather than explicitly. The base clause of quicksort-dl states priately modified version of the program.
that the result of sorting an empty list is the empty difference-list. Note The implicit concatenation of the difference-lists is done in the ini-
the use of unification to place the partitioning element X after the smaller tializing call to distribute-dls by dutch. The complete list is finally
Chapter 15 Incomplete Data Structures

dutch(Xs,RedsWhitesBlues) - dutch (Xs,RedsWhitesBlues)


RedsWhitesBlues is a list
-of elements of Xs ordered
RedsWhitesBlues is a list of elements of Xs ordered
by color: red, then whlte, then blue. by color: red, then white, then blue.
dutch(Xs,RedsWhitesBlues) -
distribute(Xs,Reds,Whites,Blues),
dutch(Xs,RedsWhitesBlues) -
distribute-dls(Xs,RedsWhitesBlues\WhitesBlues,
append(Whites,Blues,WhitesBlues), WhitesBlues\Blues,Blues\[ 1).
append(Reds,WhitesBlues,RedsWhitesBlues). distribute-dls (Xs,Reds,Whites,Blues) -difference-lists of the
distribute(Xs,Reds, Whites,Blues)- Reds, Whites, and Blues are the
red, white, and blue elements in Xs, respectively.
Reds, Whites, and Blues are the lists of the red, white,
and blue elements in Xs, respectively. distribute-dls ( [red(X) I Xsl ,
distribute( [red(X) IXsl, [red(X) (Reds1 ,Whites,Blues) - [red(~)I~eds]\Redsl,Whites,Blues)
distribute-dls(Xs,Reds\Redsl,Whites,Blues).
-
distribute(Xs,Reds,W~tes,Blues).
distribute( [white(X) lXs] ,Reds,[white(X) IWhitesl ,Blues) - distribute-dls( [white(X) I Xsl ,
-
distribute(Xs,Reds,Whites,Blues).
distribute( [blue(X) I Xs] ,Reds,Whites, [blue(X) 1Bluesl ) - ~eds,[white(X)IWhites]\Whitesl,Blues)
distribute-dls(Xs,Reds,Whites\Whitesl,Blues).
distribute-dls ( [blue (XI I Xsl ,
distribute(Xs,Reds,Whites,Blues).
distribute([ I,[ I,[ I,[ I). Reds,Whites,[blue(X) IBluesl\Bluesl) -
append(Xs,Ys,Zs) - See Program 3.15. distribute-dls(Xs,Reds,Whites,Blues\Bluesl).
distribute-dls([ 1 ,Reds\Reds,Whites\Whites,Blues\Blues)
Program 15.5 A solution to the Dutch flag problem Program 15.6 Dutch flag with difference-lists

"assembled" from its parts with the satisfaction of the base clause of curring in a binary tree, to use difference-lists and avoid an explicit
distribute-dls. call to append.
The Dutch flag example demonstrates a program that builds parts of
(iii) Rewrite Program 12.3 for solving the Towers of Hanoi so that the
the solution independently and pieces them together at the end. It is a list of moves is created as a difference-list rather than a list.
more complex use of difference-lists than the earlier examples.
Although it makes the program easier to read, the use of an explicit
constructor such as \ for difference-lists incurs noticeable overhead
in time and space. Using two separate arguments to represent the 1 5.2 Difference-Structures
difference-list is more efficient. When important, t h s efficiency can be
gained by straightforward manual or automatic transformation. The concept underlying difference-lists is the use of the difference be-
tween incomplete data structures to represent partial results of a compu-
tation. This can be applied to recursive data types other than lists. T h s
Exercises for Section 15.1 section looks at a specific example, sum expressions.
Consider the task of normalizing sum expressions. Figure 15.3 con-
(i) Rewrite Program 15.2 so that the final list of elements is in the tains two sums (a + b ) + (c + d ) and (a + ( b + ( c + d ) ) ) .Standard Prolog
reverse order to how they appear in the list of lists. syntax brackets the term a + b + c as ( ( a+ b ) + c ) . We describe a pro-
(ii) Rewrite Programs 3.27 for reorder (Tree,List), inorder (Tree, cedure converting a sum into a normalized one that is bracketed to the
List) and postorder (Tree,L i s t ) , whlch collect the elements oc- right. For example, the expression on the left in Figure 15.3 would be
Chapter 15 Incomplete Data Structures

of the recursive clause pass the tail of the first incomplete structure to
be the head of the second.
The program builds the normalized sum top-down. By analogy with the
programs using difference-lists, the program can be easily modified to
build the structure bottom-up, whlch is Exercise (ii) at the end of t h s
section.
The declarative reading of these programs is straightforward. Opera-
tionally the programs can be understood in terms of building a structure
incrementally, where the "hole" for further results is referred to explic-
Figure 15.3 Unnormalized and normalized sums
itly. This is entirely analogous to difference-lists.

normalize(Sum,NormalizedSum) - Exercises for Section 15.2


NormalizedSum is the result of normalizing the sum expression Sum.
(i) Define the predicate normalized~sum(Expression), which is true
normalize-ds(A+B,Norm++Space) if Expression is a normalized sum.
normalize-ds (A,Norm++NormB) , normalize-ds (B,~ o r m ~ + + ~ p a c e )
normalize-ds(A,(A+Space)++Space) - (ii) Rewrite Program 15.7 so that
constant (A) .
(a) The normalized sum is built bottom-up;
Program 15.7 Normalizing plus expressions
(b) The order of the elements is reversed.
(iii) Enhance Program 15.7 so that numbers appearing in the addends
converted to the one on the right. Such a procedure is useful for doing are added together and returned as the first component of the nor-
algebraic simplification, facilitating writing programs to test whether two malized sum. For example, ( 3 + x ) + 2 + (y + 4 ) should be normal-
expressions are equivalent. ized to 9 + ( x + y ) .
We introduce a difference-sum as a variant of a difference-list. A
difference-sum is represented as a structure E l ++ E 2 , where E l and (iv) Write a program to normalize products using difference-products,
E 2 are incomplete normalized sums. It is assumed that ++ is defined as a defined analogously to difference-sums.
binary infur operator. It is convenient to use 0 to indicate an empty sum.
Program 15.7 is a program for normalizing sums. The relation scheme
is normalize (Exp ,Norm), where Norm is an expression equivalent to Exp
1 5.3 Dictionaries
that is bracketed to the right and preserves the order of the constants
appearing in Exp.
A different use of incomplete data structures enables the implementa-
T h s program is similar in structure to Program 15.2 for flattening
tion of dictionaries. Consider the task of creating, using, and maintaining
lists using difference-lists. There is an initialization stage, where the
a set of values indexed under keys. There are two main operations we
difference-structure is set up, typically calling a predicate with the same
would like to perform: loolung up a value stored under a certain key, and
name but different arity or different argument pattern. The base case
entering a new key and its associated value. These operations must en-
passes out the tail of the incomplete structure, and the goals in the body
sure consistency - for example, the same key should not appear twice
Chapter 15 Incomplete Data Structures

lookup(Key,Dictionary,Value) - lookup (Key,Dictionary,Value) -


Dictionary contains Value indexed under Key.
Dictionary contains Value indexed under Key.
Dictionary is represented as an incomplete Dictionary is represented as an ordered binary tree.
list of pairs of the form ( K e y ,Value). lookup(Key ,dict (Key ,X ,Left ,Ftight),Value) -
! , X = Value.
lookup(Key,[(Key,Value)lDict1,Value).
lookup(Key,[(Keyl,Valuel) lDict1,Value) . lookup(Key ,dict (Keyl,X ,Left ,Flight), V a l u e -
Key f Keyl, lookup(Key,Dict,Value). Key < Keyl, lookup(Key,Left,Value).

Program 15.8 Dictionary lookup from a list of tuples


lookup(Key,dict(Keyl,X,Left, ~ i g h t,)V a l u e
Key > Keyl, lookup(Key,Right,Value).
-
Program 15.9 Dictionary lookup in a binary tree
with two different values. It is possible to perform both operations, look-
ing up values of keys, and entering new keys, with a single simple proce-
dure by exploiting incomplete data structures. during the matchng against the stimulus half of a stimulus-response
Consider a linear sequence of key-value pairs. Let us see the advan- pair. The constructed dictionary is used to produce the correct response.
tages of using an incomplete data structure for its representation. Pro- Note that entries are placed in the dictionary without their values being
gram 15.8 defines the relation lookup (Key, D i c t i o n a r y , Value) which is known: a strilung example of the power of logical variables. Once an
true if the entry under Key in the dictionary D i c t i o n a r y has value Value. integer is detected, it is put in the dictionary, and its value is determined
The dictionary is represented as an incomplete list of pairs of the form later.
(Key, Value). Searchng linear lists is not very efficient for a large number of key-
Let us consider an example where the dictionary is used to remember value pairs. Ordered binary trees allow more efficient retrieval of infor-
phone extensions keyed under the names of people. Suppose that D i c t is mation than linear lists. The insight that an incomplete structure can be
initially instantiated to [ ( a r n o l d , 8881) , ( b a r r y , 4513) , ( c a t h y ,5950) used to allow entry of new keys as well as to look up values carries over
/Xsl . The query lookup ( a r n o l d , D i c t ,N) ? has as answer N=8881 and to binary trees.
is used for finding Arnold's phone number. The query l o o k u p ( b a r r y , The binary trees of Section 3.4 are modified to be a four-place structure
D i c t ,4513) ? succeeds, checking that Barry's phone number is 45 13. d i c t (Key, Value, L e f t , R i g h t ) , where L e f t and Right are, respectively,
The entry of new keys and values is demonstrated by the query the left and right subdictionaries, and Key and Value are as before. The
l o o k u p ( d a v i d , D i c t ,1199) ?. Syntactically this appears to check David's functor d i c t is used to suggest a dictionary.
phone number. Its effect is different. The query succeeds, instantiating Looking up in the dictionary tree has a very elegant definition, simi-
D i c t to [ ( a r n o l d , 8 8 8 1 ) , ( b a r r y , 4 5 1 3 ) , ( c a t h y , 5950) , ( d a v i d , 1199) lar in spirit to Program 15.8. It performs recursion on binary trees rather
IXslI. Thus lookup has entered a new value. than on lists, and relies on unification to instantiate variables to dictio-
What happens if we check Cathy's number with the query lookup nary structures. Program 15.9 gives the procedure lookup (Key, D i c t i o -
( c a t h y ,D i c t ,5951)?, where the number is incorrect? Rather than en- n a r y , Value), whch as before both looks up the value corresponding to
tering a second entry for Cathy, the query fails because of the test Key f a given key and enters new values.
Keyl. At each stage, the key is compared with the key of the current node.
The lookup procedure given in Program 15.8 completes Program 14.15, If it is less, the left branch is recursively checked; if it is greater, the
the simplified ELIZA. Note that when the program begins, the dictionary right branch is taken. If the key is non-numeric, the predicates < and >
is empty, indicated by its being a variable. The dictionary is built up must be generalized. The cut is necessary in Program 15.9, in contrast to
Chapter 15 297 Incomplete Data Structures

freeze(A,B) -
Freeze term A into B. 15.4 Queues
freeze (A , B ) -
copy-term(A,B) , numbervars(B,O ,N) . An interesting application of difference-lists is to implement queues.
melt-new (A,B) -
Melt the frozen term A into B.
A queue is a first-in, first-out store of information. The head of the
difference-list represents the beginning of the queue, the tail represents
the end of the queue, and the members of the difference-list are the ele-
ments in the queue. A queue is empty if the difference-list is empty, that
melt ('$VARJ (N) ,X,Dictionary) - is, if its head and tail are identical.
Maintaining a queue is different from maintaining a dictionary. We
lookup(N,Dictionary,X).
melt(X,X,~ictionary) - consider the relation queue(S), where a queue processes a stream of
constant (X) .
melt(X,Y,Dictionary)
compound(X),
- commands, represented as a list S. There are two basic operations on a
queue-enqueuing an element and dequeuing an element-represented,
functor(X,F,N),
respectively, by the structures enqueue (XI and dequeue (X), where X is
functor(Y ,F,N) , the element concerned.
melt(N,X,Y,Dictionary). Program 15.11 implements the operations abstractly. The predicate
melt(N,X,Y,Di~ti~nary)
N > 0,
- queue(S) calls queue(S,Q),where Q is initialized to an empty queue.
queue/',? is an interpreter for the stream of enqueue and dequeue com-
arg(N,X,ArgX), mands, responding to each command and updating the state of the
melt (ArgX ,ArgY ,Dictionary), queue accordingly. Enqueuing an element exploits the incompleteness of
arg(N,Y ,ArgY) ,
the tail of the queue, instantiating it to a new element and a new tail,
N1 is N-1,
melt(Nl,X,Y,Dictionary). which is passed as the updated tail of the queue. Clearly, the calls to
melt(O,X,Y,Di~tionary). enqueue and dequeue can be unfolded, resulting in a more concise and
numbervars (Term,N1 ,N2) - See Program 10.8. efficient, but perhaps less readable, program.
lookup(Key,Dictionary,Value) - See Program 15.9.
Program 15.10 Melting a term queue(S) -
S is a sequence of enqueue and dequeue operations,
represented as a list of terms enqueue(X1 and dequeue(X)
Program 15.8, because of the nonlogical nature of comparison operators, queue(S) - queue(S ,Q\Q) .
whch will give errors if keys are not instantiated. queue ( [enqueue (XI I Xsl ,Q) -
Given a number of pairs of keys and values, the dictionary they deter-
mine is not unique. The shape of the dictionary depends on the order in
enqueue(X,Q,Ql), queue(Xs,Ql).
queue ( [dequeue (XI I Xs] ,Q) -
dequeue(X,Q,Ql), queue(Xs,Ql).
which queries are posed to the dictionary.
queue([ I ,Q).
The dictionary can be used to melt a term that has been frozen using
enqueue (X,Qh\ [X I Qtl ,Qh\Qt) .
Program 10.8 for numbervars. The code is given as Program 15.10. Each dequeue(X, [X I Qhl \Qt ,Qh\Qt) .
melted variable is entered into the dictionary, so that the correct shared
variables will be assigned. Program 15.11 A queue process
Chapter 15 Incomplete Data Structures

flatten(Xs,Ys) -
Ys is a flattened list containing the elements in X s
The explicit call to enqueue can be omitted and incorporated via unifica-
tion as follows:

flatten-q( [XI Xsl ,Ps\ [Xs l Qsl ,Ys) -


flatten-q(X,Ps\Qs,Ys).
flatten-q(~,[Q l PSI \Qs, [XIYsI ) - If the element being flattened is a constant, it is added to the output
structure being built top-down, and an element is dequeued (by unifying
constant(X), X# I , flatten-q(Q,Ps\Qs,Ys).
flatten-q([ 1 ,Q,Ys) - with the head of the difference-list) to be flattened in the recursive call:
non-empty(Q), dequeue(X,Q,Ql), flatten-q(X,Ql,Ys).
flatten-q([ I,[ I\[ I,[ I).
f latten-q(X, [Q1 Qhl \Qt , [X I Ysl)-
constant (X) , X# [ I , f latten-q(Q,Qh\Qt,ys).
non-empty([ I\[ 1 )
non-empty(Q1.
- ! , fail.
When the empty list is being flattened, either the top element is de-
dequeue (X , [X 1 Qhl \Qt,Qh\qt) queued

Program 15.12 Flattening a list using a queue

or the queue is empty, and the computation terminates:


The program terminates when the stream of commands is exhausted.
It can be extended to insist that the queue be empty at the end of the
commands by changing the base fact to A previous version of Program 15.12 incorrectly expressed the case
when the list was empty, and the top element was dequeued as

A queue is empty if both its head and tail can be instantiated to the Thls led to a nonterminating computation, since an empty queue Qs\Qs
empty list, expressed by the fact empty( [ I \ [ I ) . Logically, the clause unified with [Q I Qh] \Qt and so the base case was never reached.
empty(Xs\Xs) would also be sufficient; however, because of the lack Let us reconsider Program 15.11 operationally. Under the expected use
of the occurs check in Prolog, discussed in Chapter 4, it may succeed of a queue, enqueue()() messages are sent with X determined and de-
erroneously on a nonempty queue, creating a cyclic data structure. queue (X) with X undetermined. As long as more elements are enqueued
We demonstrate the use of queues in Program 15.12 for flattening a than dequeued, the queue behaves as expected, with the difference be-
list. Although the example is somewhat contrived, it shows how queues tween the head of the queue and the tail of the queue being the elements
can be used. The program does not preserve the order of the elements in in the queue. However, if the number of dequeue messages received ex-
the original list. ceeds that of enqueue messages, an interesting thing happens - the
The basic relation is f latten-q(Ls ,Q,Xs), where Ls is the list of lists content of the queue becomes negative. The head runs ahead of the tail,
to be flattened, Q is the queue of lists waiting to be flattened, and Xs is resulting in a queue containing a negative sequence of undetermined el-
the list of elements in Ls. The initial call of f latten-q/3 by f latten/2 ements, one for each excessive dequeue message.
initializes an empty queue. The basic operation is enqueuing the tail of It is interesting to observe that this behavior is consistent with the as-
the list and recursively flattening the head of the list: sociativity of appending of difference-lists. If a queue Qs\ [XI ,X2,X31Qsl
that contains minus three undetermined elements has the queue [a,b ,
c,d, e/Xs]\Xs that contains five elements appended to it, then the result
Chapter 1 5

will be the queue Ed, elXs1 \ X s with two elements, where the "negative"
elements XI, X2, X3 are unified with a , b ,c.
Second-OrderProgramming
1 5.5 Background

Difference-lists have been in the logic programming folklore since its


inception. The first description of them in the literature is given by Clark
and Tarnlund (1977).
The automatic transformation of simple programs without difference-
lists to programs with difference-lists, for example, r e v e r s e and f l a t -
t e n , can be found in Bloch (1984). Chapters 14 and 15 demonstrate Prolog programming techniques based
Section 15.1 implicitly contains an algorithm for converting from a directly on logic programming. T h s chapter, in contrast, shows pro-
program with explicit calls to append to an equivalent, more efficient gramming techniques that are missing from the basic logic programming
program that uses difference-lists to concatenate the elements and which model but can nonetheless be incorporated into Prolog by relying on lan-
is much more efficient. Care is needed in application of the algorithm. guage features outside of first-order logic. These techniques are called
There are excellent discussions of a correct algorithm and the dangers second-order, since they talk about sets and their properties rather than
of using difference-lists without the occurs check in Srandergaard (1990) about individuals.
and Marriott and Srandergaard (1993). The first section introduces predicates that produce sets as solutions.
There is an interesting discussion of the Dutch flag problem in O'Keefe Computing with predicates that produce sets is particularly powerful
(1990). when combined with programming techniques presented in earlier chap-
Automatic removal of a functor denoting difference-lists is described ters. The second section gives some applications. The third section looks
in Gallagher and Bruynooghe (1990). at lambda expressions and predicate variables, whch allow functions
Maintaining dictionaries and queues can be given a theoretical basis as and relations to be treated as "first-class" data objects.
a perpetual process, as described by Warren (1982) and Lloyd (1987).
Queues are particularly important in concurrent logic programming
languages, since their input need not be a list of requests but a stream,
whch is generated incrementally by the processes requesting the ser-
vices of the queue. 16.1 All-Solutions Predicates

Solving a Prolog query with a program entails finding an instance of


the query that is implied by the program. What is involved in finding
all instances of a query that are implied by a program? Declaratively,
such a query lies outside the logic programming model presented in
Chapter 1. It is a second-order question, since it asks for the set of
elements with a certain property. Operationally, it is also outside the
pure Prolog computation model. In pure Prolog, all information about a
certain branch of the computation is lost on backtraclung. This prevents
Chapter 16 Second-Order Programming

father(terach,abraham). father(haran,lot). forall (Goa1,Condition)


father(terach,nachor). father(haran,milcah) . For all solutions of Goal, Condition is true.
f ather(terach,haran) .
father(abraham,isaac).
father(haran, yiscah) .
for-all(Goa1,Condition) -
findall(Condition,Goal,Cases), check(Cases)
male(abraham).
male(isaac1.
rnale(haran).
male(nachor).
fernale(yiscah)
female(mi1cah) check( [Case 1 Cases] )
check( [ 1 ) .
- Case, check(Cases1
male(1ot).

Program 16.1 Sample data Program 16.2 Applying set predicates

who is a father and to receive as solution [ t e r a c h , h a r a n , abrahaml . T h s


a simple way of using pure Prolog to find the set of all solutions to a
answer can be obtained by removing duplicate solutions.
query, or even to find how many solutions there are to a given query.
Another interpretation can be made of the query f i n d a l l ( F , f a t h e r
T h s section discusses predicates that return all instances of a query.
( F , K) ,F s ) ? . Instead of having a single solution, all fathers, there could
We call such predicates all-solutions predicates. Experience has shown
be a solution for each child K. Thus one solution would be K=abraham,
that all-solutions predicates are very useful for programming.
Fs = [ t e r a c h ] ; another would be K=lot , Fs = [haran] ; and so on.
A basic all-solutions predicate is f i n d a l l (Term, Goal, Bag). The pred-
Standard Prolog provides two predicates that distinguish between
icate is true if and only if Bag unifies with the list of values to whch a
these two interpretations. The predicate bagof (Term, Goal, Bag) is like
variable X not occurring in Term or Goal would be bound by successive
f i n d a l l except that alternative solutions are found for the variables in
resatisfaction of c a l l ( G o a l ) , X=Term? after systematic replacement of
Goal. The predicate s e t o f (Term, Goal, Bag) is a refinement of bagof
all variables in X by new variables.
where the solutions in Bag are sorted corresponding to a standard order
Procedurally, f i n d a l l (Term, Goal, Bag) creates an empty list L, re-
of terms and duplicates removed. If we want to emphasize that the solu-
names Goal to a goal G, and executes G. If G succeeds, a copy of Term
tion should be conceived of as a set, we refer to all-solutions predicates
is appended to L, and G is reexecuted. For each successful reexecution, a
as set predicates.
copy of Term is appended to the list. Eventually, when G fails, Bag is uni-
Another all-solutions predicate checks whether all solutions to a
fied with L. The success or failure of f i n d a l l depends on the success or
query satisfy a certain condition. Program 16.2 defines a predicate f o r -
failure of the unification.
a l l (Goal , C o n d i t i o n ) , which succeeds when Condition is true for all
We demonstrate the use of all-solutions predicates using part of the
values of Goal. It uses the meta-variable facility.
biblical database of Program 1.1, repeated here as Program 16.1.
The query f o r - a l l ( f a t h e r (X ,C) ,male (C) ) ? checks whch fathers
Consider the task of finding all the chldren of a particular father. It is
have only male children. It produces two answers: X=terach and X=abra-
natural to envisage a predicate c h i l d r e n ( X , K i d s ) , where Kids is a list
ham.
of chldren of X. It is immediate to define using f i n d a l l , namely,
A simpler, more efficient, but less general version of f o r - a l l can be
c h i l d r e n (X, Kids) -- f i n d a l l a id, f a t h e r ( x , a id) ,Kids) . written directly using a combination of nondeterminism and negation by
failure. The definition is
The query c h i l d r e n ( t e r a c h , Xs)? with respect to Program 16.1 pro-
duces the answer X s = [abraham,nachor ,haran].
for-all(Goa1,Condition) - not (Goal, n o t C o n d i t i o n ) .
The query f i n d a l l (F, f a t h e r (F, K) ,Fs) ? with respect to Program It successfully answers a query such as f o r - a l l (f a t h e r ( t e r a c h , X) ,
16.1 produces the answer F = [ t e r a c h , h a r a n , t e r a c h , h a r a n , t e r a c h , male (X) ) ? but fails to give a solution to the query f o r - a l l (f a t h e r (X,
h a r a n , abrahaml . It would be useful to conceive of t h s query as askmg C) ,male (C) )?.
Chapter 16 Second-Order Programming

find-all-dl (X,Goal,lnstances) - What should happen if the two lists do not intersect? Compare the
Instances is the multiset of code with the recursive definition of intersect.
instances of X for whlch Goal is true. The multiplicity
of an element is the number of different ways Goal can be
proved with it as an instance of X .
f ind-all-dl ( X ,Goal,Xs) - 16.2 Applications of Set Predicates
asserta('$instance'('$mark')), Goal,
asserts( '$instance' ( X ) ) , f a i l .
find-all-dl(X,Goal,Xs\Ys) - Set predicates are a significant addition to Prolog. Clean solutions are ob-
tained to many problems by using set predicates, especially when other
retract ( ' $ i n s t a n c e ' ( X ) ) , r e a p ( ~ , ~ s \ Y s ) ,! .
programming techniques, discussed in previous chapters, are incorpo-
rated. T h s section presents three example programs: traversing a graph
breadth-first, using the Lee algorithm for finding routes in VLSI circuits,
and producing a keyword in context (KWIC)index.
Program 16.3 Implementing an all-solutions predicate using difference- Section 14.2 presents three programs, 14.8, 14.9, and 14.10, for
lists, assert,and retract traversing a graph depth-first. We discuss here the equivalent programs
for traversing a graph breadth-first.
The basic relation is connected(X,Y), which is true if X and Y are
We conclude this section by showing how to implement a simple vari- connected. Program 16.4 defines the relation. Breadth-first search is im-
ant of findall. The discussion serves a dual purpose. It illustrates plemented by keeping a queue of nodes waiting to be expanded. The
the style of implementation for all-solutions predicates and gives a connected clause accordingly calls connected-bf s (Queue,Y), which is
utility that will be used in the next section. The predicate find-all- true if Y is in the connected component of the graph represented by the
dl(X,Goal, Instances) is true if Instances is the bag (multiset) of nodes in the Queue.
instances of X, represented as a difference-list, where Goal is true. Each call to connected-bfs removes the current node from the head
The definition of f ind-all-dl is given as Program 16.3. The program of the queue, finds the edges connected to it, and adds them to the tail
can only be understood operationally. There are two stages to the pro- of the queue. The queue is represented as a difference-list, and the all-
cedure, as specified by the two clauses for find-all-dl. The explicit solutions predicate f ind-all-dl is used. The program fails when the;
failure in the first clause guarantees that the second will be executed. queue is empty. Because difference-lists are an incomplete data struc-:
c
The first stage finds all solutions to Goal using a failure-driven loop, as- ture, the test that the queue is empty must be made explicitly. Otherwise j
serting the associated X as it proceeds. The second stage retrieves the the program would not terminate.
solutions. Consider the edge clauses in Program 16.4, representing the left-hand
f
Asserting $mark is essential for nested all-solutions predicates to work graph in Figure 14.3. Using them, the query connected(a,X)? gives the *
correctly, lest one set should "steal" solutions produced by the other all- values a, b, c,d, e, f,g, j,k,h, i for X on backtracking, which is a breadth- f
solutions predicate. first traversal of the graph. I
Like Program 14.8, Program 16.4 correctly traverses a finite tree or a ;
Exercise for Section 16.1 directed acyclic graph (DAG).If there are cycles in the graph, the program i
will not terminate. Program 16.5 is an improvement over Program 16.4 in 1
(i) Define the predicate intersect (XS ,Ys ,Zs) using an all-solutions whch a list of the nodes visited in the graph is kept. Instead of adding f
predicate to compute the intersection Zs of two lists Xs and Ys. all the successor nodes at the end of the queue, each is checked to see if '
Chapter 16 Second-Order Programming

connected (X,Y) - connected (X,Y ) -


Node X is connected to node Y in the DAG defined by Node X is connected to node Y in the graph defined by
edge/Z facts. edge/Z facts.
connected(X,Y) - e n q u e u e ( ~ , ~ \ ~ , Q l )connected-bf
, s(Q1 ,Y) . connected(X,Y) -
connected-bf s(Q,Y) --
empty(Q), ! , fail.
enqueue (X,Q\Q ,Ql), connected-bf s(Q1 ,Y,[XI ) .
connected-bf s(Q,Y ,Visited) - empty(Q), ! , fail.
connected-bf s (Q,Y)
connected-bfs(Q,Y) -
dequeue(X,~,~l),X=Y.
connected-bfs(Q,Y ,Visited)
connected-bf s(Q ,Y,Visited)
-- dequeue(X,Q,Ql), X=Y.
dequeue(X,Q,Ql) , enqueue-edges(X,Ql ,Q2), connected-bf s(Q2,~).
enqueue-edges (X,Xs\Ys ,Xs\Zs) - ,YS\ZS), ! .
f ind-all-dl(~,edge(~,~)
dequeue (X,Q ,Ql) ,
f indall (N,edge (X ,N) ,Edges),
empty([ I\[ I). filter(~dges,Visited,Visitedl,Ql,Q2),
enqueue/3, dequeue/3 - See Program 15.11. connected-bfs(Q2,Y,Visitedl).
-
f ind-all-dl(Term,Goal ,DList) - See Program 16.3. filter( [N INS] ,Visited,Visitedl , Q , Q ~ )
member(N,Visited), ! , filter(Ns,Visited,Visitedl,Q,Ql).
Data filter([NINs],Visited,Visitedl,Q,Q2) -
not member(N,Visited) , ! , enqueue(N,Q,Ql) ,
filter(Ns,[NI~isitedl,Visitedl,Ql,Q2).
filter([ I,~isited,Visited,Q,Q).

Program 16.4 Testing connectivity breadth-first in a DAG


empty([ I\[ I).
enqueue/3, dequeue/3 - See Program 15.11.
Program 16.5 Testing connectivity breadth-first in a graph
it has been visited before. Thls is performed by the predicate filter in
Program 16.5.
Program 16.5 in fact is more powerful than its depth-first equivalent, (1) Finite trees and DAGs
Program 14.10. Not only will it correctly traverse any finite graph but it Pure Prolog
will also correctly traverse infinite graphs in which every vertex has finite (2) Finite graphs
degree as well. It is useful to summarize what extensions to pure Prolog Pure Prolog + negation
(3) Infinite graphs
have been necessary to increase the performance in searching graphs.
Pure Prolog + second order + negation
Pure Prolog correctly searches finite trees and DAGs. Adding negation
allows correct searchng of finite graphs with cycles, whle set predicates Figure 16.1 Power of Prolog for various searching tasks
are necessary for infinite graphs. This is shown in Figure 16.1.
Calculating the path between two nodes is a little more awkward than
for depth-first search. It is necessary to keep with each node in the queue
a list of the nodes linking it to the original node. The t e c h q u e is demon-
strated in Program 20.6.
The next example combines the power of nondeterministic program-
ming with the use of second-order programming. It is a program for
calculating a minimal cost route between two points in a circuit using
the Lee algorithm.
Chapter 16 Second-Order Programming

operator. Paths are calculated by the program as a list of points from


B to A, including both endpoints. In Figure 16.2 the route calculated is
(5-5,5-4,s-3,s-2,4-2,3-2,2-2,l-2,l-11, and is marked by the heavy solid
line.
The top-level relation computed by the program is l e e - r o u t e (A, B ,
O b s t a c l e s , P a t h ) , where P a t h is a route (of minimal distance) from
point A to point B in the circuit. O b s t a c l e s are the obstacles in the grid.
The program has two stages. First, successive waves of neighboring grid
points are generated, starting from the initial point, until the final point
is reached. Second, the path is extracted from the accumulated waves.
Let us examine the various components of Program 16.6,the overall
program for Lee routing.
Waves are defined inductively. The initial wave is the list [A]. Succes-
sive waves are sets of points that neighbor a point in the previous wave
and that do not already appear in previous waves. They are illustrated by
the lighter solid lines in Figure 16.2.
Wave generation is performed by waves (B, WavesSoFar ,O b s t a c l e s ,
Waves). The predicate waves/4 is true if Waves is a list of waves to
the destination B avoiding the obstacles represented by O b s t a c l e s and
Figure 16.2 The problem of Lee routing for VLSI circuits WavesSoFar is an accumulator containing the waves generated so far in
traveling from the source. The predicate terminates when the destina-
tion is in the current wave. The recursive clause calls next_wave/4, whch
finds all the appropriate grid points constituting the next wave using the
The problem is formulated as follows. Given a grid that may have all-solutions predicate f i n d a l l .
obstacles, find a shortest path between two specified points. Figure 16.2 Obstacles are assumed to be rectangular blocks. They are represented
shows a grid with obstacles. The heavy solid line represents a shortest by the term o b s t a c l e ( L , R ) , where L is the coordinates of the lower
path between the two points A and B. The shaded rectangles represent left-hand corner and R the coordinates of the upper right-hand corner.
the obstacles. Exercise (i) at the end of t h s section requires modifying the program to
We first formulate the problem in a suitable form for programming. handle other obstacles.
The VLSI circuit is modeled by a grid of points, conveniently assumed to The predicate path(A, B, Waves, P a t h ) finds the path P a t h back from B
be the upper quadrant of the Cartesian plane. A route is a path between to A through the Waves generated in the process. P a t h is built downward,
two points in the grid, along horizontal and vertical lines only, subject whch means the order of the points is from B to A. T h s order can be
to the constraints of remaining in the grid and not passing through any changed by using an accumulator in path.
obstacles. Program 16.6 produces no output while computing the Lee route. In
Points in the plane are represented by their Cartesian coordinates and practice, the user may like to see the computation in progress. T h s can
denoted X-Y. In Figure 16.2,A is 1-1 and B is 5-5. This representation be easily done by adding appropriate w r i t e statements to the procedures
next-wave and p a t h .
is chosen for readability and utilizes the definition of - as an infm binary
Chapter 16 Second-Order Programming

lee-route(Source,Destination,Obstacles,Path) - path(Source,Destination,Waves,Path) -
Path is a path from Source to Destination going through Waves.
Path is a minimal length path from Source to
Destination that does not cross Obstacles. path(A,A,Waves,[A]) - !.
-
lee-route(A,B,Obstacles,Path) - path(A,B, [Wavel Waves] , [BI Path] )
member (B1,Wave) ,
waves (B,[ [A], [ 1I ,Obstacles,Waves) ,
path(A,B,Waves,Path). neighbor (B,B1),
waves (Destination,WavesSoFar,Obstacles,Waves) - ! , path(A,Bl,Waves,Path).
Testing and data
Waves is a list of waves including WavesSoFar
(except,perhaps, its last wave) that leads to Destination
without crossing Obstacles.
waves(B,[WavelWaves],Obstacles,Waves) -
member(B,Wave), ! .
waves(B,[Wave,LastWavelLastWavesl ,~bstacles,Waves) -
next-wave (Wave,LastWave,Obstacles,NextWave), Program 16.6 (Continued)
waves (B,[NextWave,Wave,LastWave1 ~astwaves],Obstacles,Waves)
next-wave( Wave,LastWave,Obstacles,NextWave) -
NextWave is the set of admissible points from Wave,
that is, excluding points from LastWave, Our final example in t h s section concerns the keyword in context
LVave and points under Obstacles. (KWIC) problem. Again, a simple Prolog program, combining nondeter-
next~wave(Wave,LastWave,0bstacles,NextWa~~~ - ministic and second-order programming, suffices to solve a complex
findall(X,admissible(X,Wave,LastWave,~bstacles),~ext~a~e). task.
admissible(X,Wave,LastWave,Obstacles) - Finding keywords in context involves searchng text for all occurrences
adjacent(X,Wave,Obstacles), of a set of keywords, extracting the contexts in whch they appear. We
not member (X,LastWave) , consider here the following variant of the general problem: "Given a list
not member(X,Wave).
of titles, produce a sorted list of all occurrences of a set of keywords in
adjacent(X,Wave,Obstacles) - the titles, together with their context."
member(Xl,Wave),
neighbor(X1,X),
Sample input to a program is given in Figure 16.3 together with the
not obstructed(X,Obstacles). expected output. The context is described as a rotation of the title with
neighbor(X1-Y,X2-Y) - next_to(Xl,X2). the end of the title indicated by -. In the example, the keywords are
neighbor(X-Y1,X-Y2) - next_to(Yl,Y2). algorithmic, debugging,logic,problem, program,programming, prolog,
next-to(X,Xl)
next-to(X,Xl)
-- XI is X+1.
X > 0 , XI is X-1.
and solving,all the nontrivial words.
The relation we want to compute is kwic(Tit1es ,KwicTitles) where
obstructed(Point,0bstacles) - Titles is the list of titles whose keywords are to be extracted, and Kwic-
Titles is the sorted list of keywords in their contexts. Both the input
member(Obstacle,Obstacles), obstructs(~oint,0bstacle).
obstructs(X-Y,obstacle(X-Y1,X2-Y2)) - Y1 I Y , Y Y2. 5
and output titles are assumed to be given as lists of words. A more gen-
obstructs(X-Y,obstacle(Xl-Y1,X-Y2)) - Y1 Y , Y 2 Y2.
5 eral program, as a preliminary step, would convert freer-form input into
obstructs(X-Y,obstacle(Xl-Y,X2-Y2)) - XI I X, X X2. 2 lists of words and produce prettier output.
obstructs(X-Y,obstacle(Xl-Y1,X2-Y)) - X1 I X, X 2 X2. The program is presented in stages. The basis is a nondeterministic
specification of a rotation of a list of words. It has an elegant definition
Program 16.6 Lee routing in terms of append:
C h a p t e r 16 Second-Order P r o g r a m m i n g

Input: programming in prolog kwic ( Titles,KWTitles) -


logic for problem solving KWTitles is a KWIC index of the list of titles Titles.
logic programming kwic(Titles,KWTitles)
algorithmic program debugging setof(Ys,Xs~(rnember(Xs,Titles),
rotate-and-f ilter(Xs ,Ys)) , ~ W T i t l e s .)
Output: algorithmic program debugging -,
debuggMg - algorithmic program, rotate-and-filter (Xs,Ys) -
Y s is a rotation of the list Xs such that
logic for problem solving -,
logic programming -,
the first word of Y s is significant and -
is inserted after the last word of Xs.
problem solving - logic for,
program debugging - algorithmic, rotate-and-filter(Xs,Ys) -
programming in prolog -, append(As, [Key lBsl ,Xs),
not insignificant(Key1,
programming - logic,
y , [ ' - ' IAsl ,Ys).
append( [ ~ e IBsl
prolog - programming in,
solving - logic for problem Vocabulary of insignificantw o r d s

Figure 16.3 Input and output for keyword in context (KWIC)problem


Testing and data

Declaratively, Y s is a rotation of X s if X s is composed of A s followed by titles(lp,[[logic,for,problem,solving],


[logic,programming] ,
B s , and Y s is Bs followed by As.
[algorithmic,program,debuggingl,
The next stage of development involves identifying single words as [programming,in,prologl I ) .
potential keywords. This is done by isolating the word in the first call
to append. Note that the new rule is an instance of the previous one: Program 16.7 Producing a keyword in context (KWIC) index

Operationally r o t a t e - a n d - f i l t e r considers all keys, filtering out the


Thls definition also improves the previous attempt by removing the du- unwanted alternatives. The goal order is important here to maximize
plicate solution when one of the split lists is empty and the other is the program efficiency.
entire list. In Program 16.7, the final version, a complementary view to recogniz-
The next improvement involves examining a potential keyword more ing keywords is taken. Any word Word is a keyword unless otherwise
closely. Suppose each keyword Word is identified by a fact of the form specified by a fact of the form i n s i g n i f i c a n t (Word). Further the proce-
keyword(Word). The solutions to the r o t a t e procedure can be filtered
dure is augmented to insert the end-of-title mark -, providing the con-
so that only words identified as keywords are accepted. The appropriate text information. This is done by adding the extra symbol in the second
a p p e n d call. Incorporating t h s discussion yields the clause for r o t a t e -
version is
and-f i l t e r in Program 16.7.
-
r o t a t e - a n d - f i l t e r (XS ,Ys) a p p e n d & , [Key l B s ] , X s ) 9 Finally, a set predicate is used to get all the solutions. Quantification
keyword(Key) , a p p e n d ( [Key 1 Bsl , A s ,Ys) . is necessary over all the possible titles. Advantage is derived from the
Chapter I6 Second-Order Programming

behavior of setof in sorting the answers. The complete program is given has-property ( [XI Xsl , P )
has-property ( [ 1 ,P) .
- P(X), has-property (Xs ,P) .
as Program 16.7, and is an elegant example of the expressive power of
Prolog. The test predicate is test_kwic/2.

Exercises for Section 16.2 Figure 16.4 Second-order predicates

(i) Modify Program 16.6 to handle other obstacles than rectangles.


order of the elements in Xs is preserved in Ys. We can use map-list
(ii) Adapt Program 16.7 for KWIC so that it extracts keywords from to rewrite some of the programs of earlier chapters. For example, Pro-
lines of text. gram 7.8 mapping English to French words can be expressed as map-
(iii) Modify rotation of a list so that it uses difference-lists. list (Words,dict ,Mots). Like has-property, map-list is easily defined
using a variable predicate name. The definition is given in Figure 16.4.
(iv) Write a program to find a minimal spanning tree for a graph. Operationally, allowing variable predicate names implies dynamic con-
Write a program to find the maximum flow in a network design struction of goals while answering a query. The relation to be computed
(v)
using the Ford-Fulkerson algorithm. is not fixed statically when the query is posed but is determined dynam-
ically during the computation.
Some Prologs allow the programmer to use variables for predicate
names, and allow the syntax of Figure 16.4. It is unnecessary to com-
16.3 Other Second-OrderPredicates plicate the syntax however. The tools already exist for implementing
second-order predicates. One basic relation is necessary, which we call
First-order logic allows quantification over individuals. Second-order apply; it constructs the goal with a variable functor. The predicate apply
logic further allows quantification over predicates. Incorporating this is defined by a set of clauses, one for each functor name and arity. For
extension into logic programming entails using rules with goals whose example, for functor f oo of arity n, the clause is
predicate names are variables. Predicate names become "first-class" data
objects to be manipulated and modified. apply (f oo,XI, . . . ,Xn) - foo (XI, . . . ,Xn)
A simple example of a second-order relation is the determination of The two predicates in Figure 16.4 are transformed into Standard Prolog
whether all members of a list have a certain property. For simplicity in Program 16.8. Sample definitions of apply clauses are given for the
the property is assumed to be described as a unary predicate. Let us examples mentioned in the text.
define has-property(Xs,P), which is true if each element of Xs has The predicate apply performs structure inspection. The whole collec-
some property P. Extending Prolog syntax to allow variable predicate tion of apply clauses can be generalized by using the structure inspec-
names enables us to define has-property as in Figure 16.4. Because has- tion primitive, univ. The general predicate apply (P,Xs) applies predi-
property allows variable properties, it is a second-order predicate. An cate P to a list of arguments Xs:
example of its use is testing whether a list of people Xs is all male with a
query has-property (Xs ,male)?. apply (F,Xs) -- Goal =. . [F I XS] , Goal.
Another second-order predicate is map-list (Xs , P ,Ys). Ys is the map
We can generalize the function to be applied from a predicate name, i.e.,
of the list xs under the predicate P. That is, for each element X of Xs
an atom, to a term parameterized by variables. An example is substitut-
there is a corresponding element Y of Ys such that P(X,Y) is true. The
ing for a value in a list. The relation substitute/4 from Program 9.3
C h a p t e r 16 Second-Order P r o g r a m m i n g

has-property(Xs,P) -
Each element in the list Xs has property P
Although possible both theoretically and pragmatically, the use of
lambda expressions and second-order constructs such as has-property
has-property( [X I Xsl ,PI ' and map-list is not as widespread in Prolog as in functional program-
apply(P,X), has-property(Xs,P). ming languages like Lisp. We conjecture that t h s is a combination of
has-property ( [ 1 ,PI . cultural bias and the availability of a host of alternative programming
apply (male, lo - male (XI. techmques. It is possible that the ongoing work on extending the logic
maplist (Xs,P,Ys) -
Each element in the list Xs stands in relation
programming model with hgher-order constructs and integrating it with
functional programming will change the picture.
P to its corresponding element in the list Ys. In the meantime, all-solutions predicates seem to be the main and most
map-list ( [XI XS] , P , [Y I Ys] )- useful higher-order construct in Prolog.
apply(P,X,Y), map-list(Xs,P,Ys)
map-list([ I ,P, 1 ) .
apply(dict,X,Y) - dict(X,Y).
Exercise for Section 16.3
Program 16.8 Second-order predicates in Prolog
(i) Write a program performing beta reduction for lambda expressions.

can be viewed as an instance of map-list if parameterization is allowed.


Namely, map-list (Xs,substitute(Old,New) ,Ys) has the same effect in
substituting the element New for the element Old in Xs to get Ys - exactly
the relation computed by Program 9.3. In order to handle this correctly, 16.4 Background
the definition of apply must be extended a little:
The discussion of f indall uses the description contained in the Stan-
apply(P,Xs) - dard Prolog document (Scowen, 1991). An excellent discussion of the
P =. . L1, append(LI,Xs,~2), Goal = . . L2, Goal all-solutions predicates bagof and setof in Edinburgh Prolog are given
IJsing apply as part of map-list leads to inefficient programs. For ex- in Warren (1982a). Discussions of "rolling your own" set predicates can
ample, using substitute directly rather than through map-list results be found in both O'Keefe (1990) and Ross (1989).
in far fewer intermediate structures being created, and eases the task Set predicates are a powerful extension to Prolog. They can be used (in-
of compilation. Hence these second-order predicates are better used in efficiently) to implement negation as failure and meta-logical type pred-
conjunction with a program transformation system that can translate icates (Kahn, 1984). If a goal G has no solutions, whch is determined by
second-order calls to first-order calls at compile-time. a predicate such as f indall, then not G is true. The predicate var (XI
The predicate apply can also be used to implement lambda expres- is implemented by testing whether the goal X=1; X=2 has tw7o solutions.
sions. A lambda expression is one of the form lambda(Xl,.. .,X,).Expres- Further discussion of such behavior of set predicates and a survey of dif-
sion. If the set of lambda expressions to be used is known in advance, ferent implementations of set predicates can be found in Naish (1985a).
they can be named. For example, the above expression would be replaced Further description of the Lee algorithm and the general routing prob-
by some unique identifier, f oo say, and defined by an apply clause: lem for VLSI circuits can be found in textbooks on VLSI, for example,
Breuer and Carter (1983). A neat graphic version of Program 16.6 has
apply (f 00, XI, . . . ,Xn) - Expression. been written by Dave Broderick.
Chapter 16

Recent logic programming research has focused somewhat more on


higher-order logic programming. Approaches of note are Lambda-Prolog
(Miller and Nadathur, 1986) and HiLog (Chen et al., 1989).
Interpreters
KWIC was posed as a benchmark for high-level programming languages
by Perlis, and was used to compare several languages. We find the Prolog
implementation of it perhaps the most elegant of all.
Our description of lambda expressions is modeled after Warren
(1982a). Predicates such as apply and map-list were part of the utili-
ties package at'the University of Edinburgh. They were fashionable for
a while but fell out of favor because they were not compiled efficiently,
and no source-to-source transformation tools were available.
Meta-programs treat other programs as data. They analyze, transform,
and interpret other programs. The writing of meta-programs, or meta-
programming, is particularly easy in Prolog because of the equivalence
of programs and data: both are Prolog terms. We have already presented
some examples of meta-programs, namely, the editor of Program 12.5
and the shell process of Program 12.6. This chapter co\,ers interpreters,
an important and useful class of meta-programs, and Chapter 18 dis-
cusses program transformation.

- - -

17.1 Interpreters for Finite State Machines

The sharp distinction between programs and data present in most com-
puter languages is lacking in Prolog. The equivalence of programs and
data greatly facilitates the writing of interpreters. We demonstrate the
facility in this section by considering the basic computation models of
computer science. Interpreters for the various classes of automata are
very easily written in Prolog.
It is interesting to observe that the interpreters presented in this sec-
tion are a good application of nondeterministic programming. The pro-
grams that are presented illustrate typical examples of don't-know non-
determinism. The same interpreter can execute both deterministic and
nondeterministic automata because of the nondeterminism of Prolog.

Definition
A (nondeterministic) finite automaton, abbreviated NDFA, is a 5-tuple
(Q,C,b,I,F), where Q is a set of states, C is a set of symbols, 6 is a
Chapter 1 7 Interpreters

accept (Xs) -
The string represented by the list Xs is accepted by
the NDFA defined by initial/l, delta/3, and final/l.
accept(Xs) - initial(Q1, accept(~s,Q).
accept( [XIXsl ,Q)
accept ( [ I ,Q) -- delta(Q,X,Ql), accept(Xs,Ql).
final (9). Figure 17.1 A simple automaton

Program 17.1 An interpreter for a nondeterministic finite automaton


(NDFA) initial(q0).
final (q0) .

mapping from Q x C to Q, I is an initial state, and F is a set of final


states. If the mapping is a function, then an NDFA is deterministic.
Program 17.2 An NDFA that accepts the language ( a b )*
A finite automaton can be specified as a Prolog program by three col-
lections of facts. The predicate i n i t i a l ( Q ) is true if Q is the initial state.
T h s automaton is nondeterministic because on receipt of an a in state q0
The predicate f i n a l (Q) is true if Q is a final state. The most interesting is
d e l t a ( Q , X , Q l ) ,which is true if the NDFA changes from state Q to state
it is not determined which path will be followed. Nondeterminism does
Q 1 on receipt of symbol X. Note that both the set of states and the set
not affect the interpreter in Program 17.1. All that is needed to produce
the new automaton is to add the fact d e l t a ( q 0 , a , q0) and the combined
of symbols can be defined implicitly as the constants that appear in the
i n i t i a l , f i n a l , and d e l t a predicates.
program will behave correctly.
An NDFA accepts a string of symbols from the alphabet C*, if when Another simple computation model is a pushdown automaton that ac-
cepts the class of context-free languages. Pushdown automata extend
started in its initial state, and following the transitions specified by 6, the
NDFA ends up in one of the final states. An interpreter for an NDFA must NDFAs by providing a single stack for memory in addition to the in-
ternal state of the automaton. Formally, a (nondeterministic) pushdown
determine whether it accepts given strings of symbols. Program 17.1 is
an interpreter. The predicate accept (Xs) is true if the NDFA defined automaton, abbreviated NPDA, is a 7-tuple (Q,C,G,G,I,Z,F)where Q, 1,I,
by the collection of i n i t i a l , f i n a l , and d e l t a facts accepts the string F are as before, G is the set of symbols that can be pushed onto the stack,
represented as the list of symbols X s . Z is the start symbol on the stack, and 6 is changed to take the stack into
Figure 17.1 shows a deterministic automaton that accepts the language account.
(ab)*.There are two states, qO and q l . If in state q0 an a is received, the Specifically, 6 is a mapping from Q x C x G* to Q x G*. The mapping
automaton moves to state q l . The automaton moves back from ql to q0 controls the change of state of the NPDA and the pushing and popping
if a b is received. The initial state is qO, and q0 is also the single final of elements onto and off the stack by the NPDA. In one operation, the
state. NPDA can pop (push) one symbol off (onto) the stack.
To use the interpreter, a specific automaton must be given. Program Analogously to an NDFA, an NPDA accepts a string of symbols from the
17.2 is the realization in Prolog of the automaton in Figure 17.1. The alphabet X*,if when started in its initial state and with the starting syrn-
combination of Programs 17.1 and 17.2 correctly accepts strings of al- bol on the stack, and following the transitions specified by 6, the NPDA
ternating a's and b's. ends up in one of the final states with the stack empty. An interpreter
If an arc from q0 to itself labeled a is added to the automaton in Fig- for an NPDA is given as Program 17.3. The predicate accept (Xs) is true
ure 17.1, we get a new automaton that recognizes the language (a(a*)b)*. if the NDFA defined by the collection of i n i t i a l , f i n a l , and d e l t a facts
Chapter 17 Interpreters

accept(Xs) -
The string represented by the list X s is accepted by
It is straightforward to build an interpreter for a Turing machine writ-
ten in a similar style to the interpreters in Programs 17.1 and 17.3. This
the NPDA defined by initial/l, delta/5,and final/l. is posed as Exercise (iii)at the end of this section. Building an interpreter
accept (Xs) - initial (9), accept ( X s ,Q , [ I ) . for Turing machines shows that Prolog has the power of all other known
computation models.

Program 17.3 An interpreter for a nondeterministic pushdown automaton Exercises for Section 17.1
(NPDA)
(i) Define an NDFA that accepts the language a b * c .
(ii) Define an NPDA that accepts the language a n b n .

delta(qO,X,S,qO,[XIS]) (iii) Write an interpreter for a Turing machine.


delta(qO,X,S,ql,[XIS])
delta(qO,X,S,ql,S).
delta(ql,X,[XIS] ,ql,S)
17.2 Meta-Interpreters
Program 17.4 An NPDA for palindromes over a finite alphabet
We turn now to a class of especially useful interpreters. A meta-inter-
preter for a language is an interpreter for the language written in the
accepts the string represented as the list of symbols X s . The interpreter language itself. Being able to write a meta-interpreter easily is a very pow-
is very similar to the interpreter of an NDFA given as Program 17.1. The erful feature of a programming language. It gives access to the computa-
only change is the explicit manipulation of the stack by the d e l t a predi- tion process of the language and enables the building of an integrated
cate. programming environment. The examples in the rest of this chapter
A particular example of an NPDA is given as Program 17.4. T h s au- demonstrate the potential of meta-interpreters and the ease with which
tomaton accepts palindromes over a finite alphabet. A p a l i n d r o m e is a they can be written. In t h s section, we also examine issues in writing
nonempty string that reads the same backwards as forwards. Example meta-interpreters.
palindromes are noon, madam, and glenelg. Again, the automaton is Throughout the remainder of this chapter, the predicate s o l v e is used
specified by i n i t i a l , f i n a l , and d e l t a facts, and the sets of symbols for a meta-interpreter. A suitable relation scheme is as follows. The re-
being defined implicitly. The automaton has two states: q0, the initial lation solve(Goa1) is true if Goal is true with respect to the program
state when symbols are pushed onto the stack, and q l , a final state when being interpreted.
symbols are popped off the stack and compared with the symbols in the The simplest meta-interpreter that can be written in Prolog exploits the
input stream. When to stop pushing and start popping is decided nonde- meta-variable facility. It is defined by a single clause:
terministically. There are two d e l t a facts that change the state from q0
to q l to allow for palindromes of both odd and even lengths.
Programs 17.1 and 17.2 can be combined into a single program for T h s trivial interpreter is only useful as part of a larger program. For
recognizing the language (ab)*. Similarly, Programs 17.3 and 17.4 can be example, a version of the trivial interpreter forms the basis for the in-
combined into a single program for recognizing palindromes. A program teractive shell given as Program 12.6 and the logging facility given as
that can achieve this combination is given in Chapter 18. Program 12.7. In general, as we suggest here and see in more detail in
Chapter 17 Interpreters

solve( Goal) -
Goal is true given the pure Prolog program defined by clause/2.
solve (member (X , [a,b, c] ) )
clause (member (X, [a,b, cl ) ,B)
solve(true)
solve (true) .
solve((A,B))
solve(A) -
- solve(A), solve(B).
clause(A,B), solve(B).
true Output: X=a

solve(true)
Program 17.5 A meta-interpreter for pure Prolog clause(true,T) f
clause (member (X, [a,b, cl ,B)
solve (member (X, [b,c] 1)
Sections 17.3 and 17.4, meta-interpreters are useful and important be- clause (member (X, [b,cI ) ,B1)
cause of the easily constructed enhancements. solve(true)
The best known and most widely used meta-interpreter models the true Output: x=b
computation model of logic programs as goal reduction. The three
solve (true)
clauses of Program 17.5 interpret pure Prolog programs. Thls meta-
clause(true ,T) f
interpreter, called vanilla, together with its enhancements, is the basis of clause(member(X, [b,c] ) ,Bl)
the rest of this section and Section 17.3. solve (member (X, [c] ) )
The interpreter in Program 17.5 can be given a declarative reading. The clause (member (X, [c] ,B2)
solve fact states that the empty goal, represented by the constant true, solve (true)
is true. The first solve rule states that a conjunction (A,B) is true if A true Output: X=c
is true and B is true. The second solve rule states that a goal A is true if
there is a clause A - B in the interpreted program such that B is true. solve (true)
clause(true,T) f
We also give a procedural reading of the three clauses in Program clause (member (X, [cl ,B2))
17.5. The solve fact states that the empty goal, represented in Prolog by solve (member (X , [ 1 ) )
the atom true, is solved. The next clause concerns conjunctive goals. It clause (member (X, [ 1 ) ,B3) f
reads: "To solve a conjunction (A,B), solve A and solve B." The general no (more) solutions
case of goal reduction is covered by the final clause. To solve a goal,
Figure 17.2 Tracing the meta-interpreter
choose a clause from the program whose head unifies with the goal, and
recursively solve the body of the clause.
The procedural reading of Prolog clauses is necessary to demonstrate of the clauses appearing in the program. It is also responsible for giv-
that the meta-interpreter of Program 17.5 indeed reflects Prolog's choices ing different solutions on backtraclung. Backtracking also occurs in the
of implementing the abstract computation model of logic programming. conjunctive rule reverting from B to A.
The two choices are the selection of the leftmost goal as the goal to Tracing the meta-interpreter of Program 17.5 solving a goal is instruc-
reduce, and sequential search and backtraclung for the nondeterministic tive. The trace of answering the query solve (member (X, [a,b, cl ) ) with
choice of the clause to use to reduce the goal. The goal order of the body respect to Program 3.12 for member is given in Figure 17.2.
of the solve clause handling conjunctions guarantees that the leftmost The vanilla meta-interpreter inherits Prolog's representation of clauses
goal in the conjunction is solved first. Sequential search and backtracking using the system predicate clause. Alternative representations of
comes from Prolog's behavior in satisfying the clause goal. clauses are certainly possible, and indeed have been used by alter-
The hard work of the interpreter is borne by the thlrd clause of Pro- native Prologs. Lists are one possible representation. The clause A -
gram 17.5. The call to clause performs the unification with the heads B I ,B 2 , . . . ,Bn can be represented by the clause rule(A, CBI,. . . ,Bn] ). In
Chapter 17 Interpreters

solve( Goal) - builtin(A is B). builtin(A > B).


Goal is true given the pure Prolog program defined by clause/2. builtin(read(X1) . builtin(write(X)) .
solve(Goa1) -solve (Goal, [ 1 ) .
builtin(integer(X1).
builtin(clause(A,B)).
builtin(functor(T,F,N)).
builtin(builtin(X)).
solve([ I,[ I).
solve ( [ ] , [GI Goals] ) - solve (G ,Goals) . Figure 17.3 Fragment of a table of builtin predicates
-
solve([AIB],Goals)
solve(A,Goals) - append(B,Goals,Goalsl), solve(A,Goalsl).
rule(A,B), solve(B,Goals).

Program 17.6 A meta-interpreter for pure Prolog in continuation style


predicate. Figure 17.3 gives part of that table. A table of builtin predi-
cates is provided in some Prologs by another name but is not present in
this representation, the empty list represents the empty goal and list Standard Prolog.
construction represents conjunction. This representation is used in Pro- The clause solve (A) - builtin(A), A. can be added to the meta-
gram 17.6. interpreter in Program 17.5 to correctly handle builtin predicates. The
A different representation imposes a different form on the meta- resulting program handles four disjoint cases, one per clause, for solving
interpreter, as illustrated in Program 17.6. Unlike Program 17.5, this goals: the empty goal, conjunctive goals, builtin goals, and user-defined
version of the vanilla meta-interpreter makes explicit the remaining goals goals. For compatibility with a number of Prolog systems, the meta-
in the resol\,ent. Enhancements can be written to exploit the fact that the interpreters in the rest of this section contain cuts to indicate that the
resolvent is accessible during the computation, for example, allowing a clauses are mutually exclusive.
more sophisticated computation rule. The behavior of Program 17.6 can The extra solve clause makes the behavior of the builtin predicates in-
be considered as being in continuation style promoted by languages such visible to the meta-interpreter. User-defined predicates that one wants to
as Scheme. make invisible can be handled similarly with a single clause. Conversely,
Differences in meta-interpreters can be characterized in terms of their there are occasions when builtin predicates for negation and second-
granularity, that is the chunks of the computation that are made acces- order programming should be made visible.
sible to the programmer. The granularity of the trivial one-clause meta- The vanilla meta-interpreter needs to be extended to handle cuts cor-
interpreter is too coarse. Consequently there is little scope for applying rectly. A naive incorporation of cuts treats them as a builtin predicate,
the meta-interpreter. It is possible, though not as easy, to write a meta- effectively adding a clause solve ( ! - ! . T h s clause does not acheve
the correct behavior of cut. The cut in the clause commits to the current
interpreter that models unification and backtraclung. The granularity of
such a meta-interpreter is very fine. Working at this fine level is usually solve clause rather than pruning the search tree.
not worthwhile. The efficiency loss is too great to warrant the extra ap- To achieve correct behavior of cut in a meta-interpreter, one needs to
plications. The meta-interpreter in Program 17.5, at the clause reduction understand scope, that is to which clause the cut commits. The scope of
level, has the granularity most suited for the widest range of applica- cut, as described in Chapter 11, is the clause in whch the cut is a goal
tions. in the body. The scope of cut when it is contained withn a meta-logical
The vanilla meta-interpreter must be extended to handle language fea- builtin predicate such as conjunction and disjunction is less distinct and
tures outside pure Prolog. Builtin predicates are not defined by clauses varies in different Prologs. If a cut is part of a disjunction, should ex-
in the program and need different treatment. The easiest way to incor- ecution of the cut commit to the current disjunct or to the clause in
porate builtin predicates is to use the meta-variable facility to call them which the disjunction is embedded? Handling cut correctly in a meta-
directly. A table of builtin predicates is necessary. In this chapter, we interpreter is tricky and usually relies on technical details of the scope of
assume a table of facts of the form builtin(Predicate) for each builtin cut in a particular implementation of Prolog. Incorporating cuts withn
Chapter 17 Interpreters

solve-trace ( Goal) - solve( Goa1,Tree) -


Goal is true given the Prolog program defined by clause/2. Tree is a proof tree for Goal given the program defined
The program traces the proof by side effects. by clause/2.
solve(true, true) - !.
-
solve-trace(true,Depth) -
!.
solve((A,B),(ProofA,ProofB))

- ! , solve(A,ProofA), solve(B,ProofB).

--
solve-trace((A,B) ,Depth) solve(A,(A-builtin)) builtin(A), ! , A.
.
! , solve-trace(A,Depth) , solve-trace(~,~epth)
solve-trace(A,Depth) -
builtin(A), ! , A , display(A,Depth), nl.
solve(A,(A-Proof)) clsuse(A,B), solve(B,Proof).

Program 17.8 A meta-interpreter for building a proof tree


solve-trace (A,Depth) -
clause(A,B) , display(A,Depth) , nl, Depth1 is Depth + 1,
solve-trace(B,Depthl).
There is subtlety in the goal order of the clause
display(A,Depth) -
Spacing is 3*Depth, put-spaces(Spacing), write(A). solve-trace (A,Depth) -
put-spaces (N) -
between(l,N,I), put-char(' '1, fail
( A , B) , display (A, Depth) , nl, Depth1 is
solve-trace (B ,Depthl) .
+ 9

put-spaces(N) .
The display goal is between calls to clause and solve-trace, ensuring
that the goal is displayed each time Prolog backtracks to choose another
Program 17.7 A tracer for Prolog clause. If the clause and display goals are swapped, only the initial call
of the goal is displayed.
meta-interpreters has been widely studied, and references to solutions Using Program 17.7 for the query solve-trace (append(Xs ,Ys,[a,b,
are given in Section 17.5. C] ) ) ? with Program 3.15 for append generates a trace like the one pre-

We apply meta-interpreters to develop a simple tracer. Program 17.7 sented in Section 6.1. The output messages and semicolons for alterna-
handles success branches of computations and does not display failure tive solutions are provided by the underlying Prolog. There is only one
nodes in the search tree. It is capable of generating the traces presented difference from the trace in Figure 6.2. The unifications are already per-
in Chapter 6. formed. Separating out unifications requires explicit representation of
The basic predicate is solve-trace (Goal,Depth), where Goal is unification and is considerably harder.
solved at some depth. The starting depth is assumed to be 0. The first A simple application of meta-interpreters constructs a proof tree while
solve_trace/2 clause in Program 17.7 states that the empty goal is solving a goal. The proof tree is built top-down. A proof tree is essen-
solved at any depth. The second clause indicates that each goal in a con- tial for the applications of debugging and explanation in the next two
junct is solved at the same depth. The t h r d clause handles builtins. The sections.
final solve_trace/2 clause matches the goal with the head of a program
clause, displays the goal, increments the depth, and solves the body of
The basic relation is solve (Goal ,Tree), where Tree is a proof tree
for the goal Goal. Proof trees are represented by the structure Goal -
the program clause at the new depth. Proof. Program 17.8 implements solve/2 and is a straightforward en-
The predicate display(Goa1 ,Depth) is an interface for printing the hancement of the vanilla meta-interpreter. We leave as an exercise for
traced goal. The second argument, Depth, controls the amount of inden- the reader giving a declarative reading of the program.
tation of the first argument, Goal. Level of indentation correlates with Here is an example of using Program 17.8 with Program 1.2. The query
depth in the proof tree. solve (son(1ot ,haran) ,Proof)? has the solution
C h a p t e r 17 Interpreters

solve( Goa1,Certainty) - solve( Goal,Certainty,Threshold) -


Certainty is our confidence, greater than Threshold, that Goal is true.
Certainty is our confidence that Goal is true.
solve(true, 1) !. -- solve(true,l,T)
solve((A,B),C,T)
-- !.
solve((A,B) ,C)
! , solve(A,Cl), solve(B,C2), minimum(Cl,C2,C). ! , solve(A,CI,T), s o l v e ( B , C 2 , T ) , minimrn(CI,C2,C).
solve(A,l)
solve(A,C)
--
builtin(A), ! , A.
clause-cf ( A , B , C I ) , solve(B,C2), C is C 1 * C2.
solve(A,I,T)
solve(A,C,T)
-
-
builtin(A), ! , A.

minimum(X,Y,z) - See Program 11.3. clause-cf ( A , B , C l ) , C1 > T , TI is T / C 1 ,


s o l v e ( B , C 2 , T l ) , C is C 1 * C2.
Program 17.9 A meta-interpreter for reasoning with uncertainty minirnum(X , Y ,Z) - See Program 11.3.
Program 17.10 Reasoning with uncertainty with threshold cutoff
-
Proof = ( s o n ( l o t , h a r a n )
((father(haran,lot) -true),
(male ( l o t ) - t r u e ) ) ) . exceeded, the computation continues. The new threshold is the quotient
The query s o l v e (son (X ,h a r a n ) , P r o o f ) ? has the solution X=lot and the of the previous threshold by the certainty of the clause.
same value for Proof.
Our next enhancement of the vanilla meta-interpreter incorporates a Exercises for Section 17.2
mechanism for uncertainty reasoning. Associated with each clause is a
certainty factor, which is a positive real number less than or equal to 1. (i) Write a meta-interpreter to count the number of times a procedure
A logic program w i t h certainties is a set of ordered pairs ( C l a u s e , F a c t o r ) , is called in a successful computation.
where Clause is a clause and F a c t o r is a certainty factor.
The simple meta-interpreter in Program 17.9 implements the un- (ii) Write a meta-interpreter to find the maximum depth reached in a
certainty reasoning mechanism. The program is a straightforward en- computation.
hancement of the vanilla meta-interpreter. The top-level relation is (iii) Extend Program 17.6 to give a tracer and build a proof tree.
s o l v e (Goal, C e r t a i n t y ) , whlch is true when Goal is satisfied with cer-
tainty C e r t a i n t y . (iv) Extend Program 17.7 for s o l v e _ t r a c e / 2 to print out failed goals.
The meta-interpreter computes the combination of certainty factors in (v) Modify Program 17.8 to use a different representation for a proof
a conjunction as the minimum of the certainty factors of the conjuncts. tree.
Other combining strategies could be accommodated just as easily. Pro-
gram 17.9 assumes that clauses with certainty factors are represented
- - - -
using a predicate clause-cf ( A , B , CF) . - - - -

Program 17.9 can be enhanced to prune computations that do not 1 7.3 Enhanced Meta-Interpreters for Debugging
meet a desired certainty threshold. An extra argument constituting the
value of the cutoff threshold needs to be added. The enhanced program Debugging is an essential aspect of programming, even in Prolog. The
is given as Program 17.10. The new relation is s o l v e (Goal, C e r t a i n t y , promise of high-level programming languages is not so much in the
Threshold). prospect for writing bug-free programs but in the power of the com-
The threshold is used in the fourth clause in Program 17.10. The cer- puterized tools for supporting the process of program development. For
tainty of any goal must exceed the current threshold. If the threshold is reasons of bootstrapping and elegance, these tools are best implemented
Chapter 17 Interpreters

in the language itself. Such tools are programs for manipulating, analyz- solve(A,D,Overflow) -
ing, and simulating other programs, or in other words, meta-programs. A has a proof tree of depth less than D and
T h s section shows meta-programs for supporting the debugging Overflow equals no-overflow, or A has a
branch in the computation tree longer than D, and
process of pure Prolog programs. The reason for restricting ourselves Overflow contains a list of its first D elements.
to the pure part is clear: the difficulties in handling the impure parts of
the language.
solve(true,D,no~overflow) -!.
solve(A,0,overflow([ 1 ) )
To debug a program, we must assume that the programmer has some -
solve ( (A,B) ,D, Overf low)
!.
+

intended behavior of the program in mind, and an intended domain of D > O , !,


application on which the program should exhibit t h s behavior. Given solve(A,D,OverflowA),
those, debugging consists of finding discrepancies between the pro-
gram's actual behavior and the behavior the programmer intended.
solve(A,D,no-overflow)
D > 0,
-
solve~conjunction(0verflowA,B,D,Overflow).

Recall the definitions of an intended meaning and a domain from Sec- builtin(A), ! , A.
tion 5.2. An intended meaning M of a pure Prolog program is the set solve(A,D,Overflow)
D > 0,
-
of ground goals on which the program should succeed. The intended
domain D of a program is a domain on which the program should ter- clause(A,B),
Dl is D-1,
minate. We require the intended meaning of a program to be a subset of
solve(B,Dl,OverflowB),
the intended domain. return~overflow(0verflowB,A,0verflow).
We say that A, is a solution to a goal A if the program returns on a goal
solve~conjunction(overflow(S),B,D,overflow~S~~.
A its instance A , . We say that a solution A is true in an intended meaning
M if every instance of A is in M. Otherwise it is false in M.
solve~conjunction(no~overflow,B,D,Overflow~ -
solve(B,D,Overflow).
A pure Prolog program can exhibit only three types of bugs, given an return~overflow(no~overflow,A,no~overflow~.
intended meaning and an intended domain. When invoked on a goal A in return~overflow(overflow(S),A,overflow([AISl~~.
the intended domain, the program may do one of three thmgs:
Program 17.1 1 A meta-interpreter detecting a stack overflow
1. Fail to terminate
2. Return some false solution A 8
3. Fail to return some true solution A 8
succeeds if a solution is found without exceeding the predefined depth
We describe algorithms for supporting the detection and identification of of recursion, with Overflow instantiated to no-overf low. The call also
each of these three types of bugs. succeeds if the depth of recursion is exceeded, but in t h s case Over-
In general, it is not possible to detect if a Prolog program is nonter- flow contains the stack of goals, i.e., the branch of the computation tree,
minating; the question is undecidable. Second best is to assign some a whch exceeded the depth-bound D.
priori bound on the running time or depth of recursion of the program, Note that as soon as a stack overflow is detected, the computation
and abort the computation if the bound is exceeded. It is desirable to returns, without completing the proof. T h s is acheved by solve-
save part of the computation to support the analysis of the reasons for conjunction and return-overf low.
nontermination. The enhanced meta-interpreter shown in Program 17.11 For example, consider Program 17.12 for insertion sort. When called
achieves t h s . It is invoked with a call solve (A, D , Overf low), where A is with the goal solve (isort ( [2,21 ,Xs) ,6,Overflow), the solution re-
an initial goal, and D an upper bound on the depth of recursion. The call turned is
Chapter 17 Interpreters

isort (Xs,Ys) - isort(Xs,Ys) -


Buggy insertion sort.
Ys is an ordered permutation of Xs. Nontermination program.
isort ([XIXs] ,Ys) - isort(Xs,Zs) , insert(~,Zs,Ys). isort([XIXs],Ys)
isort(C 1,C I).
- isort(Xs,Zs), insert(X,Zs,Ys).
isort([ I,[ 1).
-
insert (X , [Y I Ysl , [X ,Y I Ysl ) insert(X, CY IYsl , [X,Y IYsl) -
X < Y.
insert(X, [YIYS], [YlZsl)
X 2 Y.
-
insert (X, [Y I Ysl , CY I Zsl
X 2 Y, insert(Y,[XIYsl,Zs) X > Y, insert(X,Ys,Zs).
insert(X,C I, [XI). insert(X,C ],[XI).

Program 17.12 A nonterminating insertion sort Program 17.13 An incorrect and incomplete insertion sort

The false clause in the program is


Xs = [2,2,2,2,2,21,
Overflow = overflow ( [
isort([2,21, [2,2,2,2,2,21),
and a counterexample to it is
insert (2,[21 , [2,2,2,2,2,21),
insert (2,121 , [2,2,2,2,21),
insert(2, Dl,[2,2,2,21),
insert (2,[21 , [2,2,21), Given a ground proof tree corresponding to a false solution, one can
insert(2,[21, [2,21> I1 find a false instance of a clause as follows: Traverse the proof tree in
postorder. Check whether each node in the proof tree is true. If a false
The overflowed stack can be further analyzed, upon return, to diagnose node is found, the clause whose head is the false node and whose body
the reason for nontermination. This can be caused, for example, by a is the conjunction of its sons is a counterexample to a clause in the
loop, i.e., by a sequence of goals G1,G2,. . .,G,, on the stack, where GI and program. That clause is false and should be removed or modified.
G, are called with the same input, or by a sequence of goals that calls The correctness of t h s algorithm follows from a simple inductive
each goal with increasingly larger inputs. The first situation occurs in the proof. The algorithm is embedded in an enhanced meta-interpreter,
preceding example. It is clearly a bug that should be fixed in the program. shown as Program 17.14.
The second situation is not necessarily a bug, and knowing whether the The algorithm and its implementation assume an oracle that can an-
program should be fixed or whether a larger machine should be bought swer queries concerning the intended meaning of the program. The or-
in order to execute it requires further program-dependent information. acle is some entity external to the diagnosis algorithm. It can be the.
The second type of bug is returning a false solution. A program can programmer, who can respond to queries concerning the intended mean-
return a false solution only if it has a false clause. A clause C is false ing of the program, or another program that has been shown to have
with respect to an intended meaning M if it has an instance whose body the same meaning as the intended meaning of the program under de-
is true in M and whose head is false in M. Such an instance is called a bugging. The second situation may occur in developing a new version of
counterexample to C . a program whle using the older version as an oracle. It can also occur
Consider, for example, Program 17.13 for insertion sort. On the goal when developing an efficient program (e.g., quicksort), given an ineffi-
isort ( [3,2,I] ,Xs) it returns the solution isort ( [3,2,11, [3,2,11) cient executable specification of it (i.e.,permutation sort), and using the
which is clearly false. specification as an oracle.
Interpreters
Chapter 17

false-solution (A,Clause) -
If A is a provable false instance, then
Clause is
When invoked with the goal f alse-solution(isort ( [3,2,11 ,X),C)
the algorithm e h b i t s the following interactive behavior:
a false clause in the program. Bottom-up algorithm.
false~solution(A,Clause)
solve(A,Proof),
- false~solution(isort(~3,2,1],X),c)?
Is the goal isort ( [ I , [ I ) true?
false~clause(Proof,Clause). true.
solve (Goal ,Proof) - See Program 17.8. Is the goal insert (I, [
true.
I , [I] ) true?
f alse-clause(true ,ok).
f alse-clause( (A,B) ,Clause)
f alse-clause (A,ClauseA) ,
- Is the goal isort ( [I1 , [I] ) true?
true.
check~conjunction(C1auseA,B,Clause). Is the goal insert (2, [I1 , [2,I]) true?
f alse-clause( (A-B) ,Clause)
false-clause(B,ClauseB),
- false.

check~clause(ClauseB,A,B,Clause). x = C3,2,11,
C = insert(2, [I], [2,1]) -2 2 1.

This returns a counterexample to the false clause.


check~clause(ok,A,B,Clause) - The proof tree returned by solve/2 is not guaranteed to be ground,
in contrast to the assumption of the algorithm. However, a ground proof
query-goal (A,Answer) ,
check~answer(Answer,A,B,Clause). tree can be generated by either instantiating variables left in the proof
check-clause((A1-B~),A,B,(A~-B~)). tree to arbitrary constants before activating the algorithm, or by request-
ing the oracle to instantiate the queried goal when it contains variables.
check-answer(true,A,B,ok).
check-answer(false.A,B,(A-B1))
extract-body (B,B1).
- Different instances might imply different answers. Since the goal of this
algorithm is to find a counterexample as soon as possible, the oracle
extract-body(true,true). should instantiate the goal to a false instance if it can.
extract-body ( (A-B) ,A) . One of the main concerns with diagnosis algorithms is improving their
extract-body(((A-B) ,Bs), (A,As))
query complexity, i.e., reducing the number of queries they require to
+

extract-body (Bs ,AS) .


query-goal(A,true)
builtin(A).
- diagnose the bug. Given that the human programmer may have to answer
the queries, this desire is understandable. The query complexity of the
query-goal(Goal,Answer)
not builtin(Goal1,
- preceding diagnosis algorithm is linear in the size of the proof tree.
There is a better strategy, whose query complexity is linear in the depth
writeln(['Is the goal ',Goal,' true?']), of the proof tree, not its size. In contrast to the previous algorithm,
read(Answer). which is bottom-up, the second algorithm traverses the proof tree top-
Program 17.14 Bottom-up diagnosis of a false solution down. At each node it tries to find a false son. The algorithm recurses
with any false son found. If there is no false son, then the current node
constitutes a counterexample, as the goal at the node is false, and all its
sons are true.
The implementation of the algorithm is shown in Program 17.15. Note
the use of cut to implement implicit negation in the first clause of false-
goal/2 and the use of query_goal/2 as a test predicate.
Chapter 17 Interpreters

false-solution (A,Clause) - the node at the splitting point. If the node is false, the algorithm is
applied recursively to the subtree rooted by this node. If the node is
If A is a provable false instance, then Clause
is a false clause in the program. Top-down algorithm. true, its subtree is removed from the tree and replaced by true, and a
false-solution(A,Clause) - new middle point is computed. The algorithm can be shown to require
solve (A,Proof), a number of queries logarithmic in the size of the proof tree. In case of
false~goal(Proof,Clause). close-to-linear proof trees, this constitutes an exponential improvement
solve (Goal ,Proof) - See Program 17.8. over both the top-down and the bottom-up diagnosis algorithms.
false-goal( (A-B) ,Clause) -
false~conjunction(B,Clause), ! .
The third possible type of bug is a missing solution. Diagnosing a
missing solution is more difficult than fixing the previous bugs. We say
fal~e_~oal((A-B),(A-B1)) - that a clause covers a goal A with respect to an intended meaning M if it
extract-body(B,B1).
has an instance whose head is an instance of A and whose body is in M.
false-conjunction(( (A-B) ,Bs) ,Clause) - For example, consider the goal insert (2,[I,31 ,Xs). It is covered by
q~er~-~o a flalse) , ! ,
(A,
the clause
false-goal((A-B),Clause).
false-conjunction( (A-B) ,Clause)
query-goal(A,false), ! ,
-
f alse-goal( (A-B) ,Clause).
false-conjunction((A,As),Clause) - of Program 17.13 with respect to the intended meaning M of the pro-
gram, since in the following instance of the clause
false-conjunction(As,Clause).
extract-body (Tree ,Body) - See Program 17.14.
query-goal (A,Answer) - See Program 17.14.
the head is an instance of A and the body is in M.
Program 17.15 Top-down diagnosis of a false solution
It can be shown that if a program P has a missing solution with respect
to an intended meaning M, then there is a goal A in M that is not covered
Compare the behavior of the bottom-up algorithm with the following by any clause in P. The proof of this claim is beyond the scope of the
trace of the interactive behavior of Program 17.15: book. It is embedded in the diagnosis algorithm that follows.
Diagnosing a missing solution imposes a heavier burden on the oracle.
f alse-solution(isort ( [3,2,I] ,x) ,c)?
Not only does it have to know whether a goal has a solution but it must
Is the goal isort ( [2,11 , [2,11) true?
also provide a solution, if it exists. Using such an oracle, an uncovered
false.
goal can be found as follows.
Is the goal isort ( [I] , [I1 ) true?
The algorithm is given a missing solution, i.e., a goal in the intended
true.
meaning M of the program P, for which P fails. The algorithm starts with
Is the goal insert (2,[I1 , [2,11) true?
the initial missing solution. For every clause that unifies with it, it checks,
false.
using the oracle, if the body of the clause has an instance in M. If there
X = C3,2,11, is no such clause, the goal is uncovered, and the algorithm terminates.
C = insert(2, [I], [2,1]) -- 2 2 1. Otherwise the algorithm finds a goal in the body that fails. At least one
There is a diagnosis algorithm for false solutions with an even better of them should fail, or else the program would have solved the body, and
query complexity, called divide-and-query. The algorithm progresses by hence the goal, in contrast to our assumption. The algorithm is applied
splitting the proof tree into two approximately equal parts and querylng recursively to thls goal.
Interpreters
Chapter 17

missing-solution (A,Goal) -
If A is a nonprovable true ground goal, then Goal is a Enter a true ground instance of
true ground goal that is uncovered by the program. (insert(l,[31, [1,31) - 1 2 3)
missing-solution((A,B) ,Goal) +!, if there is such, or 'no' otherwise
(not A, missing-solution(A,Goal) ;
no.
A, missing-solution(B,Goal)).
missing-solution(A,Goal)
clause(A,B) ,
- C = insert(1, [31, C1,31).

query-clause ( (A-B) ) , ! , The reader can verify that the goal insert (I, [31 , [I,31 ) is not covered
missing-solution(B,Goal).
missing-solution(A,A)
not system(A).
- by Program 17.13.
The three algorithms shown can be incorporated in a high-quality in-
query-clause(C1ause) -
writeln(CCEnter a true ground instance of ',Clause,
teractive program development environment for Prolog.

'if there is such, or "no" otherwise']),


read(Answer) , 17.4 An Explanation Shell for Rule-Based Systems
! , check-answer(Answer,Clause).

check-answer(no,Clause)
check-answer(Clause,Clause)
- -! , fail.
!.
The final section of this chapter presents an application of interpreters
check-answer(Answer,Clause) - to rule-based systems. An explanation shell is built that is capable of ex-
plaining why goals succeed and fail and that allows interaction with the
write ( ' Illegal answer' ) ,
! , query-clause(C1ause). user during a computation. The shell is developed with the methodology
of stepwise enhancement introduced in Section 13.3.
Program 17.16 Diagnosing missing solution The skeleton interpreter in this section is written in the same style as
the vanilla meta-interpreter and has the same granularity. It differs in
two important respects. First, it interprets a rule language rather than
An implementation of this algorithm is shown in Program 17.16. The Prolog clauses. Second, the interpreter has two levels to allow explana-
program attempts to trace the failing path of the computation and to find tion of failed goals.
a true goal whch is uncovered. Following is a session with the program: Before describing the interpreter, we give an example of a toy rule-
based system written in the rule language. Program 17.17 contains some
rules for placing a dish on the correct rack in an oven for baking. Facts
Enter a true ground instance of have the form fact (Goal). For example, the first fact in Program 17.17
(isort([2,1,31, [1,2,31) - states that dish1 is of type bread.
isort([1,3] ,XS),insert(2,Xs, C1,2,31)) Rules have the form rule (Head,Body ,Name), where Head is a goal,
if there is such, or "no" otherwise Body is (possibly) a conjunction of goals, and Name is the name of the
(isort([2,1,31, C1,2,3I) -
isort([l,3], [1,3l) ,insert(2, [1,3] [1,293]))
rule. Individual goals in the body are placed inside a unary postfix func-
tor is-true, for reasons to be explained shortly. Conjunctions in the
body are denoted by the binary infur operator &, whch differs from Pro-
Enter a true ground instance of
(isort([1,31,[1,3]) - isort([3],Ys),insert(l,~s,[1,3]))
log syntax. Operator declarations for & and is-true are given in Program
17.17. To paraphrase a sample rule, rule place1 in Program 17.17 states:
if there is such, or 'no' otherwise
Chapter 17 Interpreters

Rule base for a simple expert system for placing dishes in an oven. monitor (Goal) -
The predicates used in the rules are Succeeds if a result of yes is returned from solving Goal
place-in-oven(Dish,Kack) -
Dish should be placed in the oven at level R a c k for baking.
at the solve level, or when the end of the computation is reached.
-
pastry(Dish) - - Dish is a pastry.
m a i n - m e a l (Dish) Dish is a main meal.
monitor(Goa1)
monitor(Goa1).
solve(Goal,Result), filter(Resu1t).

slow-cooker (Dish)- Dish is a slow cooker.


filter(yes).
- fail.
type(Dish,Type)
size(Dish,Size)-
- Dish is best described as Type.
The size of Dish is Size.
% filter(no)
solve Goa1,Result) -
(
The rules have the form rule (Head,Body,Name) . Given a set of rules of the form rule(A,B,Name), Goal has
Result yes if it follows from the rules and no if it does not.
solve(A, yes) -fact (A).
solve(A,Result) -
rule(A,B,Name), solve-body(B,Result).
rule (place-in-oven(~ish,top) , solve(A,no).
pastry (Dish) is-true & size(Dish,small) is-true
rule (place-in-oven(Dish ,middle),
lac el). solve-body(A&B,Result) -
solve(A,ResultA), solve~and(ResultA,B,Result).
pastry (Dish) is-true & size(~ish,big) is-true ,place2) .
rule(place~in~oven(Dish,middle),main~meal(~ish is_true,~lace3).
solve-body(A is-true,Result) -
solve(A,Result).

rule (place-in-oven(Dish ,bottom),slow-cooker i s is-true , ~ l a c e 4.)


r ~ l e ( ~ a s t(Dish)
r ~ ,type(Dish,cake) is-true ,pastr~l).
rule (pastry (Dish) ,type(Dish,bread) is-true ,pastry2) . Program 17.18 A skeleton two-level rule interpreter
rule(main-meal(Dish),type(Dish,meat) is-true,main-meal).
rule(s1ow-cooker (Dish) ,type(Dish,milk-pudding is-true, slow-cooker) .

The second reason is to show by example that the best way to develop
a rule-based application in Prolog is to design a rule language on top of
Program 17.1 7 Oven placement rule-based system Prolog. Although the rule language is largely syntactic sugar, experience
has shown that users of a rule-based system are happier worlung in a
customized rule language than in Prolog. Rule languages are straightfor-
"A dish should be placed on the top rack of the oven if it is a pastry and ward to proi7ideon top of Prolog.
its size is small." We now start our presentation of the explanation shell. According to
Why use a separate rule language when the syntax is so close to Prolog? the method of stepwise enhancement, the skeleton constituting the basic
The first rule, placel, could be written as follows. control flow of the final program is presented first. Program 17.18 con-
tains the skeleton of the rule interpreter. The principal requirement that
place-in-oven(Dish, top) - pastry(Dish) size(Dish9 shaped the skeleton is the desire to handle both successful and failed
computations in one interpreter.
There are two main reasons for the rule language. The first is pedagog- The rule interpreter presented in Program 17.18 has two levels. The top
ical. The rule interpreter is neater, avoiding complicated details associ- level, or monitor level, consists of the predicates monitor and filter.
ated with Prolog's impurities such as the behavior of builtin predicates The bottom level, or solve level, consists of the predicates solve, solve-
when called by clause. Avoiding Prolog's impurities also makes it easier body, and solve-and. Two levels are needed to correctly handle failed
to partially evaluate the interpreter, as described in Chapter 18. computations.
Chapter 17 Interpreters

Let us consider the bottom level first. The three predicates consti- solve( Goa1,Result) -
Given a set of rules of the form rule(A,B,Name), Goal has
tute an interpreter at the same level of granularity as the vanilla meta-
interpreter. There is one major difference. There is a result variable that Result yes if it follows from the rules and no if it does not.
The user is prompted for missing information.
says whether a goal succeeds or fails. A goal that succeeds, with the re-
-
sult variable indicating failure, instead of failing gives rise to a different
control flow, compensated for by the top level.
solve(A,yes)
solve (A,Result)
solve(A,Result)
--
fact(A).
rule ( A ,B,Name), solve-body(B ,Result).
askable(A1, solve-askable(A,Result).
The predicate solve(Goal,Result) solves a single goal. There are solve(A,no).
three cases. The result is yes if the goal is a fact in the rule base. The
result is no if no fact or head of a rule matches the goal. If there is a
solve-body(A&B,Result) -
solve-body(A,ResultA), solve-and(ResultA,B,Result).
rule that matches the goal, the result will be returned by the predicate solve-body(A is-true,Result) -
solve(A,Result).
solve-body (Goal ,Result). The order of the thlrd clause is significant solve-and(no,A,no).
because we only want to report no for an individual goal if there is no solve-and(yes,B,Result) - solve(B,Result).
suitable fact or rule. Effectively, solve succeeds for each branch of the solve-askable(A,Result) -
search tree, the result being yes for successful branches and no for failed not known(A), ask(A,Response), respond(Response,A,Result).
branches. The following predicates facilitate interaction with the user.
solve_body/2has t~7oclauses handling conjunctive goals and goals of ask(A,Response) - display-query(A), read(Response).
respond(yes,A,yes) - assert(known-to-be-true(A)).
the form A is-true. The functor is-true is a wrapper that allow7s uni-
fication to distinguish between the two cases. A Prolog implementation respond(no,A,no) - assert(known-to-be-false(A)).
with indexing would produce efficient code. The clause handling con-
junctions calls a predicate solve_and/3,which uses the result of solving
the first conjunct to decide whether to continue. The code for solve-
and results in behavior similar to the behavior of solve-conjunction in
Program 1 7.1 1. Program 17.19 An interactive rule interpreter
The monitor level is essentially a generate-and-test program. The solve
level generates a branch of the search tree, and the test procedure f il-
ter accepts successful branches of the search tree, indicated by the re-
solve ( A , Result) - askable (A), solve-askable ( A , Result) .

sult being yes. Failed branches, i.e., ones with result no, are rejected. Note An alternative method of making the rule interpreter interactive is to
that the second clause for filter could simply be omitted. We leave it in define a new class of goals in the body. An additional solve-body clause
the program, albeit commented out, to make clear the later enhancement could be added, for example,
step for adding a proof tree.
The first enhancement of the rule interpreter makes it interactive. The
interactive interpreter is given as Program 17.19. The user is given the We prefer adding a solve clause and having a table of askable facts
opportunity to supply information at runtime for designated predicates. to embedding in the rules the information about whether a predicate
The designated predicates are given as a table of askable facts. For is askable. The rules become more uniform. Furthermore, the askable
example, a fact askable (type (Dish,Type) ) . appearing in the table information is explicit meta-knowledge, whlch can be manipulated as
would indicate that the user could ask the type of the dish. needed.
Interaction with the user is achieved by adding a new clause to the To complete the interactive component of the rule interpreter, code
solve level: for solve-askable needs to be specified. The essential components are
Chapter 17 Interpreters

displaying a query and accepting a response. Experience with users of monitor ( Goal) -
Succeeds if a result of yes is returned from solving Goal
rule-based systems shows that it is essential not to ask the same ques-
at the solve level, or when the end of the computation is reached.
tion twice. Users get very irritated telling the computer information they
feel it should know. Thus answers to queries are recorded using assert. monitor(Goa1) - solve(Goal,Result,[ 11, filter(Resu1t).
monitor(Goa1).
Program 17.19 contains appropriate code. Only the solve level is given.
The monitor level would be identical to Program 17.18.
Program 17.19 queries the user. The interaction can be extended to
filter(yes).
% f ilter(no) - fail.

allow the user also to query the program. The user may want to know solve( Goal,Result,Rules) -
Given a set of rules of the form rule(A,B,Name), Goal has
why a particular question is being asked. A facility for giving a why ex- Result yes if it follows from the rules and no if it does not.
planation is common in rule-based systems, the answer being the rule Rules is the current list of rules that have been used.
containing the queried goal in its body. In order to give this why explana-
tion, we need to extend the rule interpreter to carry the rules that have
solve(A,yes,Rules)
solve(A,Result,Rules)
--
fact(A).

been used so far. rule (A,B ,Name) , RulesB = [Name 1 Rules] ,


Program 17.20 is an enhancement of Program 17.18 that carries the list solve-body(B,Result,RulesB).
of rules that have been used in solving the query. All the predicates carry solve(A,no,Rules).
the rules as an extra argument. The rule list is initialized to be empty
in the first monitor clause. The rule list is updated in the second solve
clause when a new rule is invoked.
We now describe how the list of rules can be used to provide a why
explanation. A new respond clause needs to be added to Program 17.19.
The appropriate behavior is to display the rule, then prompt the user
again for the ansber to the query. Program 17.20 A two-level rule interpreter carrying rules

respond(why ,A, [Rule I Rules] ) -


display-rule (Rule) ,
ask(A, Answer) , respond(Answer ,A ,Rules) .
tree is a sequence of branches. Each branch is either a proof tree or a fail-
Repeated responses of why can be handled by giving the rule that ure branch that is like a proof tree. Program 17.18 can be enhanced to in-
invoked the current rule. The correct behavior is achieved by having corporate both cases. The enhanced program is given as Program 17.21.
the recursive respond goal use the rest of the rules. Finally, when there The solve level returns a branch of the search tree, and the monitor level
are no more rules to display, an appropriate response must be given. A keeps track of the failure branches since the last proof tree. The rela-
suitable respond clause is tion between the predicate solve/3 in Program 17.21 and solve/2 in
respond(why ,A,[ 1 ) - Program 17.18 is analogous to the relation between Programs 17.8 and
17.5.
writeln( ['NO more explanation possible'] ) ask(AsAnswer) 9

respond(Answer, A, [ 1 ) . Four predicates are added to the monitor level to record and remove
branches of the search tree. The fact 'search tree' (Proof) records
Now let us consider generating explanations of goals that have suc- the current sequence of branches of the search tree since the last suc-
ceeded or failed. The explanations will be based on the proof tree for cess. The predicate set-search-tree, called by the top-level monitor
successful goals and the search tree for failed goals. Note that a search goal, initializes the sequence of branches to the empty list. Similarly,
Chapter 17 Interpreters

monitor (Goa1,Proof) -
Succeeds if a result of yes is returned from solving Goal at the
The following predicates use side effects to record and remove
branches of the search tree.

--
solve level, in which case Proof is a proof tree representing the collect-proof(Proof) retract('search tree'(Proof)).
successful computation, or when the end of the computation is reached, store-proof(Proof)
in which case Proof is a list of failure branches since the last success. retract('search treeJ(Tree)),
-
monitor(Goa1,Proof)
set-search-tree, solve (Goal ,Result ,proof) , set-search-tree
reset-search-tree
--
assert('search treeJ([ProoflTreel)).
assert('search treeJ([ I ) ) .

-
filter(Result,Proof).
monitor(~oa1,Proof)
collect-proof (P) , reverse(P, [ 1 ,PI),
retract('search tree'(Proof)),
assert('search tree'([ I)).
Proof = failed(Goa1,Pl). - See Program 3.16.
--
reverse(Xs,Ys)
filter(yes,~roof) reset-search-tree.
filter(no,Proof) store-proof(Proof), fail. Program 17.2 1 (Continued)
solve( Goal,Result,Proof) -
Given a set of rules of the form rule(A,B,Name), Goal has
Result yes if it follows from the rules and no if it does not. reset-search-tree initializes the search tree but first removes the cur-
Proof is a proof tree if the result is yes and a failure branch rent set of branches. It is invoked by filter when a successful compu-
of the search tree if the result is no. tation is detected. The predicate store-proof updates the search tree,
while collect-proof removes the search tree. The failure branches are
reordered in the second clause for monitor/2.
solve (A,yes ,Tree) - fact (A) , Tree = fact (A) . Having generated an explanation, we now consider how to print it.
solve(A,Result,Tree) - The proof tree is a recursive data structure that must be traversed to
rule (A,B ,Name), solve-body (B ,Result ,proof),
Tree = A because B with Proof.
be explained. Traversing a recursive data structure is a straightforward
solve(A,no ,Tree) - exercise. Appropriate code is given in Program 17.22, and a trace of a
computation given in Figure 17.4.
not fact (A), not rule(A,B,Name) , Tree = no- match(^) .
solve~body(A&B,Result,Proof) - The explanation shell is obtained by combining the enhancements of
Programs 17.19, 17.20, and 17.21. The final program is given as Pro-
solve-body(A,ResultA,ProofA),
solve-and(ResultA,B,Result,ProofB), gram 17.23. Understanding the program is greatly facilitated by viewing
Proof = ProofA & ProofB. it as a sum of the three components.
solve-body(A is-true,Result,Proof) -
solve(~,~esult,Proof).

Exercises for Section 17.4

Program 17.21 A two-level rule interpreter with proof trees (i) Add the ability to explain askable goals to the proof explainer in
Program 17.22.
(ii) Add the ability to execute Prolog builtin predicates to the explana-
tion shell.
(iii) Write a two-level meta-interpreter to find the maximum depth
reached in any computation of a goal.
Chapter 17 Interpreters

explain(Goal) -
Explains how the goal Goal was proved.
place-in-oven(dish1 ,middle) is proved using the rule
IF pastry (dishl) AND size(dish1 ,big)
explain(Goa1) - monitor(Goal,Proof), interpret(Proof). THEN place-in-oven(dish1,middle)
monitor (Goal,Proof) - See Program 17.21. pastry (dishl) is proved using the rule

interpret(ProofA&ProofB) -
interpret(ProofA), interpret(Pro0fB).
IF type(dish1,bread)
THEN pastry (dishl)
interpret(failed(A,Branches)) +
type(dish1,bread) is a fact in the database
nl, writeln([A,' has failed with the following failure size (dishl ,big) is a fact in the database.
branches : 'I ) , X =middle ;
interpret(Branches).
interpret ( [Fail I Fails] ) - place-in-oven(dish1 ,XI has failed with the following failure branches:
place-in-oven(dish1 ,middle) is proved using the rule
interpret(Fail), nl, write('NEW BRANCH'), nl,
interpret(Fai1s). IF main-meal(dish1)
interpret ( [ I). THEN place-in-oven(dish1,middle)
interpret (fact (A)) - main-meal(dish1) is proved using the rule

interpret(A because B with Proof) -


nl, writeln([A,' is a fact in the database.']). IF type(dish1,meat)
THEN main-meal (dish11
nl, writeln([A,' is proved using the rule']),
type(dish1 ,meat) has no matching fact or rule in the rule base
display-rule(rule(A,B)), interpret(Proof).
interpret(n0-match(A)) -
nl, writeln([A,' has no matching fact or rule in the rule base.']).
NEM' BRANCH
place-in-oven(dish1 ,low) is pro\,ed using the rulc
interpret(unsearched) - IF slow-cooker(dish1)
nl, writeln(['The rest of the conjunct is unsearched.']). THEN place-in-oven(dish1 ,low)
display-rule(ru1e (A,B)) - slow-cooker(dlsh1) is pro\.ed using the rule
write('1F ' ) , write-conjunction(B) , writeln( ['THEN ' ,A I). IF type(dish1,milk-pudding)
write-conjunction(A&B) -
write-conjunction(A), write(' AND ' ) ,
THEN slow-cooker(dishl)
type(dish1 ,milk-pudding) has no matching fact or rule in the rule base
write-conjunction(B).
write_conjunction(A is-true) -
write(A). Figure 17.4 Explaining a computation
writeln(Xs) - See Program 12.1
Program 17.22 Explaining a proof
Chapter 17 Interpreters

monitor (Goa1,Proof -
Succeeds if a result of yes is returned from solving Goal at the
The following predicates use side effects to record and remove
branches of the search tree.

--
solve level, in which case Proof is a proof tree representing the collect-proof(Proof) retract('search treeJ(Proof)).
successful computation, or when the end of the computation is reached, store-proof(Proof
in which case Proof is a list of failure branches since the last success. retract ('search tree' (Tree)),
monitor(Goa1,Proof) -
set-search-tree, solve(Goa1 ,Result,[ 1 ,Proof), set-search-tree --
assert('search tree'(CProoflTree1)).
assert('search tree'([ I)).
filter(Result,Proof).
monitor(Goa1,Proof) - reset-search-tree
retract('search tree'(Proof)), assert('search tree'([ I))
collect-proof(PI , reverse (P,[ I ,PI),
Proof = failed(Goa1,Pl).
reverse (Xs ,Ys) - See Program 3.16.
--
The following predicates facilitate interaction with the user.
filter(yes,Proof) reset-search-tree.
filter(no,Proof) store-proof(Proof), fail.
solve( Goal,Result,Rules,Proof -
Given a set of rules of the form rule(A,B,Name), Goal has
respond(yes,A,yes)
respond(no,A,no) -- assert(known-to-be-true(A)).

-
assert(known-to-be-false(A1).
respond(why ,A,[Rule 1 Rules] )
Result yes if it follows from the rules and no if it does not.
Rules is the current list of rules that have been used.
Proof is a proof tree if the result is yes and a failure branch respond(why ,A,C I ) -
display-rule(Rule), ask(A,Answer), respond(Answer,A,Rules).

writeln(CLNo more explanation possibleJ]), ask(A,Answer),


of the search tree if the result is no.
respond(Answer,A,[ I ) .
:- op(40,xfy,because).
:- op(30,xfy,with).
solve(A, yes ,Rules,Tree) - fact(A), Tree = fact (A).
solve(A,Result,Rules,Tree) -
rule(A,B,Name), RulesB = [NamelRules], display-rule(rule(A,B)) -
solve-body(B,Result,RulesB,Proof), write('1F '1, write-conjunction(B) , nl, writeln( ['THEN ' ,A]).
Tree = A because B with Proof.
solve(A,Result,Rules,Tree) - write-conjunction(A&B) -
write-conjunction(A), write(' AND ' ) , write-conjunction(B).
askable (A) , solve-askable(A ,Result,Rules) , Tree = user (A) .
- write-conjunction(A is-true) -write(A).
solve(A,no,Rules,Tree)
not fact (A) , not rule(A,~,Name), Tree = no-match(A) . writeln(Xs) - See Program 12.1.
solve~body(A&B,Result,Rules,Proof)
solve~body(A,ResultA,Rules,ProofA),
- Program 17.23 (Continued)
solve~and(ResultA,B,Result,Rules,ProofB),
Proof = ProofA & ProofB.
solve-body(A is-true,Result,Rules,Proof)
solve(A,Result,Rules,Proof).
-
solve~and(no,A,no,Rules,unsearched).
solve-and(yes,B,Result,Rules,Tree)
solve(B,Result,Rules,Tree).
-
Program 17.23 An explanation shell
3 54 Chapter 17 Interpreters

ward chaining inference engine. Early advocates of Prolog for expert sys-
1 7.5 Background tems were Clark and McCabe (1982),who discussed how explanation fa-
cilities and uncertainty can be added to simple expert systems expressed
Our notation for automata follows Hopcroft and Ullman (1979).
as Prolog clauses by adding extra arguments to the predicates. Incorpo-
There is considerable confusion in the literature about the term meta-
rating interaction with the user in Prolog was proposed by Sergot (1983).
interpreter-whether it differs from the term meta-level interpreter,
An explanation facility incorporating Sergot's query-the-user was part
for example. The lack of clarity extends further to the topic of meta-
of the APES expert system shell, described in Hammond (1984).
programming. A good discussion of meta-programming can be found in
Using meta-interpreters as a basis for explanation facilities was pro-
Yalqinalp (1991).
posed by Sterling (1984).Incorporating failure in a meta-interpreter has
One dimension of the discussion is whether the interpreter is capable
been discussed by several researchers, including Hammond (1984), Ster-
of interpreting itself. An interpreter with that capability is also called
ling and Lalee (1986),and Bruffaerts and Henin (1989). The first descrip-
meta-circular or self-applicable. An important early discussion of meta-
tion of an integrated meta-interpreter for both success and failure is in
circular interpreters can be found in Steele and Sussman (1978). That
Yalqinalp and Sterling (1989). The rule interpreter given in Section 17.4
paper claims that the ability of a language to specify itself is a funda-
is an adaptation of the last paper. The layered approach can be used
mental criterion for language design.
to explain cuts clearly, as in Sterling and Yalqinalp (1989), and also for
The vanilla meta-interpreter is rooted in Prolog folklore. A version
uncertainty reasoning, as in Yal~inalpand Sterling (1991) and more com-
was in the suite of programs attached to the first Prolog interpreter
pletely in Yalqinalp (1991).
developed by Colmerauer and colleagues, and was given in the early
collection of Prolog programs (Coelho et al., 1980). Subsequently, meta-
interpreters, and more generally meta-programs, have been written to
affect the control flow of Prolog programs. References are Gallaire and
Lasserre (1982), Pereira (1982), and Dincbas and Le Pape (1984). Using
enhanced meta-interpreters for handling uncertainties is described by
Shapiro (1983~).
There have been several papers on handling cuts in meta-interpreters.
A variant of the vanilla meta-interpreter handling cuts correctly is de-
scribed in Coelho et al. (1980) and attributed to Luis Pereira. One easy
method to treat cuts is via ancestor cut, whlch is only present in a few
Prologs like Waterloo Prolog on the IBM and Wisdom Prolog, described
in the first edition of this book. There is a good discussion of meta-
interpreters in general, and cuts in particular, in O'Keefe (1990).
Shapiro suggested that enhanced meta-interpreters should be the basis
of a programming environment. The argument, along with the debugging
algorithms of Section 17.3, can be found in Shapiro (1983a). Shapiro's
debugging work has been extended by Dershowitz and Lee (1987) and
Drabent et al. (1989).
Prolog is a natural language for building rule-based systems. The basic
statements are rules, and the Prolog interpreter can be viewed as a back-
Program Transformation

As stated in the introduction to Chapter 17, meta-programming, or the


writing of programs that treat other programs as data, is particularly
easy in Prolog. This chapter gives examples of programs that transform
and manipulate Prolog programs. The first section looks at fold/unfold,
the operation that underlies most applications of program transfor-
mation for Prolog programs. The transformations given in Chapter 1 5
for using difference-lists to avoid explicit concatenation of lists can be
understood as unfold operations, for example. The second section de-
scribes a simple system for controlled unfolding and folding, which is
especially good for removing layers of interpretation. The final section
gives two examples of source-to-sourcetransformation by code wallung.

18.1 Unfold/Fold Transformations

Logic programming arose from research on resolution theorem proving.


The basic step in the logic programming computation model, goal re-
duction, corresponds to a single resolution between a query and a pro-
gram clause. Unfold/fold operations correspond to resolution between
two Horn clauses. Loosely, unfolding corresponds to replacing a goal in
the body of a clause by its definition, while folding corresponds to recog-
nizing that goal(s) in the body of a clause are an instance of a definition.
These two operations, being so similar, are often discussed together.
We demonstrate unfolding and folding with a running example in the
first part of thls chapter. The example is specializing the interpreter for
nondeterministic pushdown automata (Program 17.3) for the particular
Chapter 18 P r o g r a m Transformation

pushdown automaton for recognizing palindromes (Program 17.4). In palindrome(Xs) -


The string represented by the list Xs is a palindrome.
general, specializing interpreters is a good application for unfold/fold
operations.
Definition
Unfolding a goal Bi in a clause A - B1,. . .,B, with respect to a clause B
palindrorne(CXlXsl,push,S)
palindrome ( [X I Xsl ,push,S) ---
palindrome(Xs,push,CXlS1).
palindrome (Xs ,pop, [XI Sl ) .
palindrome (Xs ,pop ,S) .
- C,,. . .,C, where B and Bi unify with mgu 8 , produces a clause (A -
palindrome ( [XIXs] ,push,S)
palindrome(CXIXsl,pop,[XISl)
palindrome ( [ 1 ,pop, C 1 ) .
-
palindrorne(Xs,pop,S).
Bl,. . ,,Bi-l,Cl,. . .,Cm,Bi+1,. . .,Bn)8.
As an example of unfolding, we specialize the clause accept (Xs) - Program 18.1 A program accepting palindromes
initial (4), accept (Xs,4, [ ] ) to a particular initial state by unfold-
ing the initial(Q) goal with respect to a particular initial fact. Specif-
ically, unfolding with respect to the fact initial (push) produces the
clause accept (Xs) -
accept (Xs ,push, [ 1 ) . (Note that in our running
This example shows variable bindings being propagated both to the
right, and to the head of the clause left of the goal being unfolded.
example we use the states push and pop for qO and ql, respectively, from
Folding is the reverse of unfolding. The occurrence of a body of a
the NPDA of Program 17.4.)
clause is replaced by its head. It is easiest to show with an example.
The effect of the unfolding is to instantiate the initial state for the
NPDA to push. In general, the effect of unfolding is to propagate variable Folding the goal accept (Xs ,push,[ I ) in the clause accept (Xs) - ac-
cept (Xs ,push, C I ) with respect to the clause palindrome (Xs ,State,
bindings to the right, as in this example, and also to the left, to goals in
Stack) - accept (Xs ,State,Stack) produces the clause accept (Xs)
the body of the clause and possibly also to the head.
There may be several clauses whose heads unify with a given goal in
- palindrome (Xs ,push, [ 1 ) .
Note that if we now unfold the goal palindrome (Xs ,push, C I ) in ac-
the body of a clause. We extend the definition of unfolding accordingly.
cept (Xs) - palindrome (Xs ,push, [ I ) with respect to the clause just
Definition used for folding, palindrome (Xs ,State,Stack) - accept (Xs ,State,
-
Unfolding a goal B, in a clause A BI,. . .,Bn with respect to a procedure Stack), we arrive back at the original clause, accept (Xs)
push, [ I 1. Ideally, fold/unfold are inverse operations.
-accept (Xs,
defining B, is to unfold the goal with respect to each clause in the proce-
dure whose head unifies with Bi. w Our example of folding used an iterative clause, i.e., one with a single
Unfolding the delta/5 goal in the clause accept( [XIXsl ,Q,S) - goal in the body. Folding can be performed on a conjunction of goals,
but there are technical difficulties arising from the scope of variables.
delta(Q ,X,S,Q1 ,S1) , accept (Xs ,Q1,S1) with respect to the following
Here we restrict ourselves to iterative clauses. The reader interested in
procedure for delta adapted from Program 17.4
the more general case should study the references given at the end of
delta(push,X,S,push, [XIS]). delta(push,X,S,pop, [XIS]). the chapter.
delta(push,X,S,pop,S). delta(pop,X, [XIS] ,pop,S). Specialization of the interpreter of Program 17.3 is completed by un-
folding the final (Q) goal in the t h r d clause of Program 17.3, folding all
produces four clauses, one for each fact. occurrences of accept/3, and folding with respect to the clause palin-
accept ( [XI Xsl ,push,S) - accept (Xs ,push,[XI S] ) . drome (Xs) - accept (Xs). Program 18.1 is then obtained.
accept ( [XI Xsl ,push,S) - accept (Xs ,pop,1x1 S1) . Propagating bindings leftward in Prolog will not preserve correctness
accept ( [X I Xsl
accept ( [XI Xsl
,push,S) --
,pop,CX I S1)
accept (Xs,pop, S) .
accept(~s,pop,S).
in general. For example, consider unfolding the goal r (X) with respect to
the fact r (3) in the clause p(X) -
var (X) , r (x) . The resulting clause,
Chapter 18 P r o g r a m Transformation

p (3) - var (3), clearly always fails, in contrast with the original clause. preduce ( Goa1,Residue) -
Partially reduce Goal to leave the residue Residue.
Unfolding for Prolog can be performed correctly by not propagating
bindings leftward, and replacing the unfolded goal by the unifier. For t h s preduce(true,true) - -!.
example, the result would be p(X) - var (XI , X=3. This will not be an preduce ((A ,B), (PA,PB))
preduce(A,B)
! , preduce(A,PA) , preduce (B,PB) .
should-fold(A,B), ! .
issue in the examples we consider.
-
+

preduce(A,Residue)
should-unfold(A), ! , clause(A,B), preduce(B,Residue).
Exercise for Section 18.1 preduce(A,A).

Program 18.2 A meta-interpreter for determining a residue


(i) Specialize the interpreter of Program 17.1 to the NDFA of Pro-
gram 17.2, or any other NDFA, by unfold/fold operations.
Program 18.2 contains code for preduce. There are three possibilities
for handling a single goal. It can be folded, unfolded, or left alone. The
question immediately arises how to decide between the three possibil-
18.2 Partial Reduction
ities. The easiest for a system is to rely on the user. Program 18.2 as-
sumes that the user gives should-f old (Goal,FoldedGoal) declarations
In this section we develop a simple system for controlled unfold/fold
that say which goals should be folded and to what they should be folded,
operations according to prescribed user declarations. Systems for con-
and also should-unf old(Goa1) declarations that say which goals should
trolled unfolding are known in the logic programming literature as par-
be unfolded. Unification against the program clauses determines to what
tial evaluators. This name reveals the influence of functional program-
they should be unfolded. Goals not covered by either declaration are left
ming, where the basic computation model is evaluation. We prefer to
alone. The remaining clauses in Program 18.2 handle the empty goal,
refer to the system in terms of the computation model of logic program-
true, and conjunctive goals, which are treated recursively in the obvious
ming, goal reduction. We thus, nonstandardly, say our system is doing
way.
partial reduction, and call it a partial reducer.
Observe that Program 18.2 is essentially a meta-interpreter at the gran-
Considerable research on applying partial reduction has shown that
ularity level of vanilla (Program 17.5). The meta-interpreter is enhanced
partial reduction is especially useful for removing levels of interpreta-
to return the residue. Handling builtins is assigned to the exercises.
tion. The sequence of unfold/fold operations given in Section 18.1 typify
The query preduce( (initial ( a ) , accept (Xs,Q, [ 1 ) ) , Residue)?
what is possible. The general NPDA interpreter was specialized to a spe-
assuming appropriate should-f old and should-unf old declarations (to
cific NPDA, removing interpreter overhead. The resulting program, Pro-
be given shortly) has as solution Residue = (true,palindrome (Xs ,
gram 18.1, only recognizes palindromes but does so far more efficiently
push, [ I ) 1. It would be preferable to remove the superfluous call to
than the combination of Programs 17.3 and 17.4.
true. This can be done by modifying the clause handling conjunctive
Let us see how to build a system that can apply the unfold and fold
goals to be more careful in computing the conjunctive resolvent. A suit-
operations that were needed to produce the palindrome program. The
able modification is
main idea is to recursively perform unfold/fold until no more "progress"
can be achieved. A relation that replaces a goal by its equivalent under preduce( (A,B) ,Res) +-

these operations is needed. The resulting equivalent goal is known as a ! , preduce (A,PA) , preduce (B ,PB) , combine (PA ,PB,Res) .
residue. Let us call our basic relation preduce (Goal,Residue), with in-
tended meaning that Residue is a residue arising from partially reducing The code for combine, removing superfluous empty goals, is given in
Goal by applying unfold and fold operations. Program 18.3.
Chapter 18 P r o g r a m Transformation

process (Program, R e d P r o g r a m ) - program(npda, [(accept (Xsl) - initial(Q11, accept (Xsl ,Ql,[ 1 ) ,


Partially reduce each of the clauses in P r o g r a m to produce (accept ( [X2 1 Xs21 ,Q2,S2)- delta(Q2,X2,S2,Q12,S12),
RedProgram. accept(Xs2,Ql2,S12)), (accept( [ 1 ,Q3,[ 1 ) -true)]).
process(Prog,NewProg) -
f indall (PC1 , (member (C1 ,Prog) ,preduce (C1,PC11 ) ,Newprog) .
should-unfold(initial(Q)).
should-unf old(f inal (Q)) .
test ( ~ a m ,Program)
e -
program(Name ,Clauses) , process (Clauses ,program)
should-unfold(delta(A,B,C,D,E)).
should-fold(accept (Q,Xs ,Ql) ,palindrome(Q,Xs ,Ql)1.
preduce ( Goa1,Residue) - should-f~ld(acce~t(~s),palindrome(~s)).
Partially reduce Goal to leave the residue Residue.
Program 18.4 Specializing an NPDA
preduce( (A - B) , (PA -
PB) ) -
! , preduce(B,PB), p r e d u c e ( A , ~ ~ ) .
preduce(true,true) -!.
preduce ((A,B) ,Res) - cleanest way to get the whole collection of program clauses is to use the
! , preduce (A,PA) , preduce (B,PB) , combine (PA,PB, ~ e s .) all-solutions predicate f indall. That gives
preduce(A,B) -sh~uld-fold(A,B), ! .
preduce(A,Residue) - process (Prog,NewProg) -
should-unf old(A) , ! , clause(A ,B) , reduce (B,~ e s i d u e )
f indall (PC1 , (member (C1 ,Prog) , preduce (C1 ,PC11 ) ,NewProg)
preduce(A,A).

Putting all the preceding actions together gives a simple system for
partial reduction. The code is presented as Program 18.3. The program
also contains a testing clause.
Program 18.3 '4 simple partial reduction system We now concentrate on how to specify should-fold and should-
unfold declarations. Consider the NPDA example for recognizing palin-
dromes. The initial, final, and delta goals should all be unfolded. A
To extend Program 18.2 into a partial reducer, clauses must be han-
declaration is needed for each. The accept/l and accept/3 goals should
dled as well as goals. We saw a need in the previous section to partially
be folded into palindrome goals with the same argument. The declara-
reduce the head and body of a clause. The only question is in which or- tion for accept/l is should-fold(accept (Xs) ,palindrome(Xs)). All
der. Typically, we will want to fold the head and unfold the body. Since the necessary declarations are given in Program 18.4. Program 18.4 also
unfolding propagates bindings, unfolding first will allow more specific
contains the test program as data. Note the need to make all the vari-
folding. Thus our proposed rule for handling clauses is ables in the program distinct. Applying Program 18.3 to Program 18.4 by
preduce ( (A -
B) , (PA PB)) - - posing the query test (npda,P)? produces Program 18.1, with the only
difference being an explicit empty body for the last palindrome fact.
! , preduce ( B , PB) , preduce (A, PA)
We now give a more complicated example of applying partial reduction
This goal order is advantageous for the example of the rule interpreter to remove a level of overhead. We consider a simpler variant of the rule
to be presented later in this section. interpreter given in Section 17.4. The variant is at the bottom level of
To partially reduce a program, we need to partially reduce each of its the layered interpreter. The interpreter, whose relation is solve (A,N) ,
clauses. For each clause, there may be several possibilities because of counts the number of reductions used in solving the goal A. The code
nondeterminism. For example, the recursive accept/3 clause led to four for solve and related predicate solve-body is given in Program 18.5.
rules because of the four possible ways of unfolding the delta goal. The The rules that we will consider constitute Program 17.17 for determining
Chapter 18 Program Transformation

Rule interpreter for counting reductions where a dish should be placed in the oven. The rules are repeated in
Program 18.5 for convenience.
The effect of partial reduction in t h s case will be to "compile" the
rules into Prolog clauses where the arithmetic calculations are done. The
resulting Prolog clauses can in turn be compiled, in contrast to the com-
bination of interpreter plus rules. Rule place1 will be transformed to
Sample rule base
rule(oven(Dish,top),pastry(Dish) is-true oven(Dish,top,N) -
& size(Dish,small) is-true,placel). pastry (Dish,NI), size(Dish, small ,N2),
rule(oven(Dish,middle),pastry(Dish) is-true N3 is Nl+N2, N is N3+1.
& size(Dish,big) is_true,place2).
rule(oven(Dish,middle),main-meal(Dish) is_true,place3).
rule(oven(Dish,bottom),slow~cooker(Dish) is_true,place4). The idea is to unfold the calls to rule so that each rule can be handled,
rule(pastry(Dish) ,type(Dish, cake) is-true ,~astryl). and also to unfold the component of the interpreter that handles syn-
r ~ l e ( ~ a s t r(Dish),
y type(Dish, bread) is_true,~astry2). tactic structure, specifically solve-body. What gets folded are the indi-
rule (main-meal (Dish), type(Dish,meat) is-true ,main-meal) vidual calls to solve, such as solve(oven(D, P) , N ) , whlch gets replaced
rule(slow~cooker(Dish),type(~ish,milk~pudding~ by a predicate oven (D,P , N) . The necessary declarations are given in Pro-
is-true,slow-cooker). gram 18.5. Program 18.3 applied to Program 18.5 produces the desired
should-f old(solve(oven(D,P) ,N) ,oven(D,P,N)) . effect.
should-f old(solve(pastry(D) ,N) ,pastry @,Ill). Specifying what goals should be folded and unfolded is in general
should~fold(solve(main~meal(~),~),main~meal~D,N~~.
straightforward in cases similar to what we have shown. Nevertheless,
should~fold(solve(slow~cooker(~),~),slow~cooker~D,N~~.
should-f old(solve(type(D,P) ,N) ,type(D,P,N)).
malung such declarations is a burden on the programmer. In many cases,
should-fold(solve(size(D,P) ,N), size(D,P,N) 1. the declarations can be derived automatically. Discussing how is beyond
the scope of the chapter.
How useful partial reduction is for general Prolog programs is an open
program(ru1e-interpreter, [(solve(~l,1)
(solve(A2,N) - -
fact (Al)),
rule(A2,B,Name), solve-body(B,NB), N is NB+1)1)
issue. As indicated, care must be taken when handling Prolog's impuri-
ties not to change the meaning of the program. Further, interaction with
Prolog implementations can actually mean that programs that have been
Program 18.5 Specializing a rule interpreter partially reduced can perform worse than the original program. It will be
interesting to see how much partial reduction will be applied for Prolog
compilation.

Exercises for Section 18.2

(i) Extend Program 18.3 to handle builtins.

(ii) Apply Program 18.3 to the two-level rule interpreter with rules
given as Program 17.20.
366 Chapter 18 Program Transformation

the two programs being composed, and one-to-one correspondences be-


18.3 Code Walking tween the clauses of each of the programs and the common skeleton.
Programs are represented as lists of clauses. The first clause in the first
The examples of meta-programming given so far in Chapters 17 and 18 program corresponds to the first clause in the second program and to
are dynamic in the sense that they "execute" Prolog programs by per- the first clause in the skeleton. Our assumption implies that the lists of
forming reductions. Prolog is also a useful language for writing static clauses of programs being composed have the same length. The three
meta-programs that perform syntactic transformations of Prolog pro- programs have been written with corresponding clauses in the same or-
grams. In t h s section, we give two nontrivial examples in whch pro- der. (That the lists 'of clauses do have the same length is not checked
grams are explicitly manipulated syntactically. explicitly.)
The first example of explicit program manipulation is program com- In order to perform composition, a composition specification is
position. In Section 13.3, stepwise enhancement for systematic construc- needed. It states how the arguments of the final program relate to
tion of Prolog programs was introduced. The t h r d and final step in the the two extensions. The relation that we will assume is composition-
method is composition of separate enhancements of a common skele- specification(Progrml,Progrm2,Skeleton,FinalProgram). An ex-
ton. We now present a program to acheve composition that is capable of ample of the specification for our running example is composition-
composing Programs 13.1 and 13.2 to produce Program 13.3. specif ication(union(Xs ,Ys,Us), common(Xs ,Ys, N ) , skel(Xs ,Ys),
The running example we use to illustrate the program is a variant of uc (Xs ,Ys ,Us,N) . The composition specification is given as part of Pro-
the example in Chapter 13. The skeleton is the same, namely, gram 18.6.
skel( [X 1 Xs] ,Ys) - member(X,Ys) , skel(Xs ,Ys). The program for composition is given as Program 18.6. The top-level
skel ( [X (Xs],Ys) - nonmember (X,Ys), skel(Xs ,Ys) . relation is compose/4, which composes the first two programs assumed
to be enhancements of the thlrd argument to produce the composite
skel([ 1 ,Ys).
program, which is the fourth argument.
The union program, Program 13.1, is also the same, namely, The program proceeds clause by clause in the top loop of Pro-
union( [X I Xs] ,Ys,Us) -- member (X,Ys) , u n i o n ( ~ s , ~ s , ~. s ) gram 18.6, where compose-clause/4 does the clause composition. The
union([XIXs] ,Ys, [XIUS]) -nonmember(X,~s), u n i o n ( ~ s , ~ s , ~ s ) . arguments correspond exactly to the arguments for compose. To com-
pose two clauses, we have to compose the heads and the bodies. Com-
union([ 1 ,Ys,Ys).
position of the heads of clauses happens through unification with the
The second program to be composed is different and represents when composition specification. The predicate compose-bodies/4 is used to
added goals are present. The relation to be used is common (Xs ,Ys ,N) , compose the bodies. Note that the order of arguments has been changed
whch counts the number of common elements N in two lists Xs and Ys. so that we systematically traverse the skeleton. Each goal in the skeleton
The code is must be represented in each of the enhancements so that it can be used
common( [XI Xs] ,Ys,N) - as a reference to align the goals in each of the enhancements.
member(X,Ys), common(Xs,Ys,M), N is M+1. The essence of compose-bodies is to traverse the body of the skeleton
common( [XI Xs] ,Ys,N) -
nonmember (X,Ys) , common(Xs ,Ys,N) . goal by goal and construct the appropriate output goal as we proceed.
common([ I ,Ys,O). In order to produce tidy output and avoid superfluous empty goals, a
The program for composition makes some key assumptions that can difference-structure is used to build the output body. The first clause
be justified by theory underlying stepwise enhancement. Describing the for compose-bodies covers the case when the body of the skeleton is
theory is beyond the scope of t h s book. The most important assump- nonempty. The predicates first and rest, which access the body of the
tion is that there is a one-to-one correspondence between the clauses of skeleton, are a good example of data abstraction.
Chapter 18 Program Transformation

compose(Program1,Program2,Skeleton,FinalProgram)
Finalprogram is the result of composing Program1 and
- An important assumption made by Program 18.6 concerns finding the
goals in the bodies of the program that correspond to the goals in the
Program2, whch are both enhancements of Skeleton. skeleton. The assumption made, embedded in the predicate correspond,
compose ( [Cll I Clsl] , [C12 1 Cls21 , [ClSkel I ~ l s ~ k e l, LC1
compose~clause(C1l,C12,ClSkel ,C1),
l -
1~1~1) is that a mapping will be given from goals in the enhancement to goals
in the skeleton. In our running example, the predicates member and non-
compose(Clsl,Cls2,C1sSkel,Cls). member map onto themselves, while both union and common map onto
compose([ I,[ I , [ I,[ I ) . skel. This information, provided by the predicate map/2, is needed to
cornpose-clause((~l-~1),(~2-~2),(~~kel-~~kel),(A-B))
cornposition~specification(A1,A2,ASkel,A),
- correctly align goals from the skeleton with goals of the program being
composed. The code for align as presented allows for additional goals
cornpose-bodies(BSkel,Bl,B2,B\true).
to be present between goals in the skeleton. The only extra goal in our
compose-bodies(SkelBody,Bodyl,Body2,B\BRest)
first (SkelBody,G) , ! ,
- running example is the arithmetic calculation in common, whch is after
align(G,Bodyl,Gl,RestB~dyl,B\Bl),
the goals corresponding to the skeleton goals.
align(~,~ody2,G2,~estBody2,~1\(Goal,B2)), The second clause for compose-bodies covers the case when the body
compose-goal (GI,G2 ,Goal) , is empty, either from dealing with a fact or because the skeleton has been
rest (SkelBody ,Gs), traversed. In this case, any additional goals need to be included in the
compose~bodies(~s,~est~odyl,~est~ody2,~2\~Rest~.
compose-bodies (true,Bodyl ,Body2 , ~ \ B R e s t ) - result. This is the function of rest-goals.
Program 18.7 contains a testing clause for Program 18.6,along with the
rest-goals (Bodyl ,B\Bl) , rest-goals (Body2 , ~ 1 \ ~ R e s .t )
specific data for our running example. As with Program 18.4, variables
align(Goal,Body,G,RestBody,B\B) -
f irst(Body ,GI, correspond(G,Goal) , ! , r e s t ( ~ o d,~~ e s t ~ o d y ) .
in the programs being composed must be named differently. Automatic
align(Goal,(G,Body),CorrespondingG,RestBod,G,\ - generation of composition specifications for more complicated examples
is possible.
align(Goal,Body,~orres~ondin~~,~est~ody,B\Bl).
The second example of explicit manipulation of programs is the con-
first(G,G) -
first((G,Gs),G).
G f (A,B), G f true. version of context-free grammar rules to equivalent Prolog clauses.
Context-free grammars are defined over a language of symbols, divided
rest ((G,Gs) ,Gs).
rest(G,true) -
G f (A,B). into nonterminal symbols and terminal symbols. A context-free grammar
correspond(G,G).
is a set of rules of the form
correspond(G,B)
compose-goal(G,G,G)
- map(G,B).
--
!.
(head) - (body)
compose-goal (A1 ,A2,A) where head is a nonterminal symbol and body is a sequence of one
! , composition~specification(~1,~2,ASkel,A). or more items separated by commas. Each item can be a terminal or
rest-goals(true,B\B) -
!.
rest-goals(Body,(G,B)\BRest) -
nonterminal symbol. Associated with each grammar is a starting symbol
and a language that is the set of sequences of terminal symbols obtained
first (Body ,GI, ! , rest (Body ,Body11 , rest-goals(~ody1, ~ \ ~ ~ e s t ) . by repeated (nondeterministic) application of the grammar rules starting
from the starting symbol. For compatibility with Chapter 19, nonterminal
Program 18.6 Composing two enhancements of a skeleton
symbols are denoted as Prolog atoms, terminal symbols are enclosed
w i t h lists, and [ ] denotes the empty operation.
The language a(bc)* can be defined by the following context-free gram-
mar consisting of four rules:
Chapter 18 Program Transformation

test-compose(X ,Prog) -
programl(X,Pr~gl), program2(~,~rog2),
skeleton(X,~keleton), compose(~rogl,~rog2,~keleton,~rog).
program1 (test,[
(union([xl Ixsll ,Ysl,Zsl) -
member(X1 ,Ysl), union(xs1 ,Ysl,Zsl)),
(union( [ ~ 1Xs21
2 -
,Ys2,CX2 1 Zs21)
nonmember(X2 ,Ys2), union(Xs2 ,Ys2,Zs2)),
(union( [ 1 ,Ys3,Ys3)
program2(test, [
- true)] ) .
Figure 18.1 A context-free grammar for the language a*b*c*

(cornmon([Xl IXsll ,Ysl,Nl) -


member(X1 ,Ysl) , common(xs1 ,Ysl,MI), N1 is ~1+1),
(common( CX2 I Xs21 ,Ys2,N2) - s(As\Xs) - a(As\Bs), b(Bs\Cs), c(Cs\Xs) .
a(Xs\Ys) - connect ( [a] ,Xs\Xsl) , a(xsl\Ys) .
nonmember(X2,Ys2), common(Xs2,Ys2,N2)),
(common([ 1 ,Ys3,0) - true)]). a(~s\Ys)
b(xs\Ys)
-- connect ( [ I ,Xs\Ys) .
connect( [b] ,xs\xsl), b(~sl\~s).
skeleton(test, [
(skel([Xl IXsl] ,Ysl) -- member(Xl,Ysl), skel(Xsl,~sl)), b(xs\Ys)
c(Xs\Ys)
-- connect( [ I ,Xs\Ys) .
connect( Ccl ,Xs\Xsl), c(Xsl\Ys).
(skel( [X2 1 Xs21 ,Ys2)
(skel( [ 1 ,Ys3)-
nonmember(x2,~~2),skel(~s2,~s2)),
true)] 1. c (XS\YS> - connect ( [ 1 ,Xs\Ys) .
connect ( [ I ,Xs\Xs).
composition-specification(union(~s,Ys,Us) , common(~s,YS,N) ,
skel(Xs,Ys),uc(Xs,Ys,Us,N)). connect ( [W I Ws] , [W I Xs1 \Ys) - .
connect (W~,XS\YS)

map(union(Xs,Ys,Zs), skel(Xs,~s)). Program 18.8 A Prolog program parsing the language a*b*c*
map(cornmon(Xs,Ys,N), skel(Xs,Ys)).

Program 18.7 Testing program composition


Completing the grammar of Figure 18.1 in the style of the previous
rule leads to a correct program for parsing, albeit an inefficient one.
s - [a], b .
The calls to append suggest, correctly, that a difference-list might be a
b - [ b ] , C.
more appropriate structure for representing the sequence of terminals
b - [ I .
in the context of parsing. Program 18.8 is a translation of Figure 18.1 to
c - [c], b .
a Prolog program where difference-lists represent the phrases. The basic
Another example of a context-free grammar is given in Figure 18.1. relation scheme is s(Xs), which is true if Xs is a sequence of symbols
This grammar recognizes the language a*b*c*. accepted by the grammar.
A context-free grammar can be immediately written as a Prolog pro- The predicate connect (Xs ,Ws) is true if the list X s represents the same
gram. Each nonterminal symbol becomes a unary predicate whose argu- sequence of elements as Ws. The predicate is used to make explicit the
ment is the sentence or phrase it identifies. The naive choice for repre- translation of terminal symbols to Prolog programs.
senting each phrase is as a list of terminal symbols. The first grammar As a parsing program, Program 18.8 is a top-down, left-to-right re-
rule in Figure 18.1 becomes cursive parser that backtracks when it needs an alternative solution. Al-
though easy to construct, backtraclung parsers are in general inefficient.
However, the efficiency of the underlying Prolog implementation in gen-
eral more than compensates.
P r o g r a m Transformation
Chapter 18

-
translate( G r a m m a r , P r o g r a m )
P r o g r a m is the Prolog equivalent of the context-free
the same functor is created. If the goal is a list of terminal symbols, the
appropriate connect goal is created. When executed, the connect goal
grammar G r a m m a r . connects the two difference-lists. Code for connect is in Program 18.8.
translate ( [Rule I Rules] , [ClauseI Clauses] ) - Program 18.9 can be extended for automatic translation of definite
translate~rule(Rule,Clause), clause grammar rules. Definite clause grammars are the subject of Chap-
translate(Rules,Clauses). ter 19. Most versions of Edinburgh Prolog provide such a translator.
translate( C I , [ 1 ) .
translate-rule( GrammarRule,PrologClause)
PrologClause is the Prolog equivalent of the
- grammar Exercise for Section 18.3
rule G r a m m a r R u l e .
translate-rule((Lhs - - -
R h s ) , ( ~ e a d Body)) (i) Apply Program 18.6 to one of the exercises posed at the end of
Section 13.3.
translate-head(Lhs ,Head,Xs\Ys) ,
translate-body (Rhs,Body ,Xs\Ys) .

18.4 Background
translate-body(A,B),(Al,Bl),Xs\Ys) -
! , translate-body(A,Al,Xs\Xsl), translate-b~d~(~,Bl,~sl\~s).
translate-body ( A ,A1 ,Xs) - Often research in logic programming has followed in the steps of related
research in functional programming. This is true for unfold/fold and par-
translate-goal(A,Al,Xs).
translate-goal(A,Al,DList) -
nonterminal(A1, functor (A1, A ,11, arg(l ,A1, ~ ~ i s t ) .
tial evaluation. Burstall and Darlington (1977) wrote the seminal paper
on unfold/fold in the functional programming literature. Their work was
translate~goal(Terms,connect(Terms,~),S)
terminals(Terms1.
- adapted for logic programming by Tamaki and Sato (1984).
The term partial evaluation may have been used first in a paper by
Lombardi and Raphael (19641, where a simple partial evaluator for Lisp
terminals(Xs) - .
list (Xs) was described. A seminal paper introducing partial evaluation to com-
list ( X S ) - see Program 3.11. puter science is due to Futamura in 1971, who noted the possibility
of compiling away levels of interpretation. Komorowski described the
Program 18.9 Translating grammar rules to Prolog clauses first partial evaluator for pure Prolog in his thesis in 1981. He has since
preferred the term partial deduction. Gallagher in 1983 was the first to
We now present Program 18.9, whlch translates Figure 18.1 to Pro- advocate using partial evaluation in Prolog for removing interpretation
gram 18.8. As for Program 18.6, the translation proceeds clause by overhead (Gallagher, 1986). Venken (1984) was the first to list some of
clause. There is a one-to-one correspondence between grammar rules the problems of extending partial evaluation to full Prolog. The paper
and Prolog clauses. The basic relation is translate (Rules,Clauses). that sparked the most interest in partial evaluation in Prolog is due
Individual clauses are translated by translate_rule/2. To translate a to Takeuch and Furukawa (1986). They discussed using partial evalua-
rule, both the head and body must be translated, with the appropriate tion for removing runtime overhead and showed an order of magnitude
correspondence of difference-lists, which will be added as additional speedup. Sterling and Beer (1989) particularize the work for expert sys-
arguments. tems. Their paper introduces the issue of pushng down meta-arguments,
Adding an argument is handled by the predicate translate-goal. If whch is subsumed in this chapter by should-f old declarations. Specific
the goal to be translated is a nonterminal symbol, a unary predicate with Prolog partial evaluation systems to read for more details are ProMiX
Chapter 18

(Lakhotia and Sterling, 1990) and Mixtus (Sahlin, 1991). An interesting


application of partial evaluation is given by Smith (1991),where efficient
string-matching programs were developed.
Logic Grammars
Composition was first discussed in the context of Prolog meta-inter-
preters in Sterling and Beer (1989) and an informal algorithm was given
in Sterling and Lakhotia (1988).A theory is found in Kirschenbaum, Ster-
ling, and Jain (1993).

A very important application area of Prolog is parsing. In fact, Prolog


originated from attempts to use logic to express grammar rules and to
formalize the process of parsing. In thls chapter, we present the most
common logic grammar formalism, definite clause grammars. We show
how grammar rules can be considered as a language on top of Prolog,
and we apply grammar rules to parse simple English sentences. In Chap-
ter 24, definite clause grammars are used as the parsing component of a
simple compiler for a Pascal-like language.

19.1 Definite Clause Grammars

Definite clause grammars arise from adding features of Prolog to


context-free grammars. In Section 18.3, we briefly sketched how context-
free grammars could be immediately converted to Prolog programs,
which parsed the language specified by the context-free grammar. By
adding the ability of Prolog to exploit the power of unification and the
ability to call builtin predicates, a very powerful parsing formalism is
indeed achieved, as h7enow shonr.
Consider the context -free grammar for recognizing the language
a*b*c*,presented in Figure 18.1, with equivalent Prolog program Pro-
gram 18.8. The Prolog program can be easily enhanced to count the
number of symbols that appear in any recognized sequence of a's, b's,
and c's. An argument would be added to each predicate constituting
the number of symbols found. Arithmetic would be performed to add
numbers together. The first clause would become
Chapter 19 Logic Grammars

s (As\Xs ,N) s(N) - a(NA), b(NB), c(NC), {N i s NA+NB+NC}


-
+

a(As\Bs ,NA) , b (Bs\Cs ,NB) , c (Cs\Xs ,NC) , N i s NA+NB+NC.

--- C I .
a(N) [a], a(N1), {N i s N1+1}.
a(0) [
The extra argument counting the nilmber of a's, b's, and c's can be b(N) [b] , b ( N l ) , {N i s N I + I J .
added to the grammar rule just as easily, ylelding

-- C I .
b(0) 1.
c(N) Ccl , c ( N l ) , {N i s N1+1}.
c(0)

Adding arguments to nonterminal symbols of context-free grammars, Program 19.1 Enhancing the language a*b*c *
and the ability to call (arbitrary) Prolog predicates, increases their util-
ity and expressive power. Grammars in this new class are called definite
clause grammars, or DCGs. Definite clause grammars are a generaliza-
tion of context-free grammars that are executable, augmented by the
language features of Prolog.
a(N)
a(0)
b(N)
--- I ,.
[a], a(N1), {N i s ~ 1 + 1 } .
[
Cbl b ( N l ) , {N is N1+1}.

--- CC I .,
Program 18.9, translating context-free grammars into Prolog programs,
b(0) 1.
can be extended to translate DCGs into Prolog. The extension is posed as c(N) Ccl c(N1), {N i s ~ 1 + 1 } .
Exercise (i) at the end of this section. Throughout tlvs chapter we write c(0)
DCGs in grammar rule notation, being aware that they can be viewed
as Prolog programs. Many Edinburgh Prolog implementations provide Program 19.2 Recognizing the language a" b\cA
support for grammar rules. The operator used for - is -->. Grammar
rules are expanded automatically into Prolog clauses with two extra ar-
However, there is a straightforward modification to the grammar given
guments added as the last two arguments of the predicate to represent
as Program 19.1. All that is necessary is to change the first rule and make
as a difference-list the sequence of tokens or words recognized by the
the number of a's, b's, and c's the same. The modified program is given
predicate. Braces are used to delimit goals to be called by Prolog di-
as Program 19.2.
rectly, which should not have extra arguments added during translation.
In Program 19.2, unification has added context sensitivity and in-
Grammar rules are not part of Standard Prolog but will probably be in-
creased the expressive power of DCGs over context-free grammars. DCGs
corporated in the future.
should be regarded as Prolog programs. Indeed, parsing with DCGs is a
Program 19.1 gives a DCG that recognizes the language a*b*c* and
perfect illustration of Prolog programming using nondeterministic pro-
also counts the number of letters in the recognized sequence. The en-
gramming and difference-lists. The top-down, left-to-right computation
hancement from Figure 18.1 is immediate. To query Program 19.1, con-
model of Prolog yields a top-down, left-to-right parser.
sideration must be taken of the two extra arguments that will be added.
Definite clause grammars can be used to express general programs.
For example, a suitable query is s (N , [ a , a , b ,b ,b ,cl , [ 1 > ?.
For example, a version of Program 3.15 for append with its last two
Counting the symbols could, of course, be accomplished by traversing
arguments swapped can be written as follows.
the difference-list of words. However, counting is a simple enhancement
to understand, whch effectively displays the essence of definite clause
grammars. Section 19.3 presents a wider variety of enhancements.
append([ I )
append( CX I Xsl )
[ - -I .CXI , append(Xs) .
Our next example is a strilung one of the increase in expressive power
possible using extra arguments and unification. Consider recognizing the Using DCGs for tasks other than parsing is an acquired programming
language aNbNcN,which is not possible with a context-free grammar. taste.
Chapter 19 Logic Grammars

The grammar for the declarative part of a Pascal program. Procedure declarations
declarative-part -
const-declaration, type-declaration,
procedure-declaration
procedure-declaration
- [ 1.
- procedure-heading, [ ; I , block.
var-declaration, procedure-declaration.
Constant declarations
procedure-heading -
[procedure], identifier, formal-parameter-part
const-declaration
const-declaration
[ I. -- formal-parameter-part
formal-parameter-part
-
- [(I,I .
[
formal-parameter-section, [)I.
[const], const-definition, [;I, const-definitions. formal-parameter-section - formal-parameters.
const-def initions
const-definitions
[ 1. -- formal-parameter-section
formal-parameters,
-
[;I, formal-parameter-section.
const-definition, [;I, const-definitions. formal-parameters - value-parameters.
const-definition - identifier, [=I, constant formal-parameters - variable-parameters.
identifier
constant -- [XI , {atom(X) 1 .
[XI , {constant(X)
value-parameters - var-definition.
variable-parameters - [var], var-definition.
Type declarations Program 19.3 (Continued)
type-declaration -
[ I.
type-declaration -
[type] , type-def inition, [;I , type-def initions. We conclude this section with a more substantial example. A DCG is
type-definitions - 1. [ given for parsing the declarative part of a block in a Pascal program. The
type-definitions - type-definition, [;I, type-definitions code does not in fact cover all of Pascal - it is not complete in its defi-
type-definition - identifier, [=I , type nition of types or constants, for example. Extensions to the grammar are
type
type
-- ['INTEGER'].
['REAL'].
posed in the exercises at the end of this section. Parsing the statement
part of a Pascal program is illustrated in Chapter 24.
type - ['BOOLEAN'] . The grammar for the declarative part of a Pascal block is given as Pro-
type - ['CHAR']. gram 19.3. Each grammar rule corresponds closely to the syntax diagram
Variable declarations for the corresponding Pascal statement. For example, the syntax diagram
var-declaration
var-declaration
--
[ 1. for constant declarations is as follows:
--- > const ----- > Constant Definition -------> ; ------- >
[var] , var-def inition, [;] , var-def initions.
var-definitions
var-definitions
-- [ 1.
var-definition, [ ; I , var-definitions.
I
+---------------<--------------------+
I

var-definition - identifiers, [:I, type.


identifiers
identifiers
-- identifier.
identifier, [,I , identifiers.
The second grammar rule for const-declaration in Program 19.3
says exactly the same. A constant declaration is the reserved word
const followed by a constant definition, handled by the nonterminal
Program 19.3 Parsing the declarative part of a Pascal block symbol const-definition; followed by a semicolon; followed by the
rest of the constant definition, handled by the nonterminal symbol
const-definitions. The first rule for const-declaration effectively
states that the constant declaration is optional. A constant definition is
Chapter 19 Logic G r a m m a r s

an identifier followed by =, followed by a constant. The definition for parse(Start,Tokens) -


The sequence of tokens Tokens represented as a difference-list
c o n s t - d e f i n i t i o n s is recursive, being either empty or another constant
definition; followed by a semicolon; followed by the rest of the constant can be reached by applying the grammar rules defined by - / 2 ,
definition. starting from Start.
The remainder of Program 19.3 is similarly easy to understand. It parse(A,Tokens)
nonterminal(A), A
- - B , parse(B,Tokens).
clearly shows the style of writing grammars in Prolog.
parse((A,B),Tokens\Xs) -
parse(A,Tokens\Tokensl), parse(B,Tokensl\Xs).
Exercises for Section 19.1 parse(A,Tokens) -terminals(A1, connect(A,Tokens).
-
parse({^) , ~ s \ X s ) A.
(i) Extend Program 18.9 so that it translates definite clause grammars terminals(Xs) - See Program 18.9.
to Prolog as well as context-free grammars. connect (Xs,Tokens) - See Program 18.8.
(ii) Add to Program 19.3 the ability to correctly handle label declara- Program 19.4 A definite clause grammar (DCG) interpreter
tions and function declarations.
(iii) Enhance Program 19.3 to return the list of variables declared in the
declarative part. four clauses for p a r s e in Program 19.4. The first rule handles the basic
(iv) Write a program to parse the language of your choice in the style of operation of reducing a nonterminal symbol, and the second rule handles
Program 19.3. conjunctions of symbols. The third rule handles terminal symbols, and
the fourth rule covers the ability to handle Prolog predicates by calling
them directly using the meta-variable facility.
Observe that the last argument in p a r s e / 2 , the DCG interpreter, is a
19.2 A Grammar Interpreter
difference-list. This difference-list can be handled implicitly using gram-
mar rule notation. In other words, Program 19.4 could itself be written as
Grammar rules are viewed in the previous section as syntactic sugar for
a DCG. This task is posed as Exercise 19.2(i).
Prolog clauses. This view is supported by Prolog systems with automatic
Recall that the interpreters of Chapter 17 were enhanced. Similarly,
grammar rule translation. There is a second way of viewing grammar
the DCG interpreter, Program 19.4, can be enhanced. Program 19.5 gives
rules, namely as a rule language.
a simple enhancement that counts the number of tokens used in pars-
This section takes the second view and considers grammar rules as
ing. As mentioned before, thls particular enhancement could be accom-
an embedded language on top of Prolog. We consider applying the in-
plished directly, but it illustrates how an interpreter can be enhanced.
terpreter techniques of Chapter 17 to grammar rules.
Comparing Programs 19.1 and 19.5 raises an important issue. Is it
Program 19.4 is an interpreter for grammar rules. The basic relation
better to enhance a grammar by modifying the rules, as in Program 19.1,
is p a r s e (Symbol , T o k e n s ) , which is true if a sequence of grammar rules
or to add the extra functionality at the level of the interpreter? The
can be applied to Symbol to reach Tokens. The tokens are represented as
second approach is more modular, but suffers from a lack of efficiency.
a difference-list.
The granularity of the DCG interpreter is at the clause reduction level,
the same as for the vanilla meta-interpreter, Program 17.5,and the expert Exercises for Section 19.2
system rule interpreter, Program 17.18. Indeed, the code in Program 19.4
is similar to those interpreters. There are four cases, handled by the (i) Write Program 19.4 as a DCG.
Chapter 19 Logic G r a m m a r s

parse(Start, Tokens,N) - G r a m m a r Rules


The sequence of tokens Tokens, represented as a difference-list, sentence - noun-phrase, verb-phrase
can be reached by applying the grammar rules defined by -/2,
starting from Start, and N tokens are found. noun-phrase -- determiner, noun-phrase2

~arse(A,Tokens,N)
nonterminal(A), A
- - B, parse(B,Tokens,N).
noun-phrase
noun-phrase2 --
noun-phrase2.
adjective, noun-phrase2
-
parse ((A,B) ,Tokens\Xs ,N)
parse (A ,Tokens\Tokensl,NA) , ~ a r s (B
e ,~okensl\Xs
,NB),
noun-phrase2
verb-phrase - verb.
noun.

N is NA+NB. verb-phrase - verb, noun-phrase.


~arse(A,Tokens,N) - Vocabulary
terminals(A), connect(A,Tokens), length(A,N).
-
parse ( {A} ,Xs\Xs ,0 ) A.
determiner -
-
[the] . adjective - [decorated]

terminals (Xs) - See Program 18.9. determiner


-
[a].

connect (A,Tokens) - See Program 18.8. noun


noun -
[pieplate].
[surprise].
verb -- [contains]

length(Xs ,N) - See Program 8.1


1.
Program 19.6 A DCG context-free grammar
Program 19.5 A DCG interpreter that counts words

Using the terminology of stepwise enhancement introduced in Chap-


(ii) Use the partial reducer, Program 18.3, to specialize the interpreter ter 13, wc can view a grammar as a skeleton. We proceed to show how
of Program 19.4 to a particular grammar. For example, Figure 18.1 useful grammatical features can be added by enhancement. The next
should be transformed to Program 19.1. two programs are enhancements of Program 19.6. The enhancements,
although simple, typify how DCGs can be used for natural language ap-
(iii) Enhance Program 19.4 to build a parse tree.
plications. Both programs exploit the power of the logical variable.
The first enhancement is constructing a parse tree for the sentence as
it is being parsed. The program is given as Program 19.7.Arguments rep-
19.3 Application to Natural Language Understanding resenting (subparts of) the parse tree must be added to Program 19.6.
The enhancement is similar to adding structured arguments to logic pro-
An important application area of logic programming has been under- grams, as discussed in Section 2.2. The program builds the parse tree
standing natural languages. Indeed, the origins of Prolog lie withn t h s top-down, exploiting the power of the logic variable.
application. In t h s section, it is shown how Prolog, through definite The rules in Program 19.7 can be given a declarative reading. For exam-
clause grammars, can be applied to natural language processing. ple, consider the rule
A simple context-free grammar for a small subset of English is given
in Program 19.6. The nonterminal symbols are grammatical categories,
parts of speech and phrases, and the terminal symbols are English words
that can be thought of as the vocabulary. The first rule in Program 19.6 This states that the parse tree built in recognizing the sentence is a struc-
says that a sentence is a noun phrase followed by a verb phrase. The last ture sentence (NP, VP), where NP is the structure built while recognizing
rule says that surprise is a noun. A sample sentence recognized by the the noun phrase and VP is the structure built while recognizing the verb
grammar is: "The decorated pieplate contains a surprise." phrase.
Chapter 19 Logic Grammars

sentence(sentence (NP ,VP)) - noun-phrase(NP) , verb-phrase(VP)


noun-phrase (np(D ,N))
noun-phrase (np (N)) -- determiner(D1, noun-~hrase2(N).
noun-phrase2 (N) . noun-phrase (np (D ,N) ,Num) -
--
determiner (D,Num) , noun_phrase2(N , N u ) .
no~n-~hrase:!(np2(A,N)) adjective(A), noun_phrase2(N).
noun-phrase(np(N),Num) - noun_phrase2(N,Num)
no~n-~hrase2(np2(N))
verb-phrase(vp(V)) --
noun(N).
verb(V).
noun-phrase2 (np2(A,N) ,Num) -
verb-phrase (vp(V ,N) )
Vocabulary
verb (V) , noun-phrase (N) .
noun-phrase2 (np2 (N) ,Num) -
adjective(A1, noun_phrase2(~,Num).
noun (N ,Num)

determiner(det (the))
determiner(det (a)) -- [the] .
[a] .
noun(noun(piep1ate) ) -
-
[pieplate] . Vocabulary
noun(noun(surprise)) [surprise].
-[the] .

- -
determiner (det (the) ,Num)
adjective (adj (decorated) ) [decorated] . determiner (det (a), singular) -
[a] .

---
verb(verb(c0ntains)) [contains].
noun(noun(pieplate),singular) [pieplatel.
Program 19.7 A DCG computing a parse tree noun (noun (pieplates) ,plural) [pieplatesl .
noun(noun(surprise),singular) [surprise].
noun(noun(surprises) ,plural) - [surprises] .
The next enhancement concerns subject/object number agreement. adjective(adj (decorated)) - [decorated] .
verb (verb (contains) ,singular) - [contains]
Suppose we wanted our grammar also to parse the sentence "The dec-
orated pieplates contain a surprise." A simplistic way of handling plural verb(verb(contain) ,plural) - [contain] .
forms of nouns and verbs, sufficient for the purposes of this book, is to
treat different forms as separate words. We augment the vocabulary by Program 19.8 A DCG with subject/object number agreement
adding the facts
noun(noun(piep1ates)) -
[pie~latesl.
verb (verb (contain)) -- [contain] .
the same number, singular or plural. The agreement is indicated by the
The new program would parse "The decorated pieplates contain a sur- sharing of the variable Num. Expressing subject/object number agreement
prise" but unfortunately would also parse "The decorated pieplates con- is context-dependent information, whch is clearly beyond the scope of
tains a surprise." There is no insistence that noun and verb must both be context-free grammars.
singular, or both be plural. Program 19.8 is an extension of Program 19.7 that handles number
Number agreement can be enforced by adding an argument to the agreement correctly. Noun phrases and verb phrases must have the same
parts of speech that must be the same. The argument indicates whether number, singular or plural. Similarly, the determiners and nouns in a
the part of speech is singular or plural. Consider the grammar rule noun phrase must agree in number. The vocabulary is extended to indi-
sentence (sentence(NP,VP) ) - cate whch words are singular and whch plural. Where number is unim-
portant, for example, with adjectives, it can be ignored, and no extra
noun-phrase (NP ,Num) , verb-phrase (VP,Num) .
argument is given. The determiner the can be either singular or plural.
The rule insists that both the noun phrase, whch is the subject of the This is handled by leaving the argument indicating number uninstanti-
sentence, and the verb phrase, whch is the object of the sentence, have ated.
Chapter 19 Logic Grammars

The next example of a DCG uses another Prolog feature, the ability number(0)
number(N)
-- [zero] .
xxx(N).
to refer to arbitrary Prolog goals in the body of a rule. Program 19.9 is
a grammar for recognizing numbers written in English up to, but not xxx(N) -
digit (Dl, [hundred] , rest-xxx(NI), {N is D * ~ O O + N I }
including, 1,000. The value of the number recognized is calculated using
the arithmetic facilities of Prolog.
xxx(N) - xx(N).

The basic relation is number (N), where N is the numerical value of the rest-xxx(0)
rest-xxx (N)
-- [ I.
Candl , xx (N).
number being recognized. According to the grammar specified by the
program, a number is zero or a number N of at most three digits, the rela- xx(N) -- digit (N) .

tion xxx (N). Similarly xx(N) represents a number N of at most two digits,
and the predicates rest-xxx and rest-xx denote the rest of a number of
xx(N)
XX(N) - teen(N).
tens(T), rest-xx(N1), {N is T + N ~ }

three or two digits, respectively, after the leading digit has been removed.

--- ---
The predicates digit, teen, and tens recognize, respectively, single dig-
digit(1) [one] . teen(l0) [ten].
its, the numbers 10 to 19 inclusive, and the multiples of ten from 20 to digit (2) [two] . teen(l1) [eleven] .
90 inclusive.
A sample rule from the grammar is
digit (3)
digit (4) -- [three].
[four] .
[five] .
teen(l2)
teen(l3) -- [twelve].
[thirteen].
digit (5) [fourteen] .

---
teen(l4)
xxx(N) -- digit (6) - [six]. teen(l5) If ifteenl .
- [seven] . [sixteen] .
digit(D), [hundred], rest-xxx(Nl), {N is ~ * 1 0 0 + ~ 1 } . digit(7)
digit(8)
digit(9)
-
-
[eight] .
[nine] .
teen(l6)
teen(l7)
teen(l8) -
[seventeen] .
[eighteen] .
This says that a three-digit number N must first be a digit with value teen(l9) - [nineteen]
D, followed by the word hundred followed by the rest of the number, - [twenty] .
which will have value Nl. The value for the whole number N is obtained
by multiplying D by 100 and adding N1.
tens(20)
tens(30) -- [thirty] .

DCGs inherit another feature from logic programming, the ability to


tens(40)
tens(50) -- [forty].
[fifty].
be used backward. Program 19.9 can be used to generate the written
representation of a given number up to, but not including, 1,000. In
tens(60)
tens(70) -- [sixty].
[seventy]

technical terms, the grammar generates as well as accepts. The behavior


in so doing is classic generate-and-test. All the legal numbers of the
tens(80)
tens(90) - [eighty].
[ninety].

grammar are generated one by one and tested to see whether they have Program 19.9 A DCG for recognizing numbers
the correct value, until the actual number posed is reached. This feature
is a curiosity rather than an efficient means of writing numbers.
The generative feature of DCGs is not generally useful. Many grammars
have recursive rules. For example, the rule in Program 19.6 defining a
noun-phrase2 as an adjective followed by a noun-phrase2 is recursive.
Using recursively defined grammars for generation results in a nonter-
minating computation. In the grammar of Program 19.7, noun phrases
with arbitrarily many adjectives are produced before the verb phrase is
considered.
Chapter 19

Exercises for Section 19.3

(i) Write a simple grammar for French that illustrates gender agree- Search Techniques
ment.
(ii) Extend and modify Program 19.9 for parsing numbers so that it cov-
ers all numbers less than 1 million. Don't forget to include thmgs
like "thirty-five hundred" and to not include "thlrty hundred."

19.4 Background

Prolog was connected to parsing right from its very beginning. As men- In thls chapter, we show7programs encapsulating classic A1 search tech-
tioned before, the Prolog language grew out of Colmerauer's interest niques. The first section discusses state-transition frameworks for solv-
in parsing, and his experience with developing Q-systems (Colmerauer, ing problems formulated in terms of a state-space graph. The second
1973). The implementors of Edinburgh Prolog were also keen on natu- discusses the minimax algorithm with alpha-beta pruning for searching
ral language processing and wrote one of the more detailed accounts of game trees.
definite clause grammars (Pereira and Warren, 1980). This paper gives a
good discussion of the advantages of DCGs as a parsing formalism in
comparison with augmented transition networks (ATNs). 20.1 Searching State-Space Graphs
The examples of using DCGs for parsing languages in Section 19.1were
adapted from notes from a tutorial on natural language analysis given State-space graphs are used to represent problems. Nodes of the graph
by Lynette Hirschrnan at the Symposium on Logic Programming in San are states of the problem. An edge exists between nodes if there is a
Francisco in 1987. The DCG interpreter of Section 19.2 is adapted from transition rule, also called a move, transforming one state into the next.
Pereira and Shieber (1987). Solving the problem means finding a path from a given initial state to a
Even though the control structure of Prolog matches directly that of desired solution state by applying a sequence of transition rules.
recursive-descent, top-down parsers, other parsing algorithms can also Program 20.1 is a framework for solving problems by searching their
be implemented in it quite easily. For example, Matsumoto et al. (1986) state-space graphs, using depth-first search as described in Section 14.2.
describes a bottom-up parser in Prolog. No commitment has been made to the representation of states. The
The grammar in Program 19.3 is taken from Appendix 1 of Findlay moves are specified by a binary predicate move ( S t a t e , Move), where
and Watt (1985).The grammar in Program 19.6 is taken from Winograd's Move is a move applicable to S t a t e . The predicate update ( S t a t e ,Move,
(1983)book on computational linguistics. S t a t e l ) finds the state S t a t e 1 reached by applying the move Move to
For further reading on logic grammars, refer to Pereira and Shleber state S t a t e . It is often easier to combine the move and update proce-
(1987) and Abramson and Dahl(1989). dures. We keep them separate here to make knowledge more explicit and
to retain flexibility and modularity, possibly at the expense of perfor-
mance.
The validity of possible moves is checked by the predicate l e g a l
( S t a t e ) , which checks if the problem state S t a t e satisfies the con-
straints of the problem. The program keeps a history of the states visited
Chapter 20 Search Techniques

solve-dfs (State,History,Moves) -
M o v e s is a sequence of movesto reach a
The occupants of the left bank can be deduced from the occupants of
the right bank, and vice versa. But having both makes specifying moves
desired final state from the current State, clearer.
where History contains the states visited previously.
solve-df s(State ,History,[ 1 ) - It is convenient for checlung for loops to keep the lists of occupants
sorted. Thus wolf will always be listed before goat, both of whom will be
final-state(State).
solve~dfs(State,History,[Move~Movesl)
move(State ,Move),
- before cabbage if they are on the same bank.
Moves transport an occupant to the opposite bank and can thus be
update(State ,Move,Statel), specified by the particular occupant who is the Cargo. The case when
legal(State1) , nothmg is taken is specified by the cargo alone. The nondeterministic
not member(Statel,History), behavior of member allows a concise description of all the possible moves
solve~dfs(Statel,[StatellHistory],Moves).
in three clauses as shown in Program 20.2: moving something from the
Testing the f r a m e w o r k left bank, moving somethng from the right bank, or the farmer's rowing
in either direction by hlmself.
For each of these moves, the updating procedure must be specified,
Program 20.1 A depth-first state-transition framework for problem solving namely, changing the position of the boat (by update_boat/2) and up-
dating the banks (by update-banks). Using the predicate select allows
a compact description of the updating process. The insert procedure
to prevent looping. Checlung that looping does not occur is done by see- is necessary to keep the occupant list sorted, facilitating the check if a
ing if the new state appears in the history of states. The sequence of state has been visited before. It contains all the possible cases of adding
moves leading from the initial state to the final state is built incremen- an occupant to a bank.
tally in the third argument of solve-df s / 3 . Finally, the test for legality must be specified. The constraints are sim-
To solve a problem using the framework, the programmer must decide ple. The wolf and goat cannot be on the same bank without the farmer,
how states are to be represented, and axiomatize the move, update, and nor can the goat and cabbage.
legal procedures. A suitable representation has profound effect on the Program 20.2, together with Program 20.1, solves the wolf, goat, and
success of thls framework. cabbage problem. The clarity of the program speaks for itself.
Let us use the framework to solve the wolf, goat, and cabbage problem. We use the state-transition framework for solving another classic
We state the problem informally. A farmer has a wolf, goat, and cabbage search problem from recreational mathematics-the water jugs prob-
on the left side of a river. The farmer has a boat that can carry at most lem. There are two jugs of capacity 8 and 5 liters with no markings, and
one of the three, and he must transport this trio to the right bank. The the problem is to measure out exactly 4 liters from a vat containing 20
problem is that he dare not leave the wolf with the goat (wolves love liters (or some other large number). The possible operations are filling
to eat goats) or the goat with the cabbage (goats love to eat cabbages). up a jug from the vat, emptying a jug into the vat, and transferring the
He takes all his jobs very seriously and does not want to disturb the contents of one jug to another until either the pouring jug is emptied
ecological balance by losing a passenger. completely, or the other jug is filled to capacity. The problem is depicted
States are represented by a triple, wgc(B,L,R), where B is the po- in Figure 20.1.
sition of the boat (left or right), L is the list of occupants of the The problem can be generalized to N jugs of capacity CI,. . .,CN. The
left bank, and R the list of occupants of the right bank. The ini- problem is to measure a volume V, different from all the C, but less
tial and final states are wgc (left , [wolf,goat, cabbage] , [ 1 and than the largest. There is a solution if V is a multiple of the greatest
wgc (right, [ 1 , [wolf,goat, cabbage] ) , respectively. In fact, it is not common divisor of the Ci. Our particular example is solvable because 4
strictly necessary to note the occupants of both the left and right banks. is a multiple of the greatest common divisor of 8 and 5.
Chapter 20 Search Techniques

States for the wolf, goat and cabbage problem are a structure
wgc(Boat,Left,Right),where Boat is the bank on which the boat
currently is, Left is the list of occupants on the left bank of
the river, and Right is the list of occupants on the right bank.
initial-state (wgc,wgc (left , [wolf ,goat,cabbage] , [ 1 ) ) .
f inal-state (wgc (right, [ 1 , [wolf ,goat,cabbage] ) ) . -
move(wgc(left,L,~),~arg~) member(~argo,L).
Figure 20.1 The water jugs problem ric: 4 9 I
- e

<"
- deli:
+

move (wgc (right,L ,R) ,Cargo) member (Cargo,R) .


move(wgc(B,~,~),alone).
> .
r 22. U
The particular problem we solve is for two jugs of arbitrary capacity, C
but the approach is immediately generalizable to any number of jugs.
% L)

The program assumes two facts in the database, c a p a c i t y ( 1 ,CI), for


I equals 1 and 2. The natural representation of the state is a structure
jugs (Vl ,V2), where V 1 and V2 represent the volumes of liquid currently
in the two jugs. The initial state is jugs (0,O) and the desired final state
either jugs ( 0 , X I or jugs (X, 01, where X is the desired volume. In fact,
the only final state that needs to be specified is that the desired volume
insert(X,[YlYsl ,[X,YIYsl) - be in the larger jug. The volume can be transferred from the smaller
precedes(X,Y).
-
insert (X, [Y I YS] , [Y I ZS] ) volume, if it fits, by emptylng the larger jug and pouring the contents
of the smaller jug into the larger one.
~recedes(Y,XI, insert (X,Ys,Zs)
insert(X, [ I, [XI). Data for solving the jugs problem in conjunction with Program 20.1
are given in Program 20.3. There are six moves: filling each jug, emptying
each jug, and transferring the contents of one jug to another. A sam-
legal(wgc(left,L,R))
legal(wgc(right,L,R))
-- not illegal(R1.
not illegal(L).
ple fact for filling the first jug is move ( j u g s (Vl ,V2) , f i l l (1) 1. The jugs'
state is given explicitly to allow the data to coexist with other problem-
illegal(Bank) - member(wolf,Bank), member(goat,~ank). solving data such as in Program 20.2. The emptying moves are optimized
illegal (Bank) - member (goat ,Bank) , member (cabbage , ~ a n k ) to prevent emptying an already empty jug. The updating procedure asso-
select(X,Xs,Ys) - See Program 3.19. ciated with the first four moves is simple, whlle the transferring opera-
tion has two cases. If the total volume in the jugs is less than the capacity
Program 20.2 Solving the wolf, goat, and cabbage problem of the jug being filled, the pouring jug will be emptied and the other
jug will have the entire volume. Otherwise the other jug will be filled to
capacity and the difference between the total liquid volume and the ca-
pacity of the filled jug will be left in the pouring jug. This is achieved by
the predicate a d j u s t / 4 . Note that the test for legality is trivial because
all reachable states are legal.
Most interesting problems have too large a search space to be searched
exhaustively by a program like 20.1. One possibility for improvement is
Chapter 20 Search Techniques

initial-statecjugs, jugs(0,O)).
to put more knowledge into the moves allowed. Solutions to the jug prob-
final-state(jugs(4,V)). lem can be found by filling one of the jugs whenever possible, emptying
f inal_state(jugs(V,4). the other whenever possible, and otherwise transferring the contents of
move(jugs(~l,V2) ,fill(l)). the jug being filled to the jug being emptied. Thus instead of six moves
move(jugs(V1 ,v2) ,fill(2) 1. only three need be specified, and the search will be more direct, because
move(jugs(Vl,V2),empty(l))
move(jugs(vl,v2),empty(2))
--
V1 > 0.
V2 > 0.
only one move will be applicable to any given state. This may not give an
optimal solution if the wrong jug to be constantly filled is chosen.
move(jugs(V1,~2),transfer(2,1)).
Developing this point further, the three moves can be coalesced into
move(jugs(V1,~2),transfer(1,2)).
a higher-level move, f ill-and-transfer. This tactic fills one jug and
update(jugs(Vl,V2) ,fill(l), jugs(Cl,V2)) - capacity(1,Cl).
update(jugs(V1 ,V2),fill(2), jugs(V1 ,C2))- capacity(2,c2).
transfers all its contents to the other jug, emptying the other jug as
necessary. The code for transferring from the bigger to the smaller jug
update(jugs(Vl ,V2) ,empty(l) ,jugs(O,v2)).
is
update(jugs(V1 ,V2) ,empty(2), jugs(v1,O)).
update(jugs(V1,V2),transfer(2,1),jugs(~1,~2)) -
capacity(1 ,C1), move(jugs(V1 ,V2) ,fill-and-transf er(1) ) .
Liquid is V1 + V2,
update(jugs(V1 ,V2) ,fill-and-transfer (I), jugs ( 0 , V ) ) +

Excess is Liquid - C1,


capacity(1 ,Cl),
adjust(Liquid,Excess,Wl,W2).
update(jugs(V1 ,V2),transfer(l,2),jugs(W1 ,W2)) - capacity(2,C2),
capacity(2,C2), C1 > C2,
Liquid is V1 + V2, V is (Cl+V2) mod C2.
Excess is Liquid - C2,
adjust(Liquid,Excess,W2,Wl).
adjust(Liquid,Excess,Liquid,O)
adjust(Liquid,Excess,V,Excess)
--
Excess 5 0.
Using this program, we need only three fill and transfer operations to
solve the problem in Figure 20.1.
Excess > 0 , V is Liquid - Excess.
Adding such domain knowledge means changing the problem descrip-
tion entirely and constitutes programming, although at a different level.
legal (jugs (V1 , V 2 ) ) .
Another possibility for improvement of the search performance, inves-
capacity(l,8). tigated by early research in AI, is heuristic guidance. A general frame-
capacity(2,5).
work, based on a more explicit choice of the next state to search in
Program 20.3 Solving the water jugs problem the state-space graph, is used. The choice depends on numeric scores
assigned to positions. The score, computed by an evuluation function,
is a measure of the goodness of the position. Depth-first search can
be considered a special case of searching using an evaluation function
whose value is the distance of the current state to the initial state, while
breadth-first search uses an evaluation function which is the inverse of
that distance.
We show two search techniques that use an evaluation function explic-
itly: hill climbing and best-first search. In the following, the predicate
value (State ,Value) is an evaluation function. The techniques are de-
scribed abstractly.
C h a p t e r 20 Search Techniques

Hill climbing is a generalization of depth-first search where the suc- solve- hill-climb (State,History,Moves)
Moves is the sequence of moves to reach a
-
cessor position with the highest score is chosen rather than the leftmost
desired final state from the current State,
one chosen by Prolog. The problem-solving framework of Program 20.1 is where History is a list of the states visited previously.
easily adapted. The hill climbing move generates all the states that can be
reached from the current state in a single move, and then orders them
solve~hill~climb(State,History,[1 )
f inal-state(State1 .
-
in decreasing order with respect to the values computed by the evalu-
ation function. The predicate evaluate-and-order (Moves, S t a t e , MVs)
solve~hill~climb(State,History,[Move~Moves])
hill-climb(State ,Move),
-
determines the relation that MVs is an ordered list of move-value tuples update(State,Move,Statel),
legal(Statel),
corresponding to the list of moves Moves from a state S t a t e . The overall
not member(State1 ,History),
program is given as Program 20.4. solve~hill~climb(Statel, [Statel (History],Moves).
To demonstrate the behavior of the program we use the example tree
of Program 14.8 augmented with a value for each move. This is given as
hill-climb(State,Move) -
f indall(M,move(State ,M) ,Moves),
Program 20.5. Program 20.4, combined with Program 20.5and appropri- evaluate-and-order(Moves,State,[ ],MVs),
ate definitions of update and l e g a l searches the tree in the order a, d, member ( ( M o v e ,Value) ,MVs) .
j. The program is easily tested on the wolf, goat, and cabbage problem
using as the evaluation function the number of occupants on the right
evaluate-and-order (Moves,Srate,SoFar,OrderedMVs)
All the Moves from the current State
-
bank. are evaluated and ordered as O r d e r e d M V s .
Program 20.4 contains a repeated computation. The state reached by S o F a r is an accumulator for partial computations.
Move is calculated in order to reach a value for the move and then re- evaluate~and~order([MoveIMovesl,State,MVs,OrderedMVs) -
calculated by update. This recalculation can be avoided by adding an update(State,Move,Statel),
value (Statel,Value) ,
extra argument to move and keeping the state along with the move and insert ((Move ,Value),MVs ,MVsl),
the value as the moves are ordered. Another possibility if there will be evaluate-and-order(Moves,State,MVsl,OrderedMVs).
many calculations of the same move is using a memo-function. What is evaluate-and-order([ ] , S t a t e , M V s , M V s ) .
the most efficient method depends on the particular problem. For prob- insert ( M V ,[ 1 , [MV] 1.
lems where the u p d a t e procedure is simple, the program as presented i n s e r t ( ( M , V ) , [ ( ~ l , V lIMVs],[(M,V),(Ml,Vl)~MVs])
v 2 v1.
) -
will be best.
Hill climbing is a good technique when there is only one hill and the insert((~,~),[(Ml,Vl)IMVs] ,[(Ml,Vl)IMVsl])
V < V 1 , insert((M,V),MVs,MVsl).
-
evaluation function is a good indication of progress. Essentially, it takes
a local look at the state-space graph, making the decision on where next Testing the f r a m e w o r k
to search on the basis of the current state alone. test-hill-climb(Problem,Moves)
initial-state (Problem,State) ,
-
An alternative search method, called best-first search, takes a global
solve-hill-climb(State, [State] ,Moves).
look at the complete state-space. The best state from all those currently
unsearched is chosen. Program 20.4 Hill climbing framework for problem solving
Program 20.6 for best-first search is a generalization of breadth-first
search given in Section 16.2. A frontier is kept as for breadth-first search,
whch is updated as the search progresses. At each stage, the next best
available move is made. We make the code as similar as possible to
Program 20.4 for hill climbing to allow comparison.
Chapter 20 Search Techniques

solve- best (Frontier,History,Moves) -


Moves is a sequence of moves to reach a desired final state from
the initial state, where Frontier contains the current states under
consideration, and History contains the states visited previously.
solve-best([state(State,~ath,~alue)l F r o n t i e r , H i s t o r y , ~ o v e s ) +
f inal-state (State) , reverse(Path,Moves) .
solve~best([state(State,Path,~alue)~Frontier],~istory,FinalPath)-
Program 20.5 Test data findall(M,move(State,M) ,Moves),
updates(Moves ,Path,State,States),
legals (States ,Statesl),
At each stage of the search, there is a set of moves to consider rather news(Statesl,History,States2),
evaluates (States2,Values) ,
than a single one. The plural predicate names, for example, updates inserts(Values,Frontier,Frontierl),
and l e g a l s , indicate this. Thus l e g a l s ( S t a t e s , S t a t e s l ) filters a set of solve-best(Frontierl,[StatelHistory] ,Finalpath).
successor states, checlung which ones are allowed by the constraints of
the problem. One disadvantage of breadth-first search (and hence best-
updates (Moves,Path,State,States) -
States is the list of possible states accessible from the
first search) is that the path to take is not as conveniently calculated. current State, according to the list of possible Moves,
Each state must store explicitly with it the path used to reach it. T h s is where Path is a path from the initial node to State.
reflected in the code. updates([MIMsl ,Path,S,[(Sl, [MIPathl) l S s l -
Program 20.6 tested on the data of Program 20.5 searches the tree in update(S,M,Sl), updates(Ms,Path,S,Ss).
the same order as for hlll climbing. updates([ 1 ,Path,State,[ I).
Program 20.6 makes each step of the process explicit. In practice, it legals (States,Statesl) -
may be more efficient to combine some of the steps. When filtering the Statesl is the subset of the list of States that are legal.
generated states, for example, we can test that a state is new and also le- legals( [(S,P) IStatesl , [(S,P) IStatesll) -
gal at the same time. This saves generating intermediate data structures. legal(S), legals(States,Statesl).
Program 20.7 illustrates the idea by combining all the checks into one
legals( [(S,P) I Statesl ,Statesl) -
not legal(S), legals(States,Statesl).
procedure, update-f r o n t i e r . legals([ I,[ I).
news (States,History,Statesl) -
Exercises for Section 20.1 Statesl is the list of states in States but not in History.
news([(S,P) IStates],History,Statesl) -
(i) Redo the water jugs program based on the two fill-and-transfer member(S,History), news(States,History,Statesl).
operations. news( [(S,P) I Statesl ,History,[(S ,P) I States11 -
not member(S,History), news(States,History,Statesl).
(ii) Write a program to solve the missionaries and cannibals problem: news([ I ,History,[ I).

Three missionaries and three cannibals are standing on the left bank o f
evaluates (States,Values) -
Values is the list of tuples of States augmented by their value.
a river. There is a small boat to ferry them across with enough room
for only one or two people. They wish to cross the river. If ever there evaluates( [(S,P) I Statesl, [state(S,P,V) IValuesI)
value(S,V), evaluates(States,Values).
-
are more missionaries than cannibals on a particular bank of the river,
evaluates( [ I , [ I).
the missionaries will convert the cannibals. Find a series of ferryings
to transport safely all the missionaries and cannibals across the river Program 20.6 Best-first framework for problem solving
without exposing any of the cannibals to conversion.
Search Techniques
Chapter 20

inserts(States,Frontier,Frontierl) -
Frontier1 is the result of inserting States into the current Frontier.
(iii) Write a program to solve the five jealous husbands problem (Du-
deney, 1917):

During a certain flood five married couples found themselves sur-


rounded by water and had to escape from their unpleasant position
in a boat that would only hold three persons at a time. Every husband
was so jealous that he would not allow his wife to be in the boat or on
insert (State, [ 1 , [State] ) .
insert(State, [Statel I States] , [State,StatelI States]
- either bank with another man (or with other men) unless he himself was
present. Find a way o f getting these five men and their wives across to
safety.
lesseq-value (State,Statel) .
insert (State, [Statel l Statesl , [State l Statesl ) - (iv) Compose a general problem-solving framework built around
equals(State,Statel) .
insert (State, [Statel I Statesl , [Statel I States11
- breadth-first search analogous to Program 20.1, based on programs
greater-value(State ,Statel), insert (state ,states ,states11 in Section 16.2.
(v) Express the 8-queens puzzle within the framework. Find an evalua-
tion function.

-- - -

Program 20.6 (Continued)


20.2 Searching Game Trees

solve- best (Frontier,History,Moves) -


Moves is a sequence of moves to reach a desired final state
What happens when we play a game? Starting the game means setting up
the chess pieces, dealing out the cards, or setting out the matches, for
from the initial state. Frontier contains the current states example. Once it is decided who plays first, the players take turns making
under consideration. History contains the states visited previously. a move. After each move the game position is updated accordingly.
We develop the vague specification in the previous paragraph into a
solve-best ( [state(State ,Path,Value) 1 ~rontier] i is tory ,Moves) +

f inal-state(State) , reverse(Path, [ 1 ,~oves).


simple framework for playing games. The top-level statement is
-
solve-best ([state(State ,Path,Value) (~rontier] is tor^ , ~ i n a l ~ a t h )
f indall (M ,move(State ,M) ,Moves) ,
update-f rontier (Moves ,State,Path,History ,Frontier ,~rontierl),
solve~best(Frontierl,[~tate~~istor~],~inal~ath~.
update-frontier( [MIMsl , ~ t a t e , ~ a t h , ~ i s t.F,F1)
update(~tate,M,Statel),
ory -
legal(Statel), The predicate initialize (Game,Position,Player) determines the ini-
value (Statel ,Value) , tial game position Position for Game, and Player, the player to start.
not member(~tatel,History), A game is a sequence of turns, where each turn consists of a player
insert((State1, [MIPathl ,Value) ,F,FO), choosing a move, the move being executed, and the next player being
update-f rontier (Ms ,State ,Path,History ,FO,F1) .
determined. The neatest way of expressing thls is as a tail recursive
update-frontier([ 1 ,S,P,H,F,F).
procedure, play, with three arguments: a game position, a player to
insert (State ,Frontier,Frontierl) - See Program 20.6. move, and the final result. It is convenient to separate the choice of the
Program 20.7 Concise best-first framework for problem solving move by choose-move/3 from its execution by move/3. The remaining
Chapter 20 Search Techniques

play(Game) -
Play game with name G a m e .
evaluate-and-choose (Moves,Position,Record,BestMove
Chooses the BestMove from the set of M o v e s from the
-
current Position. Record records the current best move.
-
evaluate-and-choose ( [Move I Moves] ,position ,Record,~ e s t ~ o v e )
move(~ove,Position,~ositionl),
value (Positionl ,Value) ,
play(Position,Player,Re~ult) -
game-over (~osition,Player,Result) , ! , announce(Result)
update (Move,Value,Record, Recordl) ,
evaluate~and~choose(Moves,Position,Recordl,BestMo~e).
play(Position,Player,Re~ult) -
choose-move(~osition,Player,Move),
evaluate-and-choose([ ],~osition,(~ove,Value),Move).
update(Move,Value,(Movel,Valuel),(Movel,Valuel)) -
move(Move,P~~iti~n,P~~iti~nl),
display-game (Position1 ,Player) ,
next-player(P1ayer ,Playerl) ,
Value IValue 1.
update(Move,Value,(Movel,Valuel),(Move,Value~)
Value > Valuel.
-
! , play(Positionl,Playerl,Re~~lt).
Program 20.9 Choosing the best move
Program 20.8 Framework for playing games I
I
I Most game trees are far too large to be searched exhaustively. This sec-
I
predicates in the clause for play/3 display the state of the game and I tion discusses the techniques that have been developed to cope with the
determine the next player: I large search space for two-person games. In particular, we concentrate
on the minimax algorithm augmented by alpha-beta pruning. This strat-
play (position,~layer ,Result) - egy is used as the basis of a program we present for playing Kalah in
choose-move (Position,Player ,Move) ,
Chapter 21.
move (Move , ~ o s i t i ~ n , P ~ ~ i t,i ~ n l )
We describe the basic approach of searchmg game trees using evalua-
display-game(~o~iti~nl,Player),
tion functions. Again, in this section value (Posit ion,Value) denotes
next-player (Player ,Playerl) ,
an evaluation function computing the Value of Position, the current
! , play (Position1 ,Player1,Result). state of the game. Here is a simple algorithm for choosing the next move:
Program 20.8 provides a logical framework for game-playing programs. Find all possible game states that can be reached in one move.
Using it for writing a program for a particular game focuses attention on Compute the values of the states using the evaluation function.
the important issues for game playing: what data structures should be Choose the move that leads to the position with the highest score.
used to represent the game position, and how strategies for the game
should be expressed. We demonstrate the process in Chapter 2 1 by writ- This algorithm is encoded as Program 20.9. It assumes a predicate
ing programs to play Nim and Kalah. move (Move,Position,Positionl) that applies a Move to the current Po-
The problem-solving frameworks of Section 20.1 are readily adapted sition to reach Positionl. The interface to the game framework of
to playing games. Given a particular game state, the problem is to find a Program 20.8 is provided by the clause
path of moves to a winning position.
A game tree is similar to a state-space graph. It is the tree obtained by
choose~move(Position,computer,Move) -
f indall (M,move (Position,M) ,Moves),
identifying states with nodes and edges with players' moves. We do not, evaluate~and~choose(Moves ,Position,(nil,-1000) , ~ o v e .)
however, identify nodes on the tree, obtained by different sequences of
moves, even if they repeat the same state. In a game tree, each layer is The predicate move (Position,Move) is true if Move is a possible move
called a ply. from the current position.
Chapter 20 Search Techniques

The basic relation is evaluate-and-choose (Moves ,Position,Record,


BestMove) whlch chooses the best move BestMove in the possible Moves
from a given Position. For each of the possible moves, the correspond-
ing position is determined, its value is calculated, and the move with
the highest value is chosen. Record is a record of the current best move
so far. In Program 20.9, it is represented as a tuple (Move ,Value). The
structure of Record has been partially abstracted in the procedure up-
date/4. How much data abstraction to use is a matter of style and a
trade-off among readability, conciseness, and performance. Figure 20.2 A simple game tree
Looking ahead one move, the approach of Program 20.9, would be
sufficient if the evaluation function were perfect, that is, if the score
reflected which positions led to a win and which to a loss. Games become Program 20.10 encodes the minimax algorithm. The basic relation is
minimax (D ,Posit ion,MaxMin ,Move, Value), which is true if Move is the
interesting when a perfect evaluation function is not known. Choosing a
move with the highest Value from Position obtained by searchng D ply
move on the basis of looking ahead one move is generally not a good
strategy. It is better to look several moves ahead and to infer from what in the game tree. MaxMin is a flag that indicates if we are maximizing or
is found the best move to make. minimizing. It is 1 for maximizing and - 1 for minimizing, the particular
The minimax algorithm is the standard method for determining the values being chosen for ease of manipulation by simple arithmetic opera-
value of a position based on searchng the game tree several ply ahead. tions. A generalization of Program 20.9 is used to choose from the set of
The algorithm assumes that, when confronted with several choices, moves. Two extra arguments must be added to evaluate-and-choose:
the opponent would make the best choice for her, i.e., the worst choice the number of ply D and the flag MaxMin. The last argument is general-
for me. My goal then is to make the move that maximizes for me the ized to return a record including both a move and a value rather than
value of the position after the opponent has made her best move, just a move. The minimax procedure does the bookkeeping, changing the
i.e., that minimizes the value for her. Hence the name minimax. This number of moves being looked ahead and also the minimax flag. The ini-
reasoning proceeds several ply ahead, depending on the resources that tial record is (nil,-1000), where nil represents an arbitrary move and
-1000 is a score intended to be less than any possible score of the evalu-
can be allocated to the search. At the last ply the evaluation function is
used. ation function.
Assuming a reasonable evaluation function, the algorithm will produce The observation about efficiency that was made about combining the
better results the more ply are searched. It will produce the best move if move generation and update procedures in the context of searchng
the entire tree is searched. state-space graphs has an analogue when searchmg game trees. Whether
The minimax algorithm is justified by a zero-sum assumption, which it is better to compute the set of positions rather than the set of moves
(with the corresponding change in algorithm) will depend on the particu-
says, informally, that what is good for me must be bad for my opponent,
and vice versa. lar application.
Figure 20.2 depicts a simple game tree of depth 2 ply. The player has The minimax algorithm can be improved by keeping track of the re-
sults of the search so far, using a t e c h q u e known as alpha-beta pruning.
two moves in the current position, and the opponent has two replies.
The values of the leaf nodes are the values for the player. The oppo- The idea is to keep for each node the estimated minimum value found so
nent wants to minimize the score, so will choose the minimum values, far, the alpha value, along with the estimated maximum value, beta. If,
on evaluating a node, beta is exceeded, no more search on that branch is
making the positions be worth + 1 and -1 at one level hgher in the tree.
necessary. In good cases, more than half the positions in the game tree
The player wants to maximize the value and will choose the node with
value + 1. need not be evaluated.
Chapter 20 Search Techniques

evaluate~and~choose(Moves,Position,Depth,Flag,Record,BestMove~ - evaluate~and~choose(Moves,Position,Depth,Alpha,Beta,Record,BestMove) -
Choose the BestMove from the set of Moves from the current Chooses the BestMove from the set of Moves from the current
Position using the minimax algorithm searching Depth ply ahead. Position using the minimax algorithm with alpha-beta cutoff searchmg
Flag indicates if we are currently minimizing or maximizing. Depth ply ahead. Alpha and Beta are the parameters of the algorithm.
Record records the current best move. Record records the current best move.
evaluate~and~choose([MoveIMovesl,Position,D,Alpha,Beta,Movel,
evaluate-and-choose( [Move l Moves] ,Position,D, ~ a x ~ i n , ~ e c o r d , ~ e s t )
move(Move,Position,Positionl),
+

BestMove) -
move (Move,Position,Positionl) ,
minimax(D,Positionl,MaxMin,MoveX,Value),
alpha-beta(D,Positionl,Alpha,Beta,MoveX,Value),
update(Move,Value,Record,Recordl),
Value1 is -Value,
evaluate~and~choose(Moves,Position,D,~ax~in,~ecordl,~est).
cutoff(Move,Valuel,D,Alpha,Beta,Moves,Position,Move1,BestMove).
evaluate-and-choose ( [ 1 ,Position,D,MaxMin ,Record,~ecord).
minirnax(0,Position,MaxMin,Mo~e,Value) - evaluate-and-choose([ 1,Position,D,Alpha,Beta,Move,(Move,Alpha)).
alpha~beta(0,Position,Alpha,Beta,Move,Value -
value(Position,V) ,
value(Position,Value).
Value is V*MaxMin.
minimax(D,Position,MaxMin,Mo~e,Value) - alpha~beta(D,Position,Alpha,Beta,Move,Value -
f indall (M,move(Position ,M),Moves),
D > 0,
Alpha1 is -Beta,
f indall(M,move(Position,M) ,Moves),
Beta1 is -Alpha,
Dl is D - 1 ,
Dl is D-1,
MinMax is -MaxMin,
evaluate~and~choose(Moves,Position,D1,Alphal,Beta1,nil,
evaluate~and~choose(Moves,Position,Dl,MinMax,
(nil,-1000),
(Move,Value)). i (Move,Value)).

update(Move,Value,Record,Recordl) - See Program 20.9. cutoff(Move,Value,D,Alpha,Beta,Moves,Position,Move1,~Move,Value)) -


Value 1 Beta.
Program 20.10 Choosing the best move ~ v i t hthe minimax algorithm cutoff(Move,Value,D,Alpha,Beta,Moves,Position,Movel,BestMove) -
Alpha < Value, Value < Beta,
evaluate~and~choose(Moves,Position,D,Value,Beta,Move,BestMove).
Program 20.11 is a modified version of Program 20.10 that incor-
porates alpha-beta pruning. The new relation scheme is alpha-beta
cutoff(Move,Value,D,Alpha,Beta,Moves,Position,Move1,BestMove) -
Value IAlpha,
(Depth,Position,Alpha, Beta,Move,Value), which extends minimax evaluate~and~choose(Moves,Position,D,Alpha,Beta,Movel,BestMove).
by replacing the minimax flag with alpha and beta. The same relation
Program 20.1 1 Choosing a move using minimax with alpha-beta pruning
holds with respect to evaluate-and-choose.
Unlike the one in Program 20.10, the version of evaluate-and-choose
in Program 20.1 1 does not need to search all possibilities. This is - - - - -- - - -

achieved by introducing a predicate cutoff,whch either stops searching 20.3 Background


the current branch or continues the search, updating the value of alpha
and the current best move as appropriate. Search techniques for both planning and game playing are discussed in
For example, the last node in the game tree in Figure 20.2 does not A1 textbooks. For further details of search strategies or the minimax algo-
need to be searched. Once a move with value -1 is found, which is rithm and its extension to alpha-beta pruning, see, for example, Nilsson
less than the value of + I the player is guaranteed, no other nodes can (1971) or Winston (1977).
contribute to the final score. Walter Wilson originally showed us the alpha-beta algorithm in Prolog.
The program can be generalized by replacing the base case of alpha-
beta by a test of whether the position is terminal. This is necessary in
chess programs, for example, for handling incomplete piece exchanges.
IV Applications

Prolog has been used for a wide range of applications: expert systems,
natural language understanding, symbolic algebra, compiler writing,
building embedded languages, and architectural design, to name a few.
In this part, we give a flavor of writing application programs in Prolog.
The first chapter looks at programs for playing three games: master-
mind, Nim, and Kalah. The next chapter presents an expert system for
evaluating requests for credit. The third chapter presents a program for
solving symbolic equations, and the final chapter looks at a compiler for
a Pascal-like language.
The emphasis in presentation in these chapters is on writing clear
programs. Knowledge embedded in the programs is made explicit. Minor
efficiency gains are ignored if they obscure the declarative reading of the
program.

Leonardo Da Vinci, The Proportions of the Human Figure, after Vitruvius. Pen
and ink. About 1492. Venice Academy.
Game-Playing Programs

Learning how to play a game is fun. As well as understanding the rules


of the game, we must constantly learn new strategies and tactics until
the game is mastered. Writing a program to play games is also fun, and
a good vehicle for showing how to use Prolog for w~itingnontrivial pro-
grams.

2 1.1 Mastermind

Our first program guesses the secret code in the game of mastermind. It
is a good example of what can be programmed in Prolog easily with just
a little thought.
The version of mastermind we describe is what we played as kids. It
is a variant on the commercial version and needs less hardware (only
pencil and paper). Player A chooses a sequence of distinct decimal digits
as a secret code-usually four digits for beginners and five for advanced
players. Player B makes guesses and queries player A for the number of
bulls (number of digits that appear in identical positions in the guess and
in the code) and cows (number of digits that appear in both the guess and
the code, but in different positions).
There is a very simple algorithm for playing the game: Impose some
order on the set of legal guesses; then iterate, making the next guess that
is consistent with all the information you have so far until you find the
secret code.
Chapter 21 Game-Playing Programs

Rather than defining the notion of consistency formally, we appeal to mastermind(Code) -


the reader's intuition: A guess is consistent with a set of answers to cleanup, guess (Code), check(Code) , announce.

queries if the answers to the queries would have remained the same if guess(Code) -
Code = CXl,X2,X3,X41, selects(Code,[1,2,3,4,5,6,7,8,9,01).
the guess were the secret code.
The algorithm performs quite well compared with experienced players: Verify the proposed guess
an average of four to six guesses for a code with four digits with an check(Guess) -
observed maximum of eight guesses. However, it is not an easy strategy not inconsistent (Guess) , ask(Guess)
for humans to apply, because of the amount of bookkeeping needed. On inconsistent(Guess) -
the other hand, the control structure of Prolog-nondeterministic choice, query(0ldGuess,Bulls,Cows),
not bulls~and~cows~match(OldGuess,Guess,Bulls,Cows).
simulated by backtraclung-is ideal for implementing the algorithm.
We describe the program top-down. The entire program is given as bulls~and~cows~match(OldGuess,Guess,Bulls,Cows)
exact-matches (OldGuess,Guess ,N1) ,
-
Program 21.1. The top-level procedure for playing the game is Bulls = : = N1, % Correct number of bulls
rnastermind(C0de) - common~members(OldGuess,Guess,N2),
Cows =:= N2-Bulls. % Correct number of cows
cleanup, guess (Code) , check(Code), announce

The heart of the top level is a generate-and-test loop. The guessing pro-
cedure guess(Code), whch acts as a generator, uses the procedure se-
lects(Xs,Ys) (Program 7.7)to select nondeterministically a list Xs of
elements from a list Ys. According to the rules of the game, Xs is con- same-place (X, CX I Xsl , [X I Ysl ) .
strained to contain four distinct elements, while Ys is the list of the ten same-place (A,[XI Xsl , [Y I Ysl ) - same-place (A,Xs ,Ys) .
decimal digits: Asking a guess

guess (Code) - repeat,


Code = [XI,X2,X3,X41 , writeln(['How many bulls and cows in ',Guess,'?']),
selects(Code, [1,2,3,4,5,6,7,8,9,0]) read( (Bulls ,Cows)) ,
sensible(Bulls,Cows), ! ,
The procedure check(Guess) tests the proposed code Guess. It first assert(query(Guess,Bulls,Cows)),
verifies that Guess is consistent with all (i.e.,not inconsistent with any) of Bulls =:= 4 .
the answers to queries already made; then it asks the user for the number sensible(Bulls,Cows) -
of bulls and cows in Guess. The ask(Guess) procedure also controls integer(Bulls), integer(Cows), Bulls+Cows 5 4.
the generate-and-test loop, succeeding only when the number of bulls is Bookkeeping
four, indicating the correct code is found: cleanup - abolish(query,3).
check(Guess) - announce -
not inconsistent (Guess), ask(Guess). size-of(X,query(X,A,B),N),
writeln(['Found the answer after ',N,' queries']).
Ask stores previous answers to queries in the relation query (X ,B ,C) , size-of (X,G,N)- findall(X,G,Xs) , length(Xs,N) .
where X is the guess, B is the number of bulls in it, and C the number length(Xs ,N) - See Program 8.11.
of cows. A guess is inconsistent with a previous query if the number of selects(X,Xs) - See Program 7.7.
bulls and cows do not match:
abolish(F,N) - See Exercise 12.5(i).
Program 2 1.1 Playlng mastermind
415 Game-Playing Programs
Chapter 21

inconsistent(Guess) - 21.2 Nirn


query(Old,Bulls,Cows),
not bulls~and~cows~match(Old,Guess,Bulls,Cow~~. We turn our attention now from mastermind to Nim, also a game for two
players. There are several piles of matches, and the players take turns
The bulls match between a previous guess OldGuess and a conjectured
removing some of the matches (up to all) in a pile. The winner is the
guess Guess if the number of digits in the same position in the two
player who takes the last match. Figure 21.1 gives a common starting
guesses equals the number of Bulls in OldGuess. It is computed by the
position, with piles of 1, 3, 5 and 7 matches.
predicate exact-matches (OldGuess ,Guess,Bulls). The cows match if
To implement the Nim-playing program, we use the game-playing
the number of common digits without respect to order corresponds to
framework of Program 20.8.
the sum of Bulls and Cows; it is computed by the procedure bulls-
The first decision is the representation of the game position and
and-cows-match. It is easy to count the number of matchmg digits and
the moves. A natural choice for positions is a list of integers where
common digits in two queries, using an all-solutions predicate size-
elements of the list correspond to piles of matches. A move is a tu-
of /3.
ple (N,M) for talung M matches from pile N. Writing the procedure
The ask(Guess) procedure is a memo-function that records the answer
move (Move ,Position,Positionl), where Position is updated to Posi-
to the query. It performs some limited consistency checks on the input
tionl by Move, is straightforward. The recursive rule counts down match
with the procedure sensible/2 and succeeds only if four bulls are indi-
piles until the desired pile is reached. The remaining piles of matches
cated. The expected syntax for the user's reply is a tuple (Bulls,Cows).
representing the new game position are computed routinely:
The remaining (top-level) predicates are for bookkeeping. The first,
cleanup, removes unwanted information from previous games. The
predicate announce tells how many guesses were needed, whch is de-
termined using size-of /3.
There are two possibilities for updating the specified pile of matches,
A more efficient implementation of the exact-matches and common-
the base case of the procedure. If all the matches are taken, the pile is
members procedures can be obtained by writing iterative versions:
removed from the list. Otherwise the new number of matches in the pile
is computed and checked to be legal:
-
exact-matches ( [X I XsI , [X I Ysl ,K,N) move((l,N>, [NINsI ,Ns).
~1 is K+I, exact-matches (XS ,Ys,K1,N)
-
exact-matches ( [X 1 Xsl , [Y I Ysl ,K,N)
-
m o v e ( ( 1 , ~ ) , ~ N l ~ s l , C ~ 1 l ~ s l )N > M, N1 is N-M.
The mechanics of turns for two-person games is specified by two facts.
x # Y, exact-matches(~s,Ys,K,N).
exact-matches ( [: 1 , 1 ,N,N) .

common-members ( [X I Xs] ,Ys ,K,N) -


rnember(x,Ys), K1 is K+l, common-members(Xs,Ys,Kl,N).
common-members ( [X I Xsl ,Ys ,K ,N)-
common-members (XS ,Ys ,K , N) .
common-members ( [ 1 ,Ys, N , N) .

Using the more efficient versions of exact-matches and common- Figure 21.1 A starting position for Nim
members saves about 10%-30%of the execution time.
Chapter 21 Game-Playing Programs

The initial piles of matches and who moves first must be decided by
the two players. Assuming the computer moves second, the game of
play (Game) - See Program 20.8.
Filling in the game-playing framework
Figure 21.1 is specified as

initialize (nim, [I,3,5,7] ,opponent) . display-game(Position,X) - write(Position), nl.


game-over([ l,Player,Player).
The game is over when the last match is taken. T h s corresponds to the
game position being the empty list. The person having to move next is announce(computer)
announce(opponent)
-- write('You won! Congratulations.'), nl.
write('1 won.'), nl.
the loser, and the output messages of announce are formulated accord-
Choosing moves
ingly. The details are in Program 21.2.
It remains to specify how to choose the moves. The opponent's moves choose~move(Position,opponent,Move) -
writeln(['Please make move']), read(Move), legal(Move,Position).
are accepted from the keyboard; how much flexibility is allowed in input
is the responsibility of the programmer: legal((K,N),Position) - nth-member(K,Position,M), N M.
I

choose-move (Posit ion,opponent,Move) - nth-member (1, [X I XS] ,X) .


nth-member(N,[XIXs],Y) - N > 1, N1 is N-1, nth-member(Nl,Xs,Y).
writeln(['Please make move']),
read (Move) ,
legal (Move,Position) .

Choosing a move for the computer requires a strategy. A simple strat-


evaluate(Position,Safety,Sum) -
nim-sum(Position, [ 1 ,Sum), saf ety(Sum,Saf ety)
egy to implement is talung all of the first pile of matches. It is recom-
mended only for use against extremely poor players:
safety(Sum, saf e)
safety(Sum,unsafe)
-- zero(Sum), ! .
not zero(Sum), !.

choose-move ( [N I NsI ,computer (1 ,N)) . decide-move(safe,Position,Sum,(1,1)).


% The computer's "arbitrary move"
A winning strategy is known for Nim. It involves dividing game states, decide~move(unsafe,Position,Sum,Move) -
safe~move(Position,Sum,Move).
or positions, into two classes, safe and unsafe. To determine if a position
is safe or unsafe, the binary representation of the number of matches move(Move,Position,Positionl) -
in each pile is computed. The nim-sum of these binary numbers is then Position1 is the result of executing the move
Move from the current Position.
calculated as follows. Each column is summed independently modulo 2.
If the total in each column is zero, the position is safe. Otherwise the move((K,M),[NINsl,[NINsll) -
K > 1, K1 is K-1, move((KI,M),Ns,Nsl).
position is unsafe. move((1 ,N) , [NI Nsl ,Ns) .
Figure 21.2 illustrates the process for the four piles of matches in
Figure 21.1. The binary representations of 1, 3, 5 , and 7 are 1, 11, 101,
move((l,M),[N1Nsl ,[NlINsl)
N > M, N1 is N-M.
-
and 111 respectively. Calculating the nim-sum: there are four 1's in the
units column, two 1's in the 2's column and two 1's in the 4's column;
an even number of 1's in each. The nim-sum is zero, malung the position Program 21.2 A program for playing a winning game of Nim
[1,3,5,7]safe. On the other hand the position [2,6]is unsafe. The binary
representations are 10 and 110. Summing them gives one 1 in the 4's
column and two 1's in the 2's column. The single 1 in the 4's column
makes the position unsafe.
Game-PlayingPrograms
Chapter 21

nim-sum (Position,SoFar,Sum) -
Sum is the nim-sum of the current Position,
and SoFar is an accumulated value.
nim-sum( [N I Nsl ,Bs,Sum) -
binary(N,Ds) , nim-add(Ds ,Bs,Bsl), nim-sum(Ns ,Bsl,Sum).
nim-sum( [ I ,Sum,Sum). Figure 2 1.2 Computing nim-sums
nim-add(Bs, [ 1 ,Bs).
nim-add( [ 1 ,Bs,Bs).
nim-add([~IBsl,[CICs], [DIDsl) - The winning strategy is to always leave the position safe. Any unsafe
D is (B+C) mod 2, nim-add(Bs,Cs,Ds). position can be converted to a safe position (though not all moves do),
binary (1,[ll ) . while any move from a safe position creates an unsafe one. The best
binary (N,[D I Dsl ) - strategy is to make an arbitrary move when confronted with a safe posi-
N > 1, D is N mod 2, N1 is N/2, binary(N1,Ds).
tion, hoping the opponent will blunder, and to convert unsafe positions
to safe ones.
The current position is evaluated by the predicate e v a l u a t e / 3 , whlch
determines the safety of the current position. An algorithm is needed
to compute the nim-sum of a position. The nim-sum is checked by the
zero([ I).
zero( [oI Zsl ) - zero(Zs1. predicate s a f e t y (Sum, Saf e t y ) , which labels the position safe or unsafe
safe-move(Position,NimSum,Move) - depending on the value of Sum.
Move is a move from the current Position with
the value N i m S u m that leaves a safe position.

safe-move([~ileI~iles],NimSum,K,(K,M)) - The move made by the computer computed by decide_move/4 de-


binary (Pile,Bs) , can-zero(Bs,NimSum,Ds,0) , decimal (Ds,MI
safe-move ( [Pile I piles] ,NimSum,K,Move) - pends on the safety of the position. If the position is safe, the computer
makes the "arbitrary" move of one match from the first pile. If the posi-
K1 is K+1, safe-move(Piles,NimSum,K1,~ove).
tion is unsafe, an algorithm is needed to compute a move that converts
can-zero( [ I ,NirnSum,[ 1,o) - an unsafe position into a safe one. This is done by s a f e_move/3.
zero(NimSum).
-
can-zero ( [B I Bsl , [O I NimSuml , [C 1 Dsl ,C) In a prior version of the program e v a l u a t e did not return Sum. In the
writing of s a f e-move it transpired that the nim-sum was helpful, and it
can-zero(Bs,NimSum,Ds,C).
can-zero( [B I Bsl , [I I Nim~uml, [D IDS] -
,C) was sensible to pass the already computed value rather than recomput-
D is 1-B*C, C1 is 1-B, can-zero(Bs,NimSum,Ds,~i). ing it.
The nim-sum is computed by nim-sum(Ns, SoFar ,Sum). The relation
Program 21.2 (Continued)
computed is that Sum is the nim-sum of the numbers N s added to what
has been accumulated in SoFar. To perform the additions, the numbers
must first be converted to binary, done by b i n a r y / 2 :
nim-sum ( [N I Nsl ,Bs ,Sum) -
b i n a r y (N, Ds) , nim-add(Ds ,B s ,B s l ) , nim-sum (Ns ,B s l ,Sum)
Chapter 21 Game-Playing Programs

The binary form of a number is represented here as a list of digits. To


overcome the difficulty of adding lists of unequal length, the least signif-
icant digits are earliest in the list. Thus 2 (in binary 10) is represented
as [0,1],whle 6 is represented as [0,1,1].The two numbers can then be
added from least significant digit to most significant digit, as is usual for
addition. This is done by nim_add/3 and is slightly simpler than regu-
lar addition, since no carry needs to be propagated. The code for both
binary and nim-add appears in Program 2 1.2.
The nim-sum Sum is used by the predicate saf e-move (Ns ,Sum ,Move) to
find a winning move Move from the position described by Ns. The piles of
matches are checked in turn by saf e_move/4 to see if there is a number
of matches that can be taken from the pile to leave a safe position. The
interesting clause is
safe-move ( [Pile l Piles1 ,NimSum,K,(K,M)) +
binary (Pile ,Bs) , can-zero (Bs ,NimSum,Ds20) .
Figure 21.3 Board positions for Kalah
The heart of the program is can-zero (Bs ,NimSum,Ds ,Carry). This re-
lation is true if replacing the binary number Bs by the binary number In the initial state there are six stones in each hole and the two kalahs are
Ds would make NimSum zero. The number Ds is computed digit by digit.
empty. This is pictured in the top half of Figure 21.3.
Each digit is determined by the corresponding digit of Bs, NimSum, and a
A player begins his move by piclung up all the stones in one of his
carry digit Carry initially set to 0. The number is converted to its decimal
holes. Proceeding counterclockwise around the board, he puts one of
equivalent by decimal/2 in order to get the correct move. the picked-up stones in each hole and in hls own kalah, skipping the
Program 21.2 is a complete program for playlng Nim interactively in- opponent's kalah, until no stones remain to be distributed. There are
corporating the winning strategy. As well as being a program for playlng three possible outcomes. If the last stone lands on the kalah, the player
the game, it is also an axiomatization of what constitutes a winning strat- has another move. If the last stone lands on an empty hole owned by
egy. the player, and the opponent's hole directly across the board contains at
least one stone, the player takes all the stones in the hole plus his last
landed stone and puts them all in his kalah. Otherwise the player's turn
21.3 Kalah ends, and his opponent moves.
The bottom kalah board in Figure 2 1.3 represents the following move
We now present a program for playing the game of Kalah that uses alpha- from the top board by the owner of the top holes. He took the six stones
beta pruning. Kalah fits well into the paradigm of game trees for two in the rightmost hole and distributed them, the last one ending in the
reasons. First, the game has a simple, reasonably reliable evaluation func- kalah, allowing another move. The stones in the fourth hole from the
tion, and second, its game tree is tractable, whch is not true for games right were then distributed.
such as chess and go. It has been claimed that some Kalah programs are If all the holes of a player become empty (even if it is not his turn to
unbeatable by human players. Certainly, the one presented here beats us. play), the stones remaining in the holes of the opponent are put in the
Kalah is played on a board with two rows of six holes facing each other. opponent's kalah and the game ends. The winner of the game is the first
Each player owns a row of six holes, plus a kalah to the right of the holes. player to get more than half the stones in his kalah.
Chapter 21 Game-Playing Programs

The difficulty for programming the game in Prolog is finding an effi- tion for a large number of stones. The pick-up-and-distribute pred-
cient data structure to represent the board, to facilitate the calculation icate is the generalization of distribute to handle these cases. The
of moves. We use a four-argument structure board (Holes,Kalah, Opp- predicate check-capture checks if a capture has occurred and updates
Holes, OppKalah), where Holes is a list of the numbers of stones in your the holes accordingly; updat e-kalah updates the number of stones in
six holes, Kalah is the number of stones in your kalah, and OppHoles the player's kalah. Some other necessary utilities such as n-substitute
and OppKalah are, respectively, the lists of the numbers of stones in the are also included in the program.
opponent's holes and the number of stones in his kalah. Lists were cho- The evaluation function is the difference between the number of stones
sen rather than six-place structures to facilitate the writing of recursive in the two kalahs:
programs for distributing the stones in the holes.
A move consists of choosing a hole and distributing the stones therein.
value(board(H,K,Y,L),Value) - Value is K-L
A move is specified as a list of integers with values between 1 and 6 The central predicates have been described. A running program is now
inclusive, where the numbers refer to the holes. Hole 1 is farthest from obtained by filling in the details for I/O, for initializing and terminating
the player's kalah, while hole 6 is closest. A list is necessary rather than the game, etc. Simple suggestions can be found in the complete program
a single integer because a move may continue. The move depicted in for the game, given as Program 21.3.
Figure 21.3 is [1,4]. In order to optimize the performance of the program, cuts can be
The code gives all moves on backtracking. The predicate stones-in- added. Another tip is to rewrite the main loop of the program as a failure-
hole (M,Board, N ) returns the number of stones N in hole M of the Board driven loop rather than a tail recursive program. This is sometimes nec-
if N is greater than 0, failing if there are no stones in the hole. The essary in implementations that do not incorporate tail recursion opti-
predicate extend-move (M ,Board,N ,Ms) returns the continuation of the mization and a good garbage collector.
move Ms. The second clause for move handles the special case when all
the player's holes become empty during a move.
Testing whether the move continues is nontrivial, since it may involve 2 1.4 Background
all the procedures for making a move. If the last stone is not placed in the
kalah, which can be determined by simple arithmetic, the move will end, The mastermind program, slightly modified, originally appeared in
and there is no need to distribute all the stones. Otherwise the stones are SIGART (Shapiro, 1983d) in response to a program for playing master-
distributed, and the move continues recursively. mind in Pascal. The SIGART article provoked several reactions, both
The basic predicate for making a move is distribute-stones (Stones, of theoretical improvements to algorithms for playing mastermind and
N ,Board,Boardl), which computes the relation that Board1 is obtained practical improvements to the program. Most interesting was an analy-
from Board by distributing the number of stones in Stones starting sis and discussion by Powers (1984) of how a Prolog program could be
from hole number N. There are two stages to the distribution, putting rewritten to good benefit using the mastermind code as a case study.
the stones in the player's holes, distribute-my-holes, and putting the Eventually, speedup by a factor of 50 was achieved.
stones in the opponent's holes, distribute-your-holes. A proof of the correctness of the algorithm for playing Nim can be
The simpler case is distributing the stones in the opponent's holes. found in any textbook discussing games on graphs, for example, Berge
The holes are updated by distribute, and the distribution of stones (1962).
continues recursively if there is an excess of stones. A check is made Kalah was an early A1 target for game-playing programs (Slagle and
to see if the player's board has become empty during the course of the Dixon, 1969).
move, and if so, the opponent's stones are added to his kalah.
Distributing the player's stones must take into account two possibili-
ties, distributing from any particular hole, and continuing the distribu-
Chapter 21 Game-Playing Programs

Play framework distribute~stones(Stones,Hole,Board,FinalBoard) -


play (Game) -See Program 20.8.
distribute~my~holes(Stones,Hole,Board,Board1,St0ne~l~,
distribute~your~holes(Stonesl,Board1,FinalBoard).
Choosing a move by minimax with alpha-beta cutoff
choose~move(Position,computer,~ove)
lookahead(Depth) ,
- board(Hs1 ,Kl,Ys,L),Stonesl)
Stones > 7-N, ! ,
-
distribute-my-holes(Stones ,N,board(Hs ,K,Ys,L) ,

alpha-beta(~e~th,Position,-40,40,Move,~alue), pick-up-and-distribute(N,Stones ,Hs,Hsl),


nl, write(Move), nl.
choose~move(Position,opponent,Mo~e) - K1 is K+1, Stones1 is Stones+N-7.
distribute-my-holes(Stones ,N,board(Hs ,K,Ys,L),Board,O) -
nl, writeln( ['please make move'] 1, read(Move), legal(Move) Stones 5 7-N,
alpha-beta(Depth,Position,Alpha,Beta,Move,Value - pick-up-and-distribute(N ,Stones,Hs,Hsl),
check~capture(~,~tones,Hsl,Hs2,Ys,~sl,Pieces),
See Program 20.1 1.
move (Board,[M 1 Ms] ) - update-kalah(Pieces ,N,Stones,K,Kl),
check-if-finished(board(Hs2,K1,~sl,L),Board).
member(M, [1,2,3,4,5,61),
stones-in-hole(M,Board,N), check-capture(N,Stones,Hs,Hsl,Ys,Ysl,Pieces)
FinishingHole is N+Stones,
-
extend-move(N,M,Board,Ms).
move(board([0,0,0,0,0,0~,K,Ys,L),[ I). nth-member(FinishingHole,Hs,l),

stones-in-hole(M,board(Hs ,K,Ys,L),Stones) - OppositeHole is 7-FinishingHole,


nth-member(OppositeHole,YS ,Y),
nth-member(M,Hs,Stones), Stones > 0. Y > O , !,
extend-move(Stones,M,Board,[ 1) - n-substitute(OppositeHole,Ys,O,Ysl),
n-substitute(FinishingHole,Hs,O,Hsl),
Stones =\= (7-M) mod 13, ! .
extend-move(Stones,M,Board,Ms)
Stones =:= (7-M) mod 13, ! ,
- Pieces is Y+1.
check~capture(N,Stones,Hs,Hs,Ys,Ys,O) -
!.
distribute~stones(Stones,M,Board,Boardl), check-if-finished(board(Hs,K,Y~,L),board(Hs,K,Hs,L1)) -
move(Board1,Ms).
Executing a move
zero(Hs) , ! , sumlist(YS ,YsSum), L1 is L+YsSum.
check-if-f inished(board(Hs ,K,Ys,L),board(Ys ,K1,Ys,L)) -
move([NINs],Board,FinalBoard) -
stones-in-hole(N ,Board,Stones),
zero(Ys), ! , sumlist(Hs,HsSum) , K1 is K+HsSum.
check-if-finished(Board,~oard) - !.
distribute-stones(Stones,N,Board,Boardl), update-kalah(O,Stones,N,K,K) --
Stones < 7-N, ! .
Stones =:= 7-N, ! , K1 is K+1.
-
move(Ns,Boardl,FinalBoard).
move ( [ I ,Boardl,Board21
update-kalah(O,Stones,N,K,Kl)
update-kalah(Pieces,Stones,N,K,Kl) - Pieces > 0, ! , K1 is K+Pieces.
swap(Eoardl,Board2). distribute-your-holes(O,Board,Board) - !.
-
distribute-stones(Stones,Hole,Board,Boardl) -
Board1 is the result of distributing the number of stones
distribute-your-holes(Stones,board(Hs,K,Ys,L) ,board(~s,K,Ysl,L))
1 5 Stones, Stones 5 6,
Stones from Hole from the current Board. non-zero(Hs) , ! ,
It consists of two stages: distributing the stones in the player's distribute(Stones,Ys,Ysl).
holes, distribute-my-holes, and distributing the stones
in the opponent's holes, distribute-yourholes. Program 2 1.3 (Continued)

Program 21.3 A complete program for playing Kalah


Chapter 21 Game-Playing Programs

distribute~your~holes(Stones,board(Hs,K,Ys,L~,Board~
Stones > 6, ! ,
-
distribute(6,Ys,Ysl),
Stones1 is Stones-6,
distribute~stones(Stonesl,O,board(~s,~,Ys1,L~,Board~.
distribute-your-holes(Stones ,board(Hs,K,Ys,L),board(~s,K,Hs,~l))
zero(Hs) , ! , sumlist(Ys,YsSum), L1 is Stones+YsSum+L.
-
Lower-level stone distribution
pick-up-and-distribute(O,N,Hs ,Hsl) -
! , distribute(N,Hs,Hsl).
pick-up-and-distribute(l , N , [HI Hsl , [O IHsll ) - show(board(H,K,Y,L)) -
reverse (H,HR), write-stones (HR) ,
! , distribute(N,Hs,Hsl).
pick-up-and-distribute(K,N , [H 1 Hsl , [HI Hsll ) - write-kalahs(K,L), write-stones(Y).
-
K > 1, ! , K1 is K-1, pick-up-and-distribute(~l,N,Hs,Hsl) write-stones(H)
nl, tab(5) , display-holes(HI

Evaluation function
value(board(H,K,Y,L),Value) - Value is K-L
write-kalahs(K,L) -
Testing for the end o f the game
game-over(board(O,N,O,N),Player,draw) - write(K), tab(34), write(L),
zero([0,0,0,0,0,01).
nl.

pieces(K), N =:= 6*K, ! .


game-over(board(~,K,Y,L),Player,Player) - non-zero(Hs) -Hs f [0,0,0,0,0,01
Initializing
pieces(N), K > 6*N, ! .
game-over (board(H ,K,Y ,L) ,Player,Opponent) -
pieces(N), L > 6*N, next-player(Player,Opponent).
announce(opponent)
announce(computer)
-- writeln(1'You won! Congratulations.'~).
writeln(['I won.']).
announce(draw1 -writeln(['The game is a draw']).
Miscellaneous game utilities Program 2 1.3 (Continued)
nth-member (N , [HIHsl ,K) -
N > 1, ! , N1 is N - 1 , nth-member(Nl,Hs,K).
nth-member (1,[HI Hsl ,H) .
n-substitute(l,[XlXsl,Y,[YIXsl)
n-substitute(N, [XIXsl ,Y,[XI Xsll)
!. --
N > 1, ! , N1 is N-1, n-substitute(Nl,Xs,Y,Xsl).

Program 21.3 (Continued)


A Credit Evaluation Expert System

When the first edition of t h s book was published, there was a surge of
activity in the application of artificial intelligence to industry. Of partic-
ular interest were expert systems-programs designed to perform tasks
previously allocated to hghly paid human experts. One important fea-
ture of expert systems is the explicit representation of knowledge.
Thls entire book is relevant for programming expert systems. The ex-
ample programs typify code that might be written. For instance, the
equation-solving program of Chapter 23 can be, and has been, viewed as
an expert system. The knowledge of expert systems is often expressed as
rules. Prolog whose basic statements are rules is thus a natural language
for implementing expert systems.

- -

22.1 Developing the System

T h s chapter presents an account of developing a prototype expert sys-


tem. The example comes from the world of banlung: evaluating requests
for credit from small business ventures. We give a fictionalized account
of the development of a simple expert system for evaluating client re-
quests for credit from a bank. The account is from the point of view of
Prolog programmers, or knowledge engineers, commissioned by the bank
to write the system. It begins after the most difficult stage of building an
expert system, extracting the expert knowledge, has been under way for
some time. In accordance with received wisdom, the programmers have
been consulting with a single bank expert, Chas E. Manhattan. Chas has
Chapter 22 A Credit Evaluation Expert System

told us that three factors are of the utmost importance in considering a This information is sufficient to build a prototype. We show how these
request for credit from a client (a small business venture). comments and observations are translated into a system. The top-level
The most important factor is the collateral that can be offered by the basic relation is credit (Client,Answer), where Answer is the reply
client in case the venture folds. The various types of collateral are di- given to the request by Client for credit. The code has three modules-
vided into categories. Currency deposits, whether local or foreign, are collateral, f inancial-rating, and bank-yield-corresponding to the
first-class collateral. Stocks are examples of second-class collateral, and three factors the expert said were important. The initial screening to
the collateral provided by mortgages and the like is illiquid. determine that the client is worth considering in the first place is per-
Also very important is the client's financial record. Experience in the formed by the predicate ok-prof ile(C1ient). The answer Answer is
bank has shown that the two most important factors are the client's then determined with the predicate evaluate (Profile ,Answer), which
net worth per assets and the current gross profits on sales. The client's evaluates the Profile built by the three modules.
short-term debt per annual sales should be considered in evaluating the Being proud knowledge engineers, we stress the features of the top-
record, and slightly less significant is last year's sales growth. For knowl- level formulation in credit/2. The modularity is apparent. Each of the
edge engineers with some understanding of banlung, no further expla- modules can be developed independently without affecting the rest of
nation of such concepts is necessary. In general, a knowledge engineer the system. Further, there is no commitment to any particular data struc-
must understand the domain sufficiently to be able to communicate with ture, i.e., data abstraction is used. For this example, a structure pro-
the domain expert. file (C,F,Y) represents the profile of collateral rating C, the financial
The remaining factor to be considered is the expected yield to the rating F, and the yield Y of a client. Ho\z,e\.er, nothing central depends
bank. This is a problem that the bank has been worlung on for a while. on this decision, and it would be easy to change it. Let us consider some
Programs exist to give the yield of a particular client profile. The knowl- of the modular pieces.
edge engineer can thus assume that the information mill be available in Let us look at the essential features of the collateral evaluation module.
the desired form. The relation collateral_rating/2 determines a rating for a particu-
Chas uses qualitative terms in speaking about these three factors: "The lar client's collateral. The first step is to determine an appropriate pro-
client had an excellent financial rating, or a good form of collateral. His file. This is done with the predicate collateral-prof ile, which classi-
venture would provide a reasonable yield," and so on. Even concepts that fies the client's collateral as first-class, second-class, or illiquid
could be determined quantitatively are discussed in qualitative terms. and gives the percentage each covers of the amount of credit the
The financial world is too complicated to be expressed only with the client requested. The relation uses facts in the database concerning
numbers and ratios constantly being calculated. In order to make judg- both the bank and the client. In practice, there may be separate data-
ments, experts in the financial domain tend to t h n k in qualitative terms bases for the bank and the client. Sample facts shown in Program
with whch they are more comfortable. To echo expert reasoning and to 22.1 indicate, for example, that local currency deposits are first-class
be able to interact with Chas further, qualitative reasoning must be mod- collateral.
eled. The profile is evaluated to give a rating by collateral-evaluation. It
On tallung to Chas, it became clear that a significant amount of the uses rules of thumb to give a qualitative rating of the collateral: excellent,
expert knowledge he described could be naturally expressed as a mixture good, etc. The first collateral-evaluation rule, for example, reads:
of procedures and rules. On being pressed a little in the second and third "The rating is excellent if the coverage of the requested credit amount
interviews, Chas gave rules for determining ratings for collateral and by first-class collateral is greater than or equal to 100 percent."
financial records. These involved considerable calculations, and in fact, Two features of the code bear comment. First, the terminology used
Chas admitted that to save lvmself work in the long term, he did a quick in the program is the terminology of Chas. This makes the program (al-
initial screening to see if the client was at all suitable. most) self-documenting to the experts and means they can modify it with
A Credit Evaluation Expert System
Chapter 22

Financial rating
Credit Evaluation
credit(Client,Answer) - financial-rating ( Client,Rating) -
Rating is a qualitative description assessing the financial
Answer is the reply to a request by Client for credit.
credit(Client,Answer)
ok-profile(Client),
- record offered by Client to support the request for credit.
financial-rating(Client,Rating) -
financial~factors(Factors),
collateral~rating(C1ient,CollateralRating~,
score(Factors,Client,O,Score),
financial-rating(Client,FinancialRating), calibrate(Score,Rating).
bank-yield(Client,Yield),
,Yield) ,Answer)
evaluate (profile( ~ o l l a t e r a l ~ a t i~inancialRating
n~, Financial evaluation rules
The collateral rating module calibrate(Score,bad) -Score < -500.

collateral-rating (Client,Rating) -
Rating is a qualitative description assessing the collateral
calibrate(Score,medium)
calibrate(Score,good) -- -
calibrate(Score,excellent)
-500 < Score, Score < 150.
150 IScore, Score < 1000.
Score 2 1000.
offered by Client to cover the request for credit.
collateral-rating(C1ient ,Rating) -
collateral~profile(Client,FirstClass,~econd~lass,Illiquid),
Bank data - weighting factors
financial-factors([(net-worth_per_assets,5),
collateral~evaluation(FirstClass,SecondClass,Illiquid,Rating). (last-year-sales-growth,1) ,

collateral~profile(Client,FirstClass,SecondClass,~lliquid~ - (gross~profits~on~sales,5),
(short-term-debt-per_annual_sales,2) 1 ) .
requested-credit(Client,Credit),
collateral~percent(first~class,~lient,~redit,~irstCla~~~, score([(Factor,Weight)lFactorsl,Client,Acc,Score)
value(Factor,Client,Value),
-
collateral~percent(second~class,Client,~redit,~econdClass~,
collateral~percent(illiquid,Client,~redit,Illiq~id~. Accl is Acc + WeighttValue,

collateral~percent(Type,Client,Total,Value~
findall(X,(collatera1(Collateral,Type),
- score(Factors,Client,Accl,Score).
score([ l,Client,Score,Score).
amount (Collatera1,Client,XI),Xs), Final evaluation
sumlist (Xs,Sum) , evaluate(Profile,Outcome) -
Value is Sum*lOO/Total. Outcome is the reply to the client's Profile
Evaluation rules
collateral~evaluation(FirstClass,SecondClass,~lliquid,ex~ellent~
FirstClass 2 100.
-
collateral~evaluation(FirstClass,SecondClass,~lliquid,e~cellent) -
FirstClass > 7 0 , FirstClass + SecondClass 2 100.
collateral~evaluation(FirstClass,SecondClass,Illiq~id,g~~d~
FirstClass + SecondClass > 60,
-
FirstClass + SecondClass < 7 0 ,
FirstClass + SecondClass + Illiquid 2 100.
compare('=',Scale,Rating,Rating).
Bank data - classification ofcollateral compare( ' > ' ,Scale,Rat ingl ,Rating21 -
collateral(local~currency~deposits,first~class~.
collateral(foreign~currency~deposits,first~~la~~~.
precedes(Scale,Ratingl,Rating2).
compare('>',Scale,Ratingl,Rating2) -
precedes(Scale,Ratingl,Rating2) ; Rating1 = Rating2
collateral(negotiate~instruments,second~cla~s).
collateral(mortgage,illiquid).
Program 22.1 (Continued)
Program 22.1 A credit evaluation system
Chapter 22 A Credit Evaluation Expert System

expert, Chas Manhattan, rather than a universal truth. Withn the bank
there is no consensus about the subject. Some people tend to be conser-
vative and some are prepared to take considered risks.
Programming the code for determining the collateral and financial rat-
precedes( [Rl I Rs] ,R1 .R2) . ings proceeded easily. The knowledge provided by the expert was more
precedes ( [R I Rs] ,R1 ,R2) -Rf R2, precedes (Rs ,R1 ,R2)
or less directly translated into the program. The module for the overall
select-value (collateral ,profile(C ,F ,Y) ,C) . evaluation of the client, however, was more challenging.
select-value(finances,profile(~,~,~),~). The major difficulty was formulating the relevant expert knowledge.
select-value(yield,profile(C,F,Y),Y).
Our expert was less forthcoming with general rules for overall evaluation
Utilities than for rating the financial record, for example. He happily discussed
sumlist (XS,sum) - See Program 8.6b. the profiles of particular clients, and the outcome of their credit requests
Rank data and rules and loans, but was reluctant to generalize. He preferred to react to sug-
rule([condition(collateral,'~',excellent~, gestions rather than volunteer rules.
condition(finances,'2',good), T h s forced a close reevaluation of the exact problem we were solving.
condition(yield,'2',reasonable)] ,give-credit). There were three possible answers the system could give: approve the
rule( [condition(collateral, '=' ,good) ,condition(f inances, I = ' ,good), request for credit, refuse the request, or ask for advice. There were three
condition(yield,'L',reasonable)],consult~superi~~~.
rule([condition(collateral,'~',moderate),
factors to be considered. Each factor had a qualitative value that was one
condition(f inances, ' 5 ' ,medium)] , of a small set of possibilities. For example, the financial rating could
refuse-credit). be bad, medium, good, or excellent. Further, the possible values were
ranked on an ordinal scale.
Our system clearly faced an instance of a general problem: Find an
outcome from some ordinal scale based on the qualitative results of
several ordinal scales. Rules to solve the problem were thus to give a
Program 22.1 (Continued)
conclusion based on the outcome of the factors. We pressed Chas with
t h s formulation, and he rewarded us with several rules. Here is a typical
little help from the knowledge engineer. Allowing people to think in do- one: "If the client's collateral rating is excellent (or better), her financial
main concepts also facilitates debugging and assists in using a domain- rating good (or better), and her yield at least reasonable, then grant the
independent explanation facility as discussed in Section 17.4. Second, the credit request."
apparent naivete of the evaluation rules is deceptive. A lot of knowledge An immediate translation of the rule is
and experience are hidden behnd these simple numbers. Choosing poor
values for these numbers may mean suffering severe losses.
The financial evaluation module evaluates the financial stability of the
client. It uses items taken mainly from the balance and profit/loss sheets. But this misses many cases covered by the rule, for example, when the
The financial rating is also qualitative. A weighted sum of financial fac- client's profile is (excellent,good, excellent 1. All the cases for a given
tors is calculated by score and used by calibrate to determine the rule can be listed. It seemed more sensible, however, to build a more
qualitative class. general tool to evaluate rules expressed in terms of qualitative values
It should be noted that the modules giving the collateral rating and the from ordinal scales.
financial rating both reflect the point of view and style of a particular
Chapter 22 A Credit Evaluation Expert System

There is potentially a problem with using ordinal scales because Client Data
of the large number of individual cases that may need to be speci-
fied. If each of the N modules have M possible outcomes, there are
NM cases to be considered. In general, it is infeasible to have a sep-
arate rule for each possibility. Not only is space a problem for so
many rules but the search involved in finding the correct rule may
be prohibitive. So instead we defined a small ad hoc set of rules. We
hoped the rules defined, which covered many possibilities at once,
would be sufficient to cover the clients the bank usually dealt with. amount (mortgage,client 1,12000) .
amount(documents,client1,14000)
We chose the structure rule(Conditions,Conclusion) for our rules,
where Conditions is a list of conditions under whch the rule applies valuehet-worth-per-assets,clientl,40).
value(last-year-sales-growth,clientl,20).
and Conclusion is the rule's conclusion. A condition has the form con-
value(gross-profits-on-sales,clientl,45).
dition(Factor ,Relation, Rating), insisting that the rating from the value ( s h o r t - t e r m - d e b t - p e r - a n u a l - s a l e s , client l,9).
factor named by Factor bears the relation named by Relation to the
rating given by Rating.
The relation is represented by the standard relational operators: <, =, Program 22.2 Test data for the credit evaluation system
>, etc. The previously mentioned rule is represented as
rule ( [condition(collateral, ' 2 ' ,excellent), The interpreter for the rules is written nondeterministically. The pro-
condition(f inances, ' 2 ' ,good), cedure is: "Find a rule and verify that its conditions apply," as defined
condition(yield, ' 2 ' ,reasonable)] ,give-credit) by evaluate. The predicate verify (Conditions ,Profile) checks that
the relation between the corresponding symbols in the rule and the ones
Another rule given by Chas reads: "If both the collateral rating and fi- that are associated with the Profile of the client is as specified by Con-
nancial rating are good, and the yield is at least reasonable, then consult ditions. For each Type that can appear, a scale is necessary to give
your superior." This is translated to the order of values the scale can take. Examples of scale facts in the
rule ( [condition(collateral, '=' ,good), bank database are scale (collateral, [excellent ,good,moderatel )
condition (finances , '=' ,good) , and scale (finances , [excellent, good ,medium,bad1 ). The predicate
.
condition(yield, ' 2 ' ,reasonable)] ,consult-s~~erior) select-value returns the appropriate symbol of the factor under the or-
dinality test that is performed by compare. It is an access predicate, and
Factors can be mentioned twice to indicate they lie in a certain range or consequently the only predicate dependent on the choice of data struc-
might not be mentioned at all. For example, the rule ture for the profile.
At t h s stage, the prototype program is tested. Some data from real
rule ( [condition(collateral, ' 5 ' ,moderate),
clients are necessary, and the answer the system gives on these individ-
condition (finances , ' 5 ' ,medium)1 ,
uals is tested against what the corresponding bank official would say.
ref use-credit) .
The data for clientl is given in Program 22.2. The reply to the query
states that a client should be refused credit if the collateral rating is no credit (client1 ,X) is X = give-credit.
better than moderate and the financial rating is at best medium. The Our prototype expert system is a composite of styles and methods -
yield is not relevant and so is not mentioned. not just a backward chaining system. Heuristic rules of thumb are used
Chapter 22

to determine the collateral rating; an algorithm, albeit a simple one, is


used to determine the financial rating; and there is a rule language, with An Equation Solver
an interpreter, for expressing outcomes in terms of values from discrete
ordinal scales. The rule interpreter proceeds forward from conditions
to conclusion rather than backward as in Prolog. Expert systems must
become such composites in order to exploit the different forms of knowl-
edge already extant.
The development of the prototype was not the only activity of the
knowledge engineers. Various other features of the expert system were
developed in parallel. An explanation facility was built as an extension of
Program 17.22. A simulator for rules based on ordinal scales was built
to settle the argument among the knowledge engineers as to whether a A very natural area for Prolog applications is symbolic manipulation. For
reasonable collection of rules would be sufficient to cover the range of example, a Prolog program for symbolic differentiation, a typical symbol
outcomes in the general case. manipulation task, is just the rules of differentiation in different syntax,
Finally, a consistency checker for the rules was built. The following as shown in Program 3.30.
meta-rule is an obvious consistency principle: "If all of client A's factors In this chapter, we present a program for solving symbolic equations. It
are better than or equal to client B's, then the outcome of client A must is a simplification of PRESS (PRolog Equation Solving System), developed
be better than or equal to that of client B." in the mathematical reasoning group of the Department of Artificial In-
telligence at the University of Edinburgh. PRESS performs at the level of
a mathematics student in her final year of high school.
2 2.2 Background The first section gives an overview of equation solving with some exam-
ple solutions. The remaining four sections cover the four major equation-
More details on the credit evaluation system can be found in Ben-David solving methods implemented in the equation solver.
and Sterling (1986).

23.1 An Overview of Equation Solving

The task of equation solving can be described syntactically. Given an


equation Lhs = Rhs in an unknown X, transform the equation into an
equivalent equation X = Rhsl, where Rhsl does not contain X. Thls final
equation is the solution. Two equations are equivalent if one is trans-
formed into the other by a finite number of applications of the axioms
and rules of algebra.
Successful mathematics students do not solve equations by blindly
applying axioms of algebra. Instead they learn, develop, and use various
methods and strategies. Our equation solver, modeling t h s behavior, is
accordingly a collection of methods to be applied to an equation to be
Chapter 23 An Equation Solver

until the single occurrence of the unknown is isolated on the left-hand


(i) cos(x) . (1- 2 . sin(x)) = 0
side of the equation. Isolation solves 1 - 2 . sin(x) = 0 by producing the
(ii) x 2 - 3 - x + 2 = 0 preceding sequence of equations.
-
(iii) 22'x- 5 2X+1+ 16 = 0 Equation (ii) in Figure 23.1, x2- 3 . x + 2 = 0, is a quadratic equation in
x. We all learn in high school a formula for solving quadratic equations.
Figure 23.1 Test equations The discriminant, b2 - 4 . a . c , is calculated, in this case (-3)2 - 4 . 1 . 2,
which equals 1, and two solutions are given: x = ( - ( - 3) + JT)/ 2, which
equals 2, and x = (-(-3) - dl)12, whch equals 1.
solved. Each method transforms the equation by applying identities of The key to solving equation (iii) in Figure 23.1 is to realize that the
algebra expressed as rewrite rules. The methods can and do take widely equation is really a quadratic equation in ZX. The equation 2?.* - 5 .
different forms. They can be a collection of rules for solving the class of ZX+' + 16 = 0 can be rewritten as (2X)2- 5 . 2 . 2X + 16 = 0. This can be
equations to whlch the method is applicable, or algorithms implementing solved for 2X,giving two solutions of the form ZX =Rhs, where Rhs is
a decision procedure. free of x. Each of these equations are solved for x to give solutions to
Abstractly, a method has two parts: a condition testing whether the equation (iii).
method is applicable, and the application of the method itself. PRESS was tested on equations taken from British A-level examinations
The type of equations our program can handle are indicated by the in mathematics. It seems that examiners liked posing questions such
three examples in Figure 2 3.1. They consist of algebraic functions of the as equation (iii), which involved the student's manipulating logarithmic,
unknown, that is +, -, *, /, and exponentiation to an integer power, and exponential, or other transcendental functions into forms where they
also trigonometric and exponential functions. The unknown is x in all could be solved as polynomials. A method called homogenization evolved
three equations. to solve equations of these types.
We briefly show how each equation is solved. The aim of homogenization is to transform the equation into a poly-
The first step in solving equation (i) in Figure 23.1 is factorization. The nomial in some term containing the unknown. (We simplify the more
problem to be solved is reduced to solving cos(x) = 0 and 1 - 2 . sin(x) = general homogenization of PRESS for didactic purposes.) The method
0. A solution to either of these equations is a solution to the original consists of four steps, whch we illustrate for equation (iii). The equa-
equation. tion is first parsed and all maximal nonpolynomial terms containing the
Both the equations cos(x) = 0 and 1 - 2 . sin(x) = 0 are solved by mak- unknown are collected with duplicates removed. This set is called the of-
ing x the subject of the equation. T h s is possible because x occurs once fenders set. In the example, it is {Z2*,ZX+'}.The second step is finding a
in each equation. term, known as the reduced term. The result of homogenization is a poly-
The solution to cos(x) = 0 is arccos(0). The solution of 1 - 2 . sin(x) = nomial equation in the reduced term. The reduced term in our example is
0 takes the following steps: ZX. The third step of homogenization is finding rewrite rules that express
each of the elements of the offenders set as a polynomial in the reduced
term. Finding such a set guarantees that homogenization will succeed. In
our example the rewrite rules are 22X= ( 2 X ) 2and ZX+' = 2 . ZX. Finally,
the rewrite rules are applied to produce the polynomial equation.
We complete this section with a brief overview of the equation solver.
In general, equations with a single occurrence of the unknown can be The basic predicate is solve-equation (Equation,X , Solution). The re-
solved by an algorithrmc method called isolation. The method repeatedly lation is true if Solution is a solution to Equation in the unknown X. The
applies an appropriate inverse function to both sides of the equation complete code appears as Program 23.1.
Chapter 23 An Equation Solver

solve-equation(Equation,Unknown,Solution) -
Solution is a solution to the equation Equation
The isolation method
maneuver-sides(1,Lhs = Rhs,Lhs = Rhs) - !.
in the unknown Unknown. maneuver-sides(2,Lhs = Rhs,Rhs = Lhs) !.
-
+-

solve~equation(A*B=0,X,Solution)
!,
f actorize(A*B,X,Factors\ [ I ) ,
remove-duplicates (Factors,Factorsl) ,
solve~factors(Factorsl,X,Solution).
solve~equation(Equation,X,Solution)
single-occurrence (X,Equation),
- Axioms for isolation
isolax(1,-Lhs = Rhs,Lhs = -Rhs). % Unary minus
isolax(l,Terml+Term2 = Rhs,Terml = Rhs-Term2). % Addition
isolax(2,Terml+Term2 = Rhs,Term2 = Rhs-Terml). % Addition
isolax(1,Terml-Term2 = Rhs,Terml = Rhs+Term2). % Subtraction
isolax(2,Terml-Term2 Rhs,Term2 Terml-Rhs). % Subtraction
-
= =
solve~equation(Lhs=Rhs,X,Solution)
polynomial (Lhs,X),
isolax(l,Terml*Term2 = Rhs,Terml = Rhs/Term2) - % Multiplication
Term2 f 0 .
polynomial(Rhs,X), isolax(2,Terml*Term2 = Rhs,Term2 = Rhs/Terml) - % Multiplication
! 9
Term1 # 0 .
polynomial-normal-form(Lhs-Rhs,X,PolyForm),
solve~polynomial~equation(PolyForm,X,~oluti~n~. isolax(l,Terml~Term2= Rhs,Terml = RhsT(-Term2)).
% Exponentiation
isolax(2,TermllTerm2 = Rhs,Term2 = log(base(Terml),Rhs)).
% Exponentiation
% Sine
% Sine
% Cosine
The factorization method %
factorize(Expression,Subterm,Factors) - The polynomial method
Cosine

Factors is a difference-list consisting of the factors of


the multiplicative term Expression that contain the Subterm. polynomial(Term,X) - See Program 11.4.
polynomial- normal-form (Expression,Term,PolyNormalForm) -
PolyNormalk'orm is the polynomial normal form of
Expression,which is a polynomial in Term.

solve-factors (Factors,Unknown,Solution) -
Solution is a solution of the equation Factor = 0 in the
U n k n o w n for some Factor in the list of Factors.
solve-f actors( [Factor l Factors] ,X,solution) -
solve~equation(Factor=O,X,~oluti~n).
-
solve-f actors( [Factor I Factors] , ~ , ~ o l u t i o n )
solve~factors(Factors,X,Solution).

Program 23.1 A program for solving equations Program 23.1 (Continued)


Chapter 23 An Equation Solver

polynomial-form(Term1-Term2, X ,PolyForm) - multiply-polynomials(Polyl,PolyZ,Poly) -


Poly is the product of Polyl and Poly2,where Polyl,
polynomial-form(Term1,X,PolyForml),
polynomial-form(~erm2,X,PolyForm2), Poly2,and Poly are all in polynomial form.
-
subtract~polynomials(PolyForm1,PolyForm2,~olyForm)
polynomial-form(~erml*Term2,X ,~ o l ~ ~ o r m )
polynomial~form(~erml,~,~oly~orml),
polynomial~form(~erm2,X,~oly~orm2),
multiply-polynomials(PolyForml ,PolyForm2,~ o l ~ ~ o r m )
polynomial-form(~erm~~,X,~ol~~orm) - !,
polynomial-form(Term,X,PolyForml),
binomial(PolyForml,N,PolyForm). Polynomial equation solver
polynomial-form(Term,X, [(Term,O)l) - solve-polynomial-equation (Equation,Unknown,Solution) -
free-of(X,Term), ! . Solution is a solution to the polynomial Equation in the unknown
remove-zero-terms( [ (0,N) I Polyl ,Polyl) - Unknown.
! , remove-zero-terms(Po1y ,Polyl) .
remove-zero-terms([(~,~)IPoly] ,[(c,N) I~olyll) -
C f 0 , ! , remove-zero-terms(~o1~ ,~olyl).
remove-zero-terms( [ I , C I ) .
Polynomial manipulation routines
add-polynomials (Polyl,PolyZ,Poly) -
Poly is the sum of Polyl and Poly2,where Polyl,
PolyZ,and Poly are all in polynomial form.
add-polynomials( [ 1 ,Poly,Poly) -
!.
add-polynomials(P~ly,[ ],Poly) - !.
-
add-polynomials( [(Ai ,Ni) I Polyl] , [(Aj ,Nj)1 ~ 0 1 ~ 2, 1[(~i,Nill~olyl)
Ni > Nj , ! , add-polynomials(Poly1, [(Aj , ~ j 1)~ 0 1 ~ 2,~oly).
1
add-polynomials( [(Ai ,Ni)I Polyl] , [(Aj ,Nj) ~ 0 1 ~ ,2[(A,N~)
1 -
1~01~1)
Ni = : = Nj , ! , A is Ai+Aj , add-polynomials(Polyl ,pol@ ,~oly).
add-polynomials( [(Ai ,Ni) l Polyll , [(Aj ,Nj)lPoly21 , [(Aj ,Nj) lpolyl) +
Ni < Nj , ! , add-polynomials( [(Ai ,Ni)1 Polyll , P O ~ ~ ~ , P O ~ Y ) .
subtract-polynomials(Polyl,Poly2,Poly) -
Poly is the difference of Polyl and PolyZ,where Polyl,
Poly2,and Poly are all in poljnomial form.
subtract-polynomials(Polyl ,Poly2,Poly) - The homogenization method
multiply-single(Poly2, (-1 ,o),~oly3),
add-polynomials(Polyi ,Poly3,~oly), ! . homogenize(Equation,XEquation1,XI) -
multiply-single(Polyl,Monomial,Poly) -
Poly is the product of Polyl and Monomial,where Polyl
The Equation in X is transformed to the polynomial
Equation1 in X 1 where X 1 contains X.

and Poly are in polynomial form, and Monomial has the


form ( C , N ) denoting the monomial C*X"'.
multiply-single( C(C1 ,N1) IPolyl] , (C,N) , [(~2,~2)
I~olyl)
C2 is Cl*C, N2 is Nl+N, multiply-single(Polyl, (c,N) ,~oly)
-
multiply-single( [ I ,Factor,[1 ) .
Program 23.1 (Continued)
Program 23.1 (Continued)
Chapter 23 A n Equation Solver

offenders(Equation,Unknown,Offenders) - substitute(Expression,Substitutions,Expression1) -
The list of Substitutions is applied to Expression to produce
Offenders is the set of offenders of the Equation in the Unknown.
offenders(Equation,X,Offenders) - Expressionl.
parse(~quation,X,Offendersl\ [ ] ) ,
remove-duplicates(Offendersl,Offenders),
multiple(Offenders).
reduced-term(x,Off enders,Type,Xl) -
classify(Offenders,~,Type),
candidate(Type,Offenders,X,Xl).
Heuristics for exponential equations
classify(Offenders,X,exponential)
exponential-offenders(Offenders,X).
-
exponential~offenders([A~BlOffsI,X) -
free-of (X,A), subterm(X,B) , exponential-offenders(0ff s ,XI.
exponential-offenders( [ 1 ,X) .
candidate(exponential,Offenders,X,ATX) -
base(Offenders,A), polynomial~exponents(~ffenders,X).
Finding homogenization rewrite rules
rewrite( [Off 1 Off s] ,Type,Xl,[Off=Terml Rewrites] ) +

base([AtBlOffs] ,A) - base(Offs,A). homogenize-axiom(Type,Off,Xl,Term),


rewrite(Offs,Type,Xl,Rewrites).
base([ I ,A). rewrite([ 1 ,Type,X,C 1).
polynomial-exponents([ATBlOffsl,X) -
polynomial(B,X), polynomial~exponents(Offs,X).
Homogenization axioms

polynomial-exponents([ ],XI. homogenize~axiom(exponential,AT(N*X),A~X,~A~X)TN),


homogenize-axiom(exponential,AT(-X),A~X,l/(ATX)).
Parsing the equation and making substitutions homogenize~axiom(exponential,Af(X+B),AfX,AtB*AfX).
parse(Expression, Term,Offenders) -
Expression is traversed to produce the set of Offenders in Term,
Utilities
that is, the nonalgebraic subterms of Expression containing Term. subterm(Sub ,Term) - See Program 9.2.
parse(A+B,X,Ll\L2) - position(Term,Term, [ 1 )
position(Sub,Term,Path)
!.--
! , ~arse(A,X,Ll\L3), parse(B,X,L3\L2).
~arse(~*~,X,Ll\L2) - compound(Term) , functor(Term,F,N) , position(N,Sub,Term,Path) , ! .
! , parse(A,X,Ll\L3), parse(B,~,~3\~2).
-
position(N,Sub,Term, [N I Path] ) -
~arse(~-~,X,Ll\L2) arg(N,Term,Arg) , position(Sub,Arg,Path) .
! , ~arse(A,X,Ll\L3), parse(B,X,L3\L2). position(N,Sub,Term,Path) -
parse(A=B,X,Ll\L2) - N > 1, N1 is N-1, position(Nl,Sub,Term,Path).
! , ~arse(A,X,Ll\L3), parse(B,X,LB\L2).
parse(AlB,X,L) -
integer(B1, ! , parse(A,X,L).
-
--
parse(A,X,L\L) occurrence(Term,Term,1) !.
free-of (X ,A) , ! . occurrence(Sub,Term,N)
parse(A,X, [A ILI \L) - compound(Term) , ! , functor (Term,F,M), occurrence(M,Sub ,Term,O,N).
subterm(X,A), ! . occurrence(Sub,Term,O) - Term f Sub.

Program 23.1 (Continued) Program 23.1 (Continued)


Chapter 23 An Equation Solver

occurrence(M,Sub,Term,Nl,N2) -
M > 0 , ! , arg(M,Term,Arg) , o c c u r r e n c e ( ~ u b , ~ r ~ , ~N3
) ,is N+N1,
The top-level clause in Program 23.1 has a cut as the first goal in the
M1 is M-1, occurrence(M1,Sub,Term,N3,N2).
body. Thls is a green cut: none of the other methods depend on the
occurrence(O,Sub,Term,N,N). success or failure of factorization. In general, we omit green cuts from
remove-duplicates(Xs ,Ys) - no-doubles (Xs ,Ys).
clauses we describe in the text.
no-doubles(xs ,YS) - See Program 7.9.
multiple(~X1,X2~Xs~).
Testing and data
23.3 Isolation
test-press(X,Y) - equation(X,E,U), s ~ l v e - e ~ u a t i o n ( ~ , ~ , ~ ) .
A useful concept to locate and manipulate the single occurrence of the
equation(l,cos(x)*(l-2*sin(x))=O,x). unknown is its position. The position of a subterm in a term is a list of
equation(2,xt2-3*~+2=0,~). argument numbers specifying where it appears. Consider the equation
equation(3,2T(2*~)-5*2t(x+1)+16=0,~). cos(x) = 0. The term cos(x) containing x is the first argument of the
equation, and x is the first (and only) argument of cos(x). The position
Program 23.1 (Continued) of x in cos(x) = 0 is therefore [1,1].Thls is indicated in the diagram in
Figure 23.2. The figure also shows the position of x in 1 - 2 . sin(x) = 0
Program 23.1 has four clauses for solve-equation, one for each of which is [1,2,2,1].
the four methods needed to solve the equations in Figure 23.1. More The clause defining the method of isolation is
generally, there is a clause for each equation-solving method. The full
PRESS system has several more methods.
solve-equation(Equation,X,Solution) -
single-occurrence (X,Equation) ,
Our equation solver ignores several features that might be expected. position(X,Equation,[SidelPosition]),
There is no simplification of expressions, no rational arithmetic, no maneuver-sides (Side,Equation,Equationl) ,
record of the last equation solved, no help facility, and so forth. PRESS isolate(Position,Equationl,Solution).
does contain many of these facilities as discussed briefly in Section 23.6.

2 3.2 Factorization
/ \
Factorization is the first method attempted by the equation solver. Note COS 0
that the test whether factorization is applicable is trivial, being unifica-
*
tion with the equation A B = 0. If the test succeeds, the simpler equa-
tions are recursively solved. The top-level clause implementing factoriza-
tion is /\
2 sin
X
I
Figure 23.2 Position of subterms in terms
Chapter 23 An Equation Solver

The condition characterizing when isolation is applicable is that there can be rewritten to either u = w + v or Y = u - w. The first argument of
be a single occurrence of the unknown X in the equation, checked by isolax specifies whch argument of the sum contains the unknown. The
single-occurrence. The method calculates the position of X with the Prolog equivalent of the two rewrite rules is then
predicate position. The isolation of X then proceeds in two stages. First,
isolax(l,Terml-Term2 = Rhs,Terml = Rhs+Term2).
maneuver-sides ensures that X appears on the left-hand side of the
isolax(2,Terml-Term2 = Rhs,Term2 = Terml-Rhs).
equation, and second, isolate makes it the subject of the formula.
It is useful to define single-occurrence in terms of the more general Other isolation axioms are more complicated. Consider simplifying a
predicate occurrence (Subterm,Term,N) , which counts the number of product on the left-hand side of an equation. One of the expected rules
times N that Subterm occurs in the term Term. Both occurrence and would be
position are typical structure inspection predicates. Both are posed as
exercises at the end of Section 9.2. Code for them appears in the utilities
section of Program 23.1. If Term2 equals zero, however, the rewriting is invalid. A test is therefore
The predicate maneuver-sides ( N , Equat ion, Equat ion11 consists of added that prevents the axioms for multiplication being applied if the
two facts: term by whch it divides is 0. For example,
rnaneuver-sides (I,Lhs = Rhs ,Lhs = Rhs) .
maneuver-sides (2,Lhs = Rhs ,Rhs = Lhs) .

Its effect is to ensure that the unknown appears on the left-hand side of Isolation axioms for trigonometric functions illustrate another possi-
Equationl. The first argument N, the head of the position list, indicates bility that must be catered for - multiple solutions. An equation such as
the side of the equation in which the unknown appears. A 1 means the sin(x) = 112 that is reached in our example has two solutions between 0
left-hand side, and the equation is left intact. A 2 means the right-hand and 2 . n. The alternative solutions are handled by having separate iso-
side, and so the sides of the equation are swapped. lax axioms:
The transformation of the equation is done by isolate/3. It repeatedly
applies rewrite rules until the position list is exhausted:
-
isolate( [N [Position], ~ ~ u a t i o~ns,o l a t e d ~ ~ u a t i o n )
In fact, the equation has a more general solution. Integers of the form
isolax(N,Equation,Equationl),
2 . n . n can be added to either solution for arbitrary values of n. The
isolate(Position,Equationl,~solated~quati~n~. decision whether a particular or general solution is desired depends on
isolate([ I ,Equation,Equation) . context and on semantic information independent of the equation solver.
The rewrite rules, or isolation axioms, are specified by the predicate Further examples of isolation axioms are given in the complete equa-
isolax (N ,Equation,Equat ionl) . Let us consider an example used in tion solver, Program 23.1.
solving 1 - 2 . sin(x) = 0. An equivalence transformation on equations The code described so far is sufficient to solve the first equation in Fig-
is adding the same quantity to both sides of an equation. We show its ure 23.1, cos(x) . (1 - 2 . s i n ( x ) )= 0. There are four answers arccos(O),
translation into an isolax axiom for manipulating equations of the form - arccos(O),arcsin((1 - 0) 121, n - arcsin((1 - 0) 12).Each can be simpli-

u - v = w. Note that rules need only simplify the left-hand side of equa- fied, for example, arcsin((1 - 0 ) / 2 ) to n I 6 , but will not be unless the
tions, since the unknown is guaranteed to be on that side. expression is explicitly evaluated.
Two rules are necessary to cover the two cases whether the first or The usefulness of an equation solver depends on how well it can per-
second argument of u - v contains the unknown. The term u - v = w form such simplification, even though simplification is not strictly part
Chapter 23 A n Equation Solver

of the equation-solving task. Writing an expression simplifier is nontriv- fore the.fina1 step of polynomial-normal-form is removing those terms
ial, however. It is undecidable whether two expressions are equivalent in whose coefficients are zero. T h s is acheved by a simple recursive proce-
general. Some simple identities of algebra can be easily incorporated, for dure remove-zero-terms.
example, rewriting 0 + u to u. Choosing between other preferred forms, The code for polynomial-form directly echoes the code for polyno-
e.g., (1 + x ) b n d 1 + 3 . x + 3 . x2+ x 3 ,depends on context. mial. For each clause used in the parsing process, there is a correspond-
ing clause giving the resultant polynomial. For example, the polynomial
form of a term xn is [ ( I ,n ) ]whch
, is expressed in the clause
2 3.4 Polynomial

Polynomial equations are solved by a polynomial equation solver, apply- The recursive clauses for polynomial-form manipulate the polynomi-
ing various polynomial methods. Both sides of the equation are checked als in order to preserve the polynomial form. Consider the clause
as to whether they are polynomials in the unknown. If the checks are
successful, the equation is converted to a polynomial normal form by polynomial~form(Poly1+Poly2,X,PolyF~rm) -
polynomial-normal-f orm, and the polynomial equation solver solve- polynomial-form(Poly1, X ,PolyForml),
polynomial-equation is invoked: polynomial-form(Poly2 ,X,PolyForm2),

solve~equation(Lhs=Rhs,X,Solution) - add~polynomials(PolyForml,PolyForm2,PolyForm).

polynomial (Lhs,X) , The procedure add-polynomials contains an algorithm for adding poly-
polynomial (Rhs,X) , nomials in normal form. The code is a straightforward list of the possi-
polynomial~normal~form(Lhs-Rhs,X,~ol~Form), bilities that can arise:
solve~polynomial~equation(PolyForm,X,Sol~ti~~~.
add-polynomials ( [ ] ,Poly ,Poly) .
The polynomial normal form is a list of tuples of the form (A,,Ni), add-polynomials(Poly, [ 1 ,Poly) .
where A, is the coefficient of PI, which is necessarily nonzero. The tuples add-polynomials( [(Ai ,Ni) IPolylI , [(Aj ,Nj) 1 Poly21 , [(Ai,Ni) IPoly]) -
are sorted into strictly decreasing order of N,; for each degree there is at Ni > Nj , add-polynomials(Polyl, [(Aj ,Nj) lPoly21 ,Poly).
most one tuple. For example, the list [ ( I ,2 ) , ( - 3 , l ) ,( 2 , O ) ] is the normal add-polynomials( [(Ai ,Ni) (Polyll , [(Aj ,Nj) I Poly21 , [(A,Ni) IPoly] -
form for x2- 3 . x + 2. The leading term of the polynomial is the head of Ni = : = Nj, A is Ai+Aj, add-polynomials(Poly1,Poly2,Poly).
the list. The classic algorithms for handling polynomials are applicable to add-polynomials([(Ai,Ni) lPolyl], [(Aj,Nj) lPoly2], [(~j,Nj)~PolyI) -
equations in normal form. Reduction to polynomial normal form occurs Ni < Nj, add-polynomials([(Ai,Ni) ~Polyl],Poly2,Poly).
in two stages:
Similarly, the procedures subtract-polynomials,multiply-polyno-
mials, and binomial are algorithms for subtracting, multiplying, and
binomially expanding polynomials in normal form to produce results in
normal form. The subsidiary predicate multiply-single(Poly1 ,Mono-
The predicate polynomial-f orm(X ,Polynomial,PolyForm) decom- mial,Poly2) multiplies a polynomial by a monomial (C,N) to produce a
poses the polynomial. PolyForm is a sorted list of coefficient-degree new polynomial.
tuples, where tuples with zero coefficients may occur. Once the polynomial is in normal form, the polynomial equation solver
It is convenient for many of the polynomial methods to assume that is invoked. The structure of the polynomial solver follows the structure
all the terms in the polynomial form have nonzero coefficients. There- of the overall equation solver. The solver is a collection of methods that
Chapter 23 An Equation Solver

are tried in order to see whch is applicable and can be used to solve the
equation. The predicate solve-polynomial-equation is the analogous
relation to solve-equation.
The second equation in Figure 23.1 is quadratic and can be solved with
the standard formula. The equation solver mirrors the human method.
The code for homogenize/4 implements the four stages of homoge-
The polynomial is identified as being suitable for the quadratic method
nization, described in Section 23.1. The offenders set is calculated by
by checking (with quadratic) if the leading term in the polynomial is of
off enders/3, whch checks that there are multiple offenders. If there is
second degree. Since zero terms have been removed in putting the poly-
only a single offender, homogenization will not be useful:
nomial into its normal form, pad puts them back if necessary. The next
two steps are familiar: calculating the discriminant, and returning the
roots according to the value of the discriminant. Again multiple solutions
homogenize(Equation,X,Equationl,Xl) -
offenders (Equation,X, Of fenders) ,
are indicated by having multiple possibilities: reduced-term(X,Offenders,Type,Xl),

solve~polynomial~equation(Poly,X,Solution~ - rewrite(0ffenders,Type,X1,Substitutions),
substitute(Equation,Substitutions,Equationl).
quadratic (Poly) ,
pad(Poly, [(A,2), (B,I), (C,0)1), The predicate reduced_term/4 finds a reduced term, that is, a candi-
discriminant(A,B,C,Discriminant), date for the new unknown. In order to structure the search for the re-
root(X,A,B,C,Discriminant,Solution). duced term, the equation is classified into a type. This type is used in the
discriminant (A,B,C,D) -D is (B*B - 4*A*C) .
next stage to find rewrite rules expressing each element of the offenders
set as an appropriate function of the reduced term. The type of the exam-
ple equation is exponential. PRESS encodes a lot of heuristic knowledge
about finding a suitable reduced term. The heuristics depend on the type
of the terms appearing in the offenders set. To aid the structuring (and
retrieval) of knowledge, finding a reduced term proceeds in two stages -
Other clauses for solve-polynomial-equation constitute separate classifying the type of the offenders set, and finding a reduced term of
methods for solving different polynomial equations. Linear equations
that type:
are solved with a simple formula. In PRESS, cubic equations are handled
by guessing a root and then factoring, reducing the equation to a qua- reduced-term(X, Of fenders,Type ,XI) +

dratic. Other tricks recognize obvious factors, or that quartic equations classif y(0f fenders ,X,Type),
missing a cubic and a linear term are really disguised quadratics. candidate(Type,Offenders,X,Xl).

We look at the set of rules appropriate to our particular equation.


The offenders set is of exponential type because all the elements in the
23.5 Homogenization offenders set have the form AB, where A does not contain the unknown
but B does. Standard recursive procedures check that thls is true.
The top-level clause for homogenization reflects the transformation of The heuristic used to select the reduced term in this example is that if
the original equation into a new equation in a new unknown, which is all the bases are the same, A, and each exponent is a polynomial in the
recursively solved; its solution is obtained for the original unknown: unknown, X, then a suitable reduced term is AX:
Chapter 23 An Equation Solver

candidate(exponential,Offenders,X,AtX) - (v) Modify Program 23.1 so that it solves simple simultaneous equa-
base (Offenders ,A) , polynomial-exponents (offenders ,X) tions.
The straightforward code for base and polynomial-exponents is in the
complete program. The heuristics in PRESS are better developed than the
ones shown here. For example, the greatest common divisor of all the 23.6 Background
leading terms of the polynomials is calculated and used to choose the
reduced term. Symbolic manipulation was an early application area for Prolog. Early
The next step is checking whether each member of the offenders set examples are programs for symbolic integration (Bergman and Kanoui,
can be rewritten in terms of the reduced term candidate. T h s involves 1973) and for proving theorems in geometry (Welham, 1976).
finding an appropriate rule. The collection of clauses for homogenize- The PRESS program, from whch Program 23.1 is adapted, owes a debt
axiom constitute the possibly applicable rewrite rules. In other words, to many people. The original version was written by Bob Welham. Many
relevant rules must be specified in advance. The applicable rules in t h s of the researchers in the mathematical reasoning group worlung with
case are Alan Bundy at the University of Edinburgh subsequently t~nkeredwith
the code. Published descriptions of the program appear in Bundy and
Welham (1981),Sterling et al. (1982),and Silver (1986).The last reference
has a detailed discussion of homogenization.
Substituting the term in the equation echoes the parsing process used PRESS includes various modules, not discussed in t h s chapter, that
by offenders as each part of the equation is checked to see whether it is are interesting in their own right: for example, a package for inter-
the appropriate term to rewrite. val arithmetic (Bundy, 1984), an infinite precision rational arithmetic
package developed by Richard O'Keefe, and an expression simplifier
Exercises for Chapter 23 based on difference-structures as described in Section 15.2, developed by
Lawrence Byrd. The successful integration of all these modules is strong
(i) Add isolation axioms to Program 23.1 to handle quotients on the evidence for the practicality of Prolog for large programming projects.
left-hand side of the equation. Solve the equation x / 2 = 5 . The development of PRESS showed up classic points of software engi-
neering. For example, at one stage the program was being tuned prior
(ii) Add to the polynomial equation solver the ability to solve disguised to publishng some statistics. Profiling was done on the program, whch
linear and disguised quadratic equations. Solve the equations 2 . showed that the predicate most commonly called was f ree-of. Rewriting
x3-8=x3,andx4-5.xG6=00. it as suggested in Exercise 23(iv) resulted in a speedup of 35 percent in
(iii) The equation cos(2 . x ) - sin(x) = 0 can be solved as a quadratic the performance of PRESS.
equation in sin(x) by applying the rewrite rule cos(2 . x ) = 1 - 2 . Program 23.1 is a considerably cleaned-up version of PRESS. Tidying
sin2(x).Add clauses to Program 23.1 to solve t h s equation. You the code enabled further research. Program 23.1 was easily translated to
will need to add rules for identifying terms of type trigonometric, other logic programming languages, Concurrent Prolog and FCP (Sterling
heuristics for finding trigonometric reduced terms, and appropriate and Codish, 1986).Malung the conditions when methods were used more
homogenization axioms. explicit enabled the writing of a program to learn new equation-solving
methods from examples (Silver, 1986).
(iv) Rewrite the predicate free-of (Term,X) so that it fails as soon as it
finds an occurrence of X in Term.
A Compiler

Our final application is a compiler. The program is presented top-down.


The first section outlines the scope of the compiler and gives its defini-
tion. The next three sections describe the three major components: the
parser, the code generator, and the assembler.

-- - -- - ---- -

24.1 Overview of the Compiler

The source language for the compiler is PL, a simplified version of Pascal
designed solely for the purposes of this chapter. It contains an assign-
ment statement, an if-then-else statement, a while statement, and simple
1/0 statements. The language is best illustrated with an example. Fig-
ure 24.1 contains a program for computing factorials written in PL. A
formal definition of the syntax of the language is implicit in the parser
in Program 24.1.
The target language is a machine language typical for a one-accumu-
lator computer. Its instructions are given in Figure 24.2. Each instruction
has one (explicit) operand, which can be one of four things: an integer
constant, the address of a storage location, the address of a program
instruction, or a value to be ignored. Most of the instructions also have a
second implicit operand, which is either the accumulator or its contents.
In addition, there is a pseudoinstruction block that reserves a number of
storage locations as specified by its integer operand.
The scope of the compiler is clear from its behavior on our example.
Figure 24.3 is the translation of the PL program in Figure 24.1 into ma-
Chapter 24 A Compiler

program f a c t o r i a l ; Symbol Address Instruction Operand Symbol


begin
read value; 1 READ 21 VALUE
count := 1 ; 2 LOADC 1
r e s u l t := 1; 3 STORE 19 COUNT
w h i l e count < v a l u e do 4 LOADC 20
begin 5 STORE 20 RESULT
count := c o u n t + l ; LABEL1 6 LOAD 19 COUNT
r e s u l t := result*count 7 SUB 21 VALUE
end ; 8 JUMPGE 16 LABEL2
write result 9 LOAD 19 COUNT
end 10 ADDC 1
11 STORE 19 COUNT
Figure 24.1 A PL program for computing factorials 12 LOAD 20 RESULT
13 MUL 19 COUNT
14 STORE 20 RESULT
15 JUMP 6 LABEL 1
Arithmetic
LABEL2 16 LOAD 20 RESULT
Literals Memory Control I/O, e t c .
17 WRITE 0
jumpeq read 18 HALT 0
addc add jumpne write COUNT 19 BLOCK 3
subc sub jumplt halt RESULT 20
mulc mu1 j umpgt VALUE 21
divc div jumple
loadc load
Figure 24.3 Assembly code version of a factorial program
j umpge
store jump

Figure 24.2 Target language instructions


Object Object

c h n e language. The compiler produces the columns labeled Instruction


and Operand.
The task of compiling can be broken down into the five stages given
in Figure 24.4. The first stage transforms a source text into a list of
Lexical
~nal~sis,
Syntax
Structure

Analysis *

Figure 24.4 The stages of compilation


Code
ene era ti on)
Assembly - Output cObject
Program

tokens. The list of tokens is parsed in the second stage, syntax analysis,
to give a source structure. The thlrd and fourth stages transform the
source structure into relocatable code and assemble the relocatable code The basic predicate compile(Tokens,ObjectCode) relates a list of to-
into absolute object code, respectively. The final stage outputs the object kens Tokens to the Objectcode of the program the tokens represent.
program. The compiler compiles correctly any legal PL program but does not han-
Our compiler implements the middle three stages. Both the first stage dle errors; that is outside the scope of t h s chapter. The list of tokens
of lexical analysis and the final output stage are relatively uninteresting is assumed to be input from some previous stage of lexical analysis.
and are not considered here. The top level of the code handles syntax The parser performing the syntax analysis, implemented by the predi-
analysis, code generation, and assembly. cate parse, produces from the Tokens an internal parse tree Structure.
Chapter 24 A Compiler

compile( Tokens,ObjectCode) -
Objectcode is the result of compilation of
a list of Tokens representing a PL program.
compile(~okens,ObjectCode)
parse(Tokens,Structure),
-
encode(Structure,Dictionary,Code),
assemble(Code,Dictionary,0bjectCode). The codegenerator
The parser encode(Structure,Dictionary,RelocatableCode) -
parse( Tokens,Structure) - RelocatableCode is generated from the parsed Structure
building a Dictionary associating variables with addresses.
Structure represents the successfully parsed list of Tokens.
parse(Source,Structure) - encode((X;Xs),D,(Y;Ys)) -
encode(X,D,Y), encode(Xs,D,Ys).
pl~program(Structure,Source\[ I). encode(void,D,no-op).
pl-program(S) - [program] , identifier(X) , [ ' ; ' I , statement(S) . encode(assign(Name,E),D,(Code; instr(store,Address))) -
statement((S; Ss)) -
[begin], statement(S), rest-statements(Ss).
lookup(Name,D,Address), encode-expression(E,D,Code).
encode(if(Test,Then,Else),D,
statement(assign(X,V)) - (TestCode; Thencode; instr(jump,L2) ;
label(L1); Elsecode; label(L2))) -
identifier(X), [':='I, expression(V).
statement(if(T,Sl,S2)) - encode-test(Test,Ll,D,TestCode),
encode(Then,D,ThenCode),
[if] , test (TI , [then] , statement(S1) , [else] , statement( ~ 2 ).
statement(while(T,S)) - encode(Else,D,ElseCode).
[while] , test (T) , [do] , statement(S) .
statement(read(X) -
encode(while(Test,Do),D,
(label(L1); TestCode; DoCode; instr(jump,Ll); label(L2)))
encode-test(Test,L2,D,TestCode), encode(Do,D,DoCode).
-
[read] , identifier(X) .
statement(write(X)) - encode(read(X),D,instr(read,Address)) -
lookup(X,D,Address).
[write], expression(X) .
rest-statements((S;Ss)) - [';'I, statement(S1, rest-statements(Ss)
encode(write(E),D,(Code; instr(write,O))) -
rest-statements(void) - [end] .
encode-expression(E,D,Code).
-
expression(X) -pl-constant(X) .
encode-expression (Expression,Dictionary,
Code)

expression(expr(Op,X,Y)) -
pl-constant(X), arithmetic-op(Op), expression(Y).
Code corresponds to an arithmetic Expression.

arithmetic-op('+')
arithmetic-op('-')
-- ['+'I.
['-'I.
arithmetic-op('*')
arithmetic-op('/')
-- ['*'I.
['/'I.
pl-constant(name(l0
pl-constant(number(X))
-- identifier()().
pl-integer()().
encode~expression(expr(Op,E1,E2),D,Code) -
not single~instruction(0p,E2,D,Instruction),

-- [XI , atom(^)}.
identifier(X) single-operation(Op,EI,D,E2Code,Code),
pl-integer()() [XI , {integer(x)}. encode-expression(E2,D,E2Code).
test(compare(Op,X,Y)) -
expression(X) , comparison-op(Op), expression(Y) .
Program 24.1 (Continued)

Program 24.1 A compiler from PL to machine language


A Compiler
Chapter 24

single-instruction(0p ,number(C) ,D,instr (0p~ode,C)) - allocate(void,N,N).


allocate(dict (Name,Nl,Before,After) ,NO,N) -
literal-operation(Op,OpCode).
single-instruction(Op,name (XI ,D,instr(0pCode ,A)) - allocate(Before ,NO,Nil,
N2 is N1+1,
memory-operation(Op,OpCode), lookup(X,D,A). allocate(After ,N2,N).
single~operation(0p,E,D,Code,(Code;Instruction~~ -
commutative(Op), single~instruction(Op,E,D,~nstruction). Program 24.1 (Continued)
single-operation(Op,E,D,Code,
(Code;instr(store,Address) ;Load;instr(0pCode Address ) ) +

not commutative(Op),
lookup('$temp',D,Address),
encode-expression(E,D,Load),
op-code(E,Op,OpCode).
op-code(number(C) ,Op ,OpCode)
op-code (name(X) ,Op,OpCode) --literal-operation(Op, OpCode) .
memory-operation(Op, OpCode) . program(f actorial,
literal-operation( ' + ' ,addc) . memory-operation( '+' ,add). [program,factorial,';'
literal-operation('-',subc). memory-operation('-',sub). ,begin
literal~operation('*',mulc). memory-operation('*',mul). ,read,value,';'
literal-operation('/' ,divc). memory-operation('/' ,div) . ,count,':=',l,';'
,result,':=',l,';'
commutative('+'). commutative( ' * ' .
,while,count,'~',value,do
encode-test(compare(Op,El,E2),Label,D,
(Code;instr(OpCode,Label)))
comparison~opcode(0p,OpCode),
- ,begin
,count,':=',count,'+',l,';'
,result,':=',result,'*',count
encode-expression(expr ( '-' ,El,E2) ,D ,Code).
comparison-opcode( f ' ,jumpeq) . end,'; '
comparison-opcode('=' ,jumpne) .
,write,result
comparison~opcode('>',jumple). comparison~opcode('2',jumplt).
comparison-opcode( ' < ' ,jumpge) . comparison-opcode( ' I ' ,jumpgt) .
,end] ) .
lookup(Name,Dictionary,Address) - See Program 15.9. Program 24.2 Test data
The assembler
assemble( Code,Dictionary,TidyCode) - The structure is used by the code generator encode to produce relocat-
able code Code. A dictionary associating variable locations to memory
TidyCode is the result of assembling Code removing
no-ops and labels,and filling in the Dictionary. addresses and keeping track of labels is needed to generate the code.
assemble(Code,Dictionary,TidyCode) -
tidy-and-count(Code,1 ,N,TidyCode\(instr(halt ,O);block(L))),
This is the second argument of encode. Finally, the relocatable code is
assembled into object code by assemble with the aid of the constructed
N1 is N+1, Dictionary.
allocate(Dictionary,Nl,N2),
The testing data and instructions for the program are given as Pro-
L is N2-N1, ! .
tidy-and-count ( (Codel ;Code2),M,N,TCodel\TCode2) - gram 24.2. The program factorial is the PL program of Figure 24.1 trans-
lated into a list of tokens. The two small programs consist of a single
tidy~and~count(Codel,M,M1,TCodel\Rest),
tidy-and-count(Code2,M1,N,Rest\TCode2). statement each, and test features of the language not covered by the
tidy-and-count(instr(X,Y),N,Nl,(instr(~,~);~ode)\Code)
N1 is N+1.
- factorial example. The program test1 tests compilation of a nontrivial
arithmetic expression, and test2 checks the if-then-else statement.
tidy-and-count (label(N) ,N,N,Code\Code).
tidy-and-count(no-op,N,N,Code\Code).

Program 24.1 (Continued)


466 Chapter 24 A Compiler

24.2 The Parser


statement ((S;Ss)) -
[begin] , statement (S) , rest-statements (Ss) .

The parser proper is written as a definite clause grammar, as described Statements in PL are delimited by semicolons. The rest of the state-
in Chapter 19. The predicate parse as given in Program 24.1 is just an ments are accordingly defined as a semicolon followed by a nonempty
interface to the DCG, whose top-level predicate is pl-program. The DCG statement, and recursively the remaining statements:
has a single argument, the structure corresponding to the statements, as
described later. A variant of Program 18.9 is assumed to translate the
rest-statements( (S;Ss)) -
[ ' ; '1 , statement (S) , rest-statements(Ss) .
DCG into Prolog clauses. The convention of that program is that the last
argument of the predicates defined by the DCG is a difference-list: The end of a sequence of statements is indicated by the standard iden-

parse (Source,Structure) -
pl-program(Structure, Source\ [ 1 .
tifier end. The atom void is used to mark the end of a statement in the
internal structure. The base case of rest-statements is therefore
rest-statements (void) - [end] .
The first statement of any PL program must be a program statement. A
The above definition of statements precludes the possibility of empty
program statement consists of the word program followed by the name
statements. Programs and compound statements in PL cannot be empty.
of the program. We call words that must appear for rules of the grammar
The next statement to discuss is the assignment statement. It has a
to apply standard identifiers, the word program being an example. The
simple syntactic definition - a left-hand side, followed by the standard
name of the program is an identifier in the language. What constitutes
identifier is, followed by the right-hand side. The left-hand side is re-
identifiers, and more generally constants, is discussed in the context of
stricted to being a PL identifier, and the right-hand side is any arithmetic
arithmetic expressions. The program name is followed by a semicolon,
expression whose definition is to be given:
another standard identifier, and then the program proper begins. The
body of a PL program consists of statements or, more precisely, a sin- statement (assign(X,E) ) -
gle statement that may itself consist of several statements. All thls is identif ier(X), [ ' :='I , expression(E) .
summed up in the top-level grammar rule:
The structure returned by the successful recognition of an assignment
pl-program(S) - statement has the form assign(X, E) . The (Prolog)variable E represents
[program] , identifier (XI, [ ' ; 'I , statement (S) . the structure of the arithmetic expression, and X is the name of the
(PL) variable to be assigned the value of the expression. It is implicitly
The structure returned as the output of the parsing is the statement assumed that X will be a PL identifier.
constituting the body of the program. For the purpose of code genera- For simplicity of both code and explanation, we restrict ourselves to
tion, the top-level program statement has no significance and is ignored a subclass of arithmetic expressions. Two rules define the subclass. An
in the structure built. expression is either a constant or a constant followed by an arithmetic
The first statement we describe is a compound statement. Its syntax operator and recursively an arithmetic expression. Examples of expres-
is the standard identifier begin followed by the first statement, S, say, sions in the subclass are x, 3, 2 . t and x + y - 212, the expression in the
in the compound statement, and then the remaining statements Ss. The first test case in Program 24.2:
structure returned for a compound statement is (S;Ss), where ; is used
as a two-place infix functor. Note that S, Ss, or both may be compound expression()o -pl-constant (X) .
statements or contain them. The semicolon is chosen as functor to echo expression(expr (Op,X,Y)) -
pl-constant (X) , arithmetic-op(Op), expression(Y) .
its use in PL for denoting sequencing of statements:
Chapter 24 A Compiler

T h s subclass of expressions does not respect the standard precedence Tests are defined to be an expression followed by a comparison oper-
of arithmetic operators. The expression x . 2 + y is parsed as x . (2 + y ) . ator and another expression. The structure returned has the form com-
On the other hand, the expression x + y - 212 is interpreted unambigu- pare (Op ,X ,Y) , where Op is the comparison operator, and X and Y are the
ously as x + ( y - ( 2 1 2 ) ) . left-hand and right-hand expressions in the test, respectively:
For this example, we restrict ourselves to two types of constants in PL:
identifiers and integers. The specification of pl-constant duly consists
test (compare(Op,X,Y)) -
expression(X) , comparison-op(Op), expression(Y)
of two rules. Whch of the two is found is reflected in the structure
returned. For identifiers X, the structure name (X) is returned, whereas The definition of comparison operators using the predicate compari-
number (X) is returned for the integer X: son-op is analogous to the use of arithmetic-op to define arithmetic

pl-constant (name (X) )


pl-constant (number (XI )
-- identifier (X) .
pl-integer (X)
operators. Program 24.1 contains definitions for =, f , >, <, 2 ,and I.
While statements consist of a test and the action to take if the test is
true. The structure returned is while (T,S), where T is the test and S is
For simplicity we assume that PL integers and PL identifiers are Prolog the action. The syntax is defined by the following rule:
integers and atoms, respectively. This allows the use of Prolog system statement (while(T, S) ) -
predicates to identify the PL identifiers and integers. Recall that the curly [while] , test (TI, [do] , statement (S) .
braces notation of DCGs is used to specify Prolog goals:
1/0 is handled in PL with a simple read statement and a simple write
identifier (x) - [XI , {atom(X) 1 . statement. The input statement consists of the standard identifier read
pl-integer (XI - [XI , {integer(X) 1 . followed by a PL identifier; it returns the structure reado(), where X is
the identifier. Write statements are similar:
In fact, all grammar rules that use PL identifiers and constants could
be modified to call the Prolog predicates directly if greater efficiency is statement (reado() ) - [read] , identifier (X) .
needed. statement(write(X)) - [write], expression(X)
A list of arithmetic operators is necessary to complete the definition
Collecting the various pieces of the DCG just described gives a parser
of arithmetic expressions. The form of the statement for addition, repre-
for the language. Note that ignoring the arguments in the DCG gives a
sented by +, follows. The grammar rules for subtraction, multiplication,
formal BNF grammar for PL.
and division are analogous, and appear in the full parser in Program 24.1:
Let us consider the behavior of the parser on the test data in Pro-
gram 24.2. The parsed structures produced for the two single statement
programs have the form (structure);void,where (structure) repre-
The next statement to be discussed is the conditional statement, or sents the parsed statement. The write statement is translated to
if-then-else. The syntax for conditionals is the standard identifier if fol-
lowed by a test (to be defined). After the test, the standard identifier then write (expr (+ ,name(x) ,expr (- ,name(y) ,expr(/yname(~)
is necessary, followed by a statement constituting the then part, the stan- number(2) >> ,
dard identifier else and a statement constituting the else part, in that and the if-then-else statement is translated to
order. The structure built by the parser is if (T,Sl,S2), where T is the
test, S1 is the then part, and S2 is the else part: if (compare (> ,name (a) ,name(b) ) ,assign(max ,name (a) ) 3

assign(max ,name(b) 1) .
statement(if (T,SI,S2)) - The factorial program is parsed into a sequence of five statements fol-
[if] , test (T) , [then] , statement (S1) ,
[else] , statement (S2). lowed by void. The output after parsing for all three test programs is
Chapter 24 A Compiler

Program test1 : The structure produced by the parser for the general PL assignment
statement has the form assign(Name ,Expression), where Expression
is the expression to be evaluated and assigned to the PL variable Name.
The corresponding compiled form calculates the expression followed
Program test2 :
by a store instruction whose argument is the address corresponding
to Name. The representation of individual instructions in the compiled
code is the structure instr (X,Y), where X is the instruction and Y is the
Program test3 : operand. The appropriate translation of the assign structure is there-
fore (Code; instr (store,Address) ), where Code is the compiled form
of the expression, whch, after execution, leaves the value of the ex-
pression in the accumulator. It is generated by the predicate encode-
expression(Expression, D, Expressioncode). Encoding the assignment
statement is performed by the clause
Figure 24.5 Output from parsing

given in Figure 24.5. This is the input for the second stage of compila-
tion, code generation.

This clause is a good example of Prolog code that is easily understood


24.3 The Code Generator declaratively but hides complicated procedural bookkeeping. Logically,
relations have been specified between Name and Address, and between
'The basic relation of the code generator is encode (Structure,Dict io- Expression and Code. From the programmer's point of view it is irrele-
nary,Code), which generates Code from the Structure produced by the vant when the final structure is constructed, and in fact the order of the
parser. This section echoes the previous one. The generated code is de- two goals in the body of this clause can be swapped without changing
scribed for each of the structures produced by the parser representing the behavior of the overall program. Furthermore, the lookup goal, in re-
the various PL statements. lating Name with Address, could be making a new entry or retrieving a
Dictionary relates PL variables to memory locations, and labels to previous one, where the final instantiation of the address happens in the
instruction addresses. The dictionary is used by the assembler to resolve assembly stage. None of this bookkeeping needs explicit mention by the
locations of labels and identifiers. Throughout t h s section D refers to programmer. It goes on correctly in the background.
this dictionary. An incomplete ordered binary tree is used to implement There are several cases to be considered for compiling the expression.
it, as described in Section 15.3. The predicate lookup (Name,D,Value) Constants are loaded directly; the appropriate machine instruction is
(Program 15.9)is used for accessing the incomplete binary tree. loadc C, where C is the constant. Similarly identifiers are compiled into
The structure corresponding to a compound statement is a sequence the instruction load A, where A is the address of the identifier. The two
of its constituent structures. This is translated into a sequence of blocks corresponding clauses of encode-expression are
of code, recursively defined by encode. The functor ; is used to denote
encode-expression(number ( C ) ,D, instr (loadc ,C ) ) .
sequencing. The empty statement denoted by void is translated into a
null operation, denoted no-op. When the relocatable code is traversed encode-expression(name(x) ,D,instr (load,Address) -
during assembly this "pseudoinstruction" is removed. lookup(X ,D,Address) .
Chapter 24 A Compiler

The general expression is the structure expr (Op ,El,E2), where Op is single-operation(Op,E,D,Code,
the operator, El is a PL constant, and E2 is an expression. The form (Code ;
of the compiled code depends on E2. If it is a PL constant, then the instr(store,Address) ;
final code consists of two statements: an appropriate load instruction Load ;
determined recursively by encode-expression and the single instruction instr(0pCode ,Address))
corresponding to Op. Again, it does not matter in whch order the two
instructions are determined. The clause of encode-expression is
1 - not commutative (0p) ,
lookup('$temp' ,D,Address) ,
encode-expression (E,D,Load) ,
op-code(E,Op,OpCode).

The nature of the single instruction depends on the operator and An optimization is possible if the operation is commutative, e.g., ad-
whether the PL constant is a number or an identifier. Numbers refer dition or multiplication, whch circumvents the need for a temporary
to literal operations, and identifiers refer to memory operations: variable. In t h s case, the memory or literal operation can be performed
single~instruction(0p,number(C),D,instr(Opcode,C~~ - on El, assuming that the result of computing E2 is in the accumulator:
literal-operation(Op,Opcode).
single~instruction(Op,name(X),D,instr(Op~~d~,A~~ -
memory-operation(Op, Opcode) , lookup (X ,D,A) .
The next statement is the conditional if-then-else parsed into the struc-
A separate table of facts is needed for each sort of operation. The ture if (Test,Then,Else). To compile the structure, we have to intro-
respective form of the facts is illustrated for +: duce labels to which instructions can jump. For the conditional we need
two labels marking the beginning and end of the else part respectively.
The labels have the form label (N), where N is the address of the instruc-
A separate calculation is necessary when the second expression is not tion. The value of N is filled in during the assembling stage, when the
a constant and cannot be encoded in a single instruction. The form of the label statement itself is removed. The schematic of the code is given by
compiled code is determined from the compiled code for calculating E2, the t h r d argument of the following encode clause:
and the single operation is determined by Op and El:
encode~expression(expr(Op,E1,E2),D,Code) - encode(if (Test ,Then,Else),D,
(Testcode ;
not single-instruction(Op,E2,D,1nstruction),
ThenCode ;
single-operation(Op,El ,D,E2Code, Code) ,
instr(jump,L2) ;
encode-expression(E2, D, E2Code) .
label (Ll) ;
In general, the result of calculating E2 must be stored in some tempo- ElseCode ;
rary location, called $temp in the following code. The sequence of instruc- label (L2))
tions is then the code for E2, a store instruction, a load instruction for >- encode~test(Test,Ll,D,TestCode),
El, and the appropriate memory operation addressing the stored con-
tents. The predicates shown previously are used to construct the final encode (Then,D ,ThenCode) ,
form of the code: e ElseCode) .
encode ( ~ l s ,D,
Chapter 24 A Compiler

In order to compare two arithmetic expressions, we subtract the sec- Program test1 :
ond from the first and make the jump operation appropriate to the par- ((((instr(load,~);instr(divc,2));instr(~tore,~emp~;
ticular comparison operator. For example, if the test is whether two ex- instr(load,Y) ;instr(sub,Temp)) ; instr(add,X)) ;
instr(write,O));no-op
pressions are equal, we circumvent the code if the result of subtracting
the two is not equal to zero. Thus comparison~opcode( '=' ,jumpne) is a Program test2 :
fact. Note that the label that is the second argument of encode-test is (((instr(load,A) ; instr(sub,B)) ;instr(jumple,L1)) ;
the address of the code following the test. (instr(load ,A); instr (store ,Max)) ; instr( j m p , L 2 ) ; label(L1) ;
(instr(load,B) ; instr(store,Max)) ;label(L2)) ;nO-OP
encode-test (compare (Op,E1 ,E2) ,Label,D, Program factorial :
(Code; instr(OpCode,Label))) - instr(read,Value);(in~tr(l~adc,l);instr(~t~re,C~~nt));
comparison~opcode(0p ,OpCode) , (instr(loadc,l);instr(~t0re,~es~lt));(label(~1);
encode~expression(expr('-',EI,E2),D,Code~. ((instr(load,Count) ; instr(sub,Value)) ; instr(jmpge ,L2)) ;
(((instr(load,Count) ;instr(addc, 1)) ; instr(store,~ount)) ;
The next statement to consider is the while statement. The statement is ((instr(load,~esult);instr(mul,~~unt));instr(store,~esult));
parsed into the structure while (Test ,Statements). A label is necessary no-op) ; instr(jump,Ll) ;label(~2)); (instr(load,~esult);
before the test, then the test code is given as for the if-then-else state- instr(write,O));no-op
ment, then the body of code corresponding to Statements and a jump
Figure 24.6 The generated code
to reperform the test. A label is necessary after the jump instruction for
when the test fails.
encode (while (Test,Do) ,D,
(label (L1) ; Figure 24.6 contains the relocatable code after code generation and be-
TestCode ; fore assembly for each of the three examples of Program 24.2. Mnemonic
DoCode ; variable names have been used for easy reading.
instr(jump,Ll) ;
label (L2))
) - encode-test (Test,L2 ,D,~estCode), 24.4 The Assembler
encode (Do,D ,DoCode) .
The final stage performed by the compiler is assembling the relocatable
The 1/0 statements are straightforward. The parsed structure for in- code into absolute object code. The predicate assemble (Code ,Dictio-
put, read(X), is compiled into a single read instruction, and the table is nary,Obj ectCode) takes the Code and Dictionary generated in the pre-
used to get the correct address: vious stage and produces the object code. There are two stages in the as-
encode (read(X) ,D,instr (read,~ d d r e s s )) - sembly. During the first stage, the instructions in the code are counted, at
the same time computing the addresses of any labels created during code
lookup(X,D,Address).
generation and removing unnecessary null operations. This tidied code
The output statement is translated into encoding an expression and then is further augmented by a halt instruction, denoted by instr (halt,0) ,
a write instruction: and a block of L memory locations for the L PL variables and tempo-
rary locations in the code. The space for memory locations is denoted
encode(write (E) ,D,(Code; instr (write ,0))) +

by block(L). In the second stage, addresses are created for the PL and
encode-expression(E,D,Code). temporary variables used in the program:
Chapter 24 A Compiler

assemble(Code,Di~ti~nary,TidyCode) - Program test1 :


,o) ;block(L) 1)
tidy-and-count (Code,1 ,~,~idy~ode\(instr(halt 3

N1 is N+1,
allocate(Dictionary ,N1,N2) , Program test2 :
L is N2-N1, ! . instr(load, 10) ; instrcsub, 11) ; instr(jumple ,7) ;instr(load,l~);
instr(store,12) ;instr(jump,9) ;instr(load,ll) ;instr(store,l2);
The predicate t idy-and-count (Code,M ,N ,TidyCode) tidies the Code instr (halt,0) ;block(3)
into TidyCode, where the correct addresses of labels have been filled
Program factorial :
in and the null operations have been removed. Procedurally, executing
instr(read,21) ; instr(loadc, 1) ; instr (store,19) ;instr(loadc, 1) ;
tidy-and-count constitutes a second pass over the code. M is the ad-
instr(store ,20); instr(load, 19) ;instr(sub,21) ; instr(jumpge, 16) ;
dress of the beginning of the code, and N is 1 more than the address of instr(load,l9);instr(addc,l);instr(store,19);instr(load,20);
the end of the original code. Thus the number of actual instructions in instr(mul,19);instr(store,20);instr(jump,6);instr(load,20);
Code is N+1-M. TidyCode is represented as a difference-structure based instr(write ,0) ; instr(ha1t ,0) ;block(3)
on ; .
Figure 24.7 The compiled object code
The recursive clause of tidy-and-count demonstrates both standard
difference-structure technique and updating of numeric values:
in the program, plus any temporary variables needed for computing ex-
tidy~and~count((Code1;Code2),M,N,~~odel\~~ode2~ +
pressions. The effect of allocate is to assign actual memory locations
tidy-and-count (Code1 ,M,MI ,TCodel\Rest) ,
for the variables and to fill in the references to them in the program.
tidy-and-count(Code2,Ml,N,Rest\TCode2).
The variables are found by traversing the Dictionary. M is the address
Three types of primitives occur in the code: instructions, labels, and of the memory location for the first variable, and N is 1 more than the
no-ops. Instructions are handled routinely. The address counter is incre- address of the last. The order of variables is alphabetic corresponding to
mented by 1, and the instruction is inserted into a difference-structure: their order in the dictionary. The code also completes the dictionary as a
data structure.
tidy-and_count(instr(X,Y) ,N,NI,(instr (x,Y) ;Code)\Code) +

N1 is N+1. allocate (void,N,N) .


Both labels and no-ops are removed without updating the current ad-
allocate(dict (Name,Nl ,Before,After), N O , N -
allocate(Bef ore ,NO,Nil,
dress or adding an instruction to the tidied code:
N2 is Nl+l,
allocate(After,N2,N).

Because the dictionary is an incomplete data structure, the predicate


Declaratively, the clauses are identical. Procedurally, the unification of allocate can succeed many times. The variables at the end of the tree
the label number with the current address causes a major effect in the match both the fact and the recursive clause. For the compiler, the easi-
program. Every reference to the label address is filled in. This program is est way to stop multiple solutions is to add a cut to the clause for assem-
another illustration of the power of the logical variable. ble/3, which commits to the first (and minimal) assignment of memory
The predicate allocate (Dict ionary ,M ,N) has primarily a procedu- locations for variables.
ral interpretation. During the code generation as the dictionary is con- The compiled versions of the test programs given in Program 24.2
structed, storage locations are associated with each of the PL variables appear in Figure 24.7.
Chapter 24

Exercises for Chapter 24

Extend the compiler so that it handles repeat loops. The syntax is


Operators
(i)
repeat (statement) until (test).Extensions to both the parser and
the compiler need to be made. Test the program on the following:
program r e p e a t ;
begin
i := 1 ;
repeat
i begin
ii w r i t e ( i ) ;
11 1 : = i + l
An operator is defined by its name, specifier, and priority. The name is
i end
usually an atom. The priority is an integer between 1 and 1200 inclusive.
u n t i l i = I1 The specifier is a mnemonic that defines two things, class and asso-
end. ciativity. There are three classes of operators: prefix, infix, and postfix.
Associativity, which determines how to associate terms containing mul-
(ii) Extend the definition of arithmetic expressions to allow arbitrary tiple operators, can be one of three possibilities: left-associative, right-
ones. In the encoder, you will have to cater for the possibility of associative, and non-associative.
needing several temporary variables. There are seven possible operator types, whlch are given in Table A.1.
A left-associative prefix operator is not possible, nor is a right-associative
postfix operator. An operator specifier yfy does not make sense as it
24.5 Background would lead to ambiguity. Consequently Standard Prolog does not allow
such a specifier.
The compiler described is based on a delightful paper by Warren (1980). To explain the associativity, consider a term a :: b :: c. If the infix
operator :: was left-associative, the term would be read as (a :: b) :: c. If
the operator :: was right-associative, the term would be read as a :: (b :: c).
If the operator :: was non-associative, the term would be illegal.
If uncertain about priorities when using operators, terms can always be
bracketed. If you prefer not to bracket terms, you must take into account
the associativity of the operatorb) involved and the priorities of terms.
For example, the following three rules apply.

1. An operand with the same priority as a non-associative operator


must be bracketed to avoid a syntax error by the Prolog reader.
2 . An operator with the same (or smaller) priority as a right-associative
operator that follows that operator need not be bracketed.
3. An operator with smaller priority than a left-associative operator
that precedes that operator need not be bracketed.
Appendix A Operators

Table A.l The system of operator declarations in Prolog is straightforward and


Types of Operators in Standard Prolog can be used effectively for applications. The reader should be aware,
Specifier Class Associativity however, that there are some subtle semantic anomalies in how opera-
tors are defined and handled. The anomalies, best discovered by trial and
fx prefix non-associative error, should not cause problems and can be "programmed around."
fy prefix right-associative
xfx infix non-associative
xfy infix right-associative
~ f x infix left-associative
xf postfuc non-associative
Yf postfuc left-associative

Table A.2
Predefined Operators in Standard Prolog

Priority Specifier Operator(s)


xfx
fx
xf)
xfy
xfx
xfx
xfx
xfx
yfx
yfx
xfy
fl'

Standard Prolog specifies some predefined operators. The priorities


and specifiers of the operators which have been used in the text are given
in Table A.2.
New operators are added with the directive

where X is the priority, Y is the operator specifier, and Z is the operator


name. These were used in Chapter 17 when defining a new rule language.
References

Abelson, H. and Sussman, G. J., Structure and Interpretation of Computer


Programs, MIT Press, Cambridge, Massachusetts, 1985.
Abramson, H. and Dahl, V., Logic Grammars, Springer-Verlag, New York,
1989.
Abramson, H. and Rogers, M. (eds.), Meta-Programming in Logic Progmm-
ming, MIT Press, Cambridge, Massachusetts, 1989.
At-Kaci, H., The WAM: A (Real) Tutorial, MIT Press, Cambridge, Massachu-
setts, 1991.
Apt, K. R., Logic Programming, in Handbook o f Theoretical Computer Science,
J . van Leeuwen (ed.),pp. 493-574, North-Holland,Amsterdam, 1990.
Apt, K. R. and van Emden, M. H., Contributions to the Theory of Logic Pro-
gramming, J. ACM 29, pp. 841-862, 1982.
Bansal, A. and Sterling, L. S., Classifying Generate-and-Test Logic Programs,
International J. of Parallel Processing 8(4),pp. 401-446, 1989.
Barklund, J., What is a Variable in Prolog? in (Abramson and Rogers, 1989),
pp. 383-398.
Ben-David, A. and Sterling, L., A Prototype Expert System for Credit Evalua-
tion, in Artificial Intelligence in Economics and Management, L. F. Pau (ed.),
pp. 121-1 28, North-Holland,Amsterdam, 1986.
Berge, C., The Theory o f Graphs and its Applications, Methuen & Co., London,
1962.
Bergman, M. and Kanoui, H., Application of Mechanical Theorem Proving
to Symbolic Calculus, in Proc. Third International Symposium on Advanced
Computing Methods in Theoretical Physics, CNRS, Marseilles, 1973.
References References

Bloch, C., Source-to-Source Transformations of Logic Programs, Tech. Report Chikayama, T., Unique Features of ESP, in Proc. lnternational Conference on
CS-84-22, Department of Applied Mathematics, Weizmann Institute of Sci- Fifth Generation Computer Systems, pp. 292-298, ICOT - Institute for New
ence, Rehovot, Israel, 1984. Generation Computer Technology, Tokyo, Japan, 1984.

Bowen, D. L., Byrd, L., Pereira, L. M., Pereira, F. C. N., and Warren, D. H. D., Ciepielewski, A., Scheduling in OR-parallel Prolog Systems, Frameworks, Sur-
Prolog on the DECSystem-10, User's Manual, University of Edinburgh, 1981. vey, and Open Problems, Techrucal Report 91-02, University of Iowa, 1991.

Bowen, K. and Kowalski, R., Amalgamating Language and Meta-Language, in Clark, K. L., Negation as Failure, in (Gallaire and Minker, 1978), pp. 293-322.
(Clark and Tarnlund, 1982), pp. 153-172.
Clark, K. L. and Gregory, S., A Relational Language for Parallel Programming,
Boyer, R. S. and Moore, J. S., A Computational Logic, Academic Press, ACM in Proc. ACM Symposium on Functional Languages and Computer Architec-
Monograph Series, 1979. ture, pp. 171-178, 1981.

Breuer, G. and Carter, H. W., VLSI Routing, in Hardware and Software Con- Clark, K. L. and Gregory, S., PARLOG: Parallel Programming in Logic, Re-
cepts in LZSI, G. Rabbat (ed.),pp. 368-405, Van Nostrand Reinhold, 1983. search Report 84/4, Department of computing, Imperial College of Science
and Technology, England, 1984.
Bruffaerts, A. and Henin, E., Negation as Failure: Proofs, Inference Rules and
Meta-interpreters, in (Abramson and Rogers, 1989), pp. 169-190. Clark, K. L. and McCabe, F. G., The Control Facilities of IC-Prolog, in Expert
Systems in the Microelectronic Age, D. Michle (ed.),pp. 153-167, University of
Bruynooghe, M., The Memory Management of Prolog Implementations, in Edinburgh Press, 1979.
(Clark and Tarnlund, 1982), pp. 83-98.
Clark, K. L. and McCabe, F. G., PROLOG: A Language for lmplementing Expert
Bruynooghe, M. and Pereira, L. M., Deductive Revision by Intelligent Back- Systems, in Machine Intelligence 10, J. Hayes, D. Michie and Y. H. Pao (eds.),
tracking, in (Campbell, 1984), pp. 194-215. pp. 45 5-470, Ellis Horwood Publication, Wiley, New York, 1982.
Bundy, A., A Computer Model o f Mathematical Reasoning, Academic Press, Clark, K. L, McCabe, F. G., and Gregory, S., IC-Prolog Language Features, in
New York, 1983. (Clark and Tarnlund, 1982).
Bundy, A., A Generalized Interval Package and Its Use for Semantic Checking, Clark, K. L. and Tarnlund, S.-A., A First Order Theory of Data and Programs,
ACM Transactions on Mathematical Software 10, pp. 392-407, 1984. Information Processing 77, pp. 939-944, North-Holland, Amsterdam, 1977.
Bundy, A. and Welham, R., Using Meta-level Inference for Selective Appli-
Clark, K. L. and Tarnlund, S.-A. (eds.), Logic Programming, Academic Press,
cation of Multiple Rewrite Rules in Algebraic Manipulation, Artificial Intelli-
gence 16, pp. 189-212, 1981. London, 1982.

Burstall, R. M. and Darlington, J., A Transformation System for Developing Clocksin, W. F. and Mellish, C. S., Programming in Prolog, 2nd Edition,
Recursive Programs, J. ACM 24, pp. 46-67, 1977. Springer-Verlag, New York, 1984.

Byrd, L., Understanding the Control Flow of Prolog Programs, in (Tarnlund, Coelho, H. and Cotta, J., Prolog by Example, Springer-Verlag, New York, 1988.
1980).
Coelho, H., Cotta, J. C., and Pereira, L. M., How to Solve It in Prolog, 2nd
Campbell, J. A. (ed.), Implementations of Prolog, Ellis Horwood Publication, Edition, Laboratorio Nacional de Engenharia Civil, Lisbon, Portugal, 1980.
Wiley, New York, 1984.
Cohen, J., Describing Prolog by Its Interpretation and Compilation, Comm.
Chen, W., Kiefer, M., and Warren, D. S., HiLog: A First-Order Semantics for ACM 28, pp. 1311-1324, 1985.
Higher-Order Logic Programming Constructs, in Proc. 1989 North American
Conference on Logic Programming, E. Lusk, and R. Overbeek, (eds.),pp. 1090- Cohen, J., A View of the Origins and Development of Prolog, Comm. ACM
1114, MIT Press, Cambridge, Massachusetts, 1989. 31(1),pp. 26-36, 1988.
References References

Colmerauer, A., Les systemes-Q ou un Formalisme pour Analyser et Syn- Dwork, C., Kanellakis, P. C., and Mitchell, J. C., On the Sequential Nature of
thesizer des Phrases sur Ordinateur, Publication Interne No. 43, Dept. Unification, J. Logic Programming 1 , pp. 3 5-50, 1984.
d'Informatique, Universite de Montreal, Canada, 1973.
Eggert, P. R. and Chow, K. P., Logic Programming Graphcs with Infinite
Colmerauer, A., Prolog-11, Manuel de reference et modele theorique, Groupe Terms, Tech. Report University of California, Santa Barbara 83-02, 1983.
d'Intelligence Artificielle, Universite d'Aix-Marseille 11, France, 1982a.
Elcock, E. W., The Pragmatics of Prolog: Some Comments, in Proc. Logic Pro-
Colmerauer, A,, Prolog And Infinite Trees, in (Clark and Tarnlund, 1982)b,pp. gramming Workshop '83,pp. 94-106, Algarve, Portugal, 1983.
231-251. Even, S., Graph Algorithms, Computer Science Press, 1979.
Colmerauer, A,, An Introduction to Prolog-111, Comm. ACM 33(70), pp. 70-90, Findlay, W. and Watt, D. A., PASCAL: An Introduction to Methodical Program-
1990. ming, 2nd edition, Pitman, 1985.
Colmerauer, A., Kanoui, H., Pasero, R., and Roussel, P., Un Systeme de Com- Futamura, Y., Partial Evaluation of Computation Process-An Approach to a
munication Homme-machine en Francais, Research Report, Groupe Intelli- Compiler-Compiler, Systems, Computers, Controls 2, pp. 45-50, 1971.
gence Artificielle, Universite Aix-Marseille 11, France, 1973.
Gallagher, J., Transforming Logic Programs by Specializing Interpreters, in
Colmerauer, A. and Roussel, P., The Birth of Prolog, in ACM SIGPLAN Notices, Proc. Seventh European Conference on Artificial Intelligence, pp. 109-122,
28(3),pp. 37-52, 1993. Brighton, England, 1986.
Deo, N., Graph Theory with Applications to Engineering and Computer Sci- Gallagher, J. and Bruynooghe, M., Some Low-Level Source Transformations
ence, Prentice Hall, Englewood Cliffs, N. J., 1974. for Logic Programs, in Proc. Second Workshop on Meta-Programming in Logic,
pp. 229-244, Leuven, Belgium, 1990.
Dershowitz, N, and Lee, Y. J., Deductive Debugging, in Proc. Third IEEE Sym-
posium on Logic Programming, San Francisco, pp. 298-306, 1987. Gallaire, H. and Lasserre, C., A Control Metalanguage for Logic Programming,
in (Clark and Tarnlund, 1982), pp. 173-185.
Deville, Y., Logic Programming-Systematic Program Development, Addi-
son-Wesley, Reading, Massachusetts, 1990. Gallaire, H. and Minker, J., Logic and Databases, Plenum Publishing Co., New
York, 1978.
Deville, Y., Sterling, L. S. and Deransart, P., Prolog for Software Engineering,
Tutorial presented at Eighth International Conference on Logic Programming, Gallaire, H., Minker, J., and Nicolas, J. M., Logic and Databases: A Deductive
Paris, France, 1991. Approach, Computing Suweys 16, pp. 153-185, 1984.
Dincbas, M. and Le Pape, J. P., Metacontrol of Logic Programs in METALOG, Gregory, S., Parallel Logic Programming in PARLOG, Addison-Wesley, Read-
in Proc. International Conference on Fifth Generation Computer Systems, pp. ing, Massachusetts, 1987.
361-370, ICOT - Institute for New Generation Computer Technology, Tokyo,
Japan, 1984. Hammond, P., Micro-Prolog for Expert Systems, Chapter 11 in Micro-Prolog:
Programming in Logic, K. Clark and F. McCabe (eds.), Prentice Hall, Engle-
Disz, T., Lusk, E., and Overbeek, R., Experiments with OR-Parallel Logic Pro- wood Cliffs, N. J., 1984.
grams, in Proc. Fourth International Conference on Logic Programming, J. L.
Lassez (ed.), pp. 576-600, MIT Press, Cambridge, Massachusetts, 1987. Heintze, N., Michaylov, S., Stuckey, P. and Yap, R., On Meta-Programming in
CLP(R), in Proc. 1989 North American Conference on Logic Programming, E.
Drabent, W., Nadjm-Tehrani, S., and Maluszynski, J., Algorithmic Debugging Lusk, and R. Overbeek, (eds.), pp. 52-66, MIT Press, Cambridge, Massachu-
with Assertions, in (Abramson and Rogers, 1989),pp. 501-521. setts, 1989.

Dudeney, H. E., Amusements in Mathematics, Thomas Nelson and Sons, Lon- Hill, P. and Lloyd, J. W., Analysis of Meta-Programs, in (Abramson and Rogers,
don, 1917. 19891, pp. 23-51.
References References

Hill, P. and Lloyd, J. W., The Godel Programming Language, MIT Press, Cam- Kowalski, R., Algorithm = Logic + Control, Comm. ACM 22, pp. 424-436,
bridge, Massachusetts, 1993. 1979b.

Hill, R., LUSH-Resolution and Its Completeness, DCL Memo 78, Department Kowalslu, R., The Early Years of Logic Programming, Comm. ACM 31(1), pp.
of Artificial Intelligence, University of Edinburgh, Scotland, 1974. 38-43, 1988.

Hopcroft, J. E. and Ullman, J. D., Introduction to Automata Theory, Languages, Kunen, K., Logic for Logic Programmers, Tutorial Notes T1, North American
Computation, Addison-Wesley, Readmg, Massachusetts, 1979. Conference on Logic Programming, Cleveland, October, 1989.

Horowitz, E. and Sahni, S., Fundamentals of Computer Algorithms, Computer Lakhotia, A., Incorporating 'Programming Techniques' into Prolog programs,
Science Press, 1978. in Proc. 1989 North American Conference on Logic Programming, E. Lusk,
and R. Overbeek, (eds.), pp. 426-440, MIT Press, Cambridge, Massachusetts,
Jaffar, J., Lassez, J. L., and Lloyd, J. W., Completeness of the Negation as 1989.
Failure Rule, in Proc. of the International Joint Conference on Artificial Intelli-
gence, pp. 500-506, Karlsruhe, Germany, 1983. Lakhotia, A. and Sterling, L. S., ProMiX: A Prolog Partial Evaluation System, in
(Sterling, 1990),pp. 137-1 79.
Jaffar, J. and Lassez, J. L., From Unification to Constraints, in Proc. of Logic
Programming '87, K. Furukawa, H. Tanaka, and T. Fujisaki, (eds.), pp. 1-18, Lassez, J. L., From LP to LP: Programming with Constraints, Unpublished
Springer-Verlag LNCS 3 15, 1987. Technical Report, 1991.
Janson, S. and Haridi, S., Programming Paradigms of the Andorra Kernel Lassez, J. L., Maher, M., and Marriott, K., Unification Revisited, in Foundations
Language, in Proc. 1991 International Symposium on Logic Programming, V. of Deductive Databases and Logic Programming, J. Minker, (ed.),pp. 587-625,
Saraswat, and K. Ueda, (eds.), pp. 167-183, MIT Press, Cambridge, Massachu- Morgan Kaufmann, 1988.
setts, 1991.
Li, D., A Prolog Database System, Research Studies Press, Ltd., Wiley, England,
Journal of Logic Programming, Special Issue on Abstract Interpretation, 15, 1984.
1993.
Lim, P. and Stuckey, P., Meta-Programming as Constraint Programming, in
Kahn, K. M., A Primitive for the Control of Logic Programs, in Proc. Inter- Proc. NACLP-90, S. Debray and M. Hermenegildo (eds.), pp. 406-420, MIT
national IEEE Symposium on Logic Programming, Atlantic City, pp. 242-251, Press, Cambridge, Massachusetts, 1990.
1984.
Lindholm, T. and O'Keefe, R., Efficient Implementation of a Defensible Se-
Kirschenbaum, M., Sterling, L., and Jain, A., Relating Logic Programs via Pro- mantics for Dynamic Prolog Code, in Proc. Fourth International Conference
gram Maps, Annals of Mathematics and Artificial Intelligence 8(3-4), 1993. on Logic Programming, J . L. Lassez (ed.), pp. 21-39, MIT Press, Cambridge,
Massachusetts, 1987.
Knuth, D., The Art Of Computer Programming, Volume 1,Fundamental Algo-
rithms, Addison-Wesley, Reading, Massachusetts, 1968. Lloyd, J. W., Foundations O f Logic Programming, 2nd Edition, Springer-Verlag,
New York, 1987.
Knuth, D., The Art O f Computer Programming, Volume 3, Sorting and Search-
ing, Addison-Wesley, Reading, Massachusetts, 1973. Lombardi, L. A. and Raphael, B., Lisp as the Language for an Incremental
Komorowski, H. J., A Specification of an Abstract Prolog Machine and Its Computer, in The Programming Language LISP: Operation and Application,
Application to Partial Evaluation, Ph. D. Thesis, available as Report No. 69, E. C . Berkeley and D. G. Bobrow (eds.), pp. 204-219, MIT Press, Cambridge,
Software Systems Research Center, Linkoping University, 1981. Massachusetts, 1964.

Kowalski, R., Predicate Logic as a Programming Language, in Proc. IFIP Maier, D., The Theory o f Relational Databases, Computer Science Press, 1983.
Congress, J. Rosenfeld (ed.),pp. 569-574, North-Holland, Amsterdam, 1974. Maier, D. and Warren, D. S., Computing with Logic-Logic Programming with
Kowalski, R., Logic For Problem Solving, North-Holland, Amsterdam, 1979a. Prolog, Benjamin-Cummings, 1988.
References References

Marriott, K. and S~lndergaard,H., Difference-List Transformation for Prolog, O'Keefe, R. A,, On the Treatment of Cuts in Prolog Source-Level Tools, in
New Generation Computing 11(2),pp. 125-1 57, 1993. Proc. 1985 IEEE Symposium on Logic Programming, Boston, pp. 68-72, IEEE
Computer Society Press, 1985.
Martelli, A. and Montanari, U., An Efficient Unification Algorithm, ACM Trans-
actions on Programming Languages and Systems 4(2),pp. 258-282, 1982. O'Keefe, R. A., The Craft o f Prolog, MIT Press, Cambridge, Massachusetts,
1990.
Matsumoto, Y., Tanaka, H., and Kiyono, M., BUP: A Bottom-Up Parsing System
for Natural Languages, in (van Caneghem and Warren, 19861, pp. 262-275. Paterson, M. S. and Wegman, M. N., Linear Unification, J. Computer and Sys-
tems Sciences 16, pp. 158-167, 1978.
Mellish, C. S., Some Global Optimizations for a Prolog Compiler, J. Logic
Pereira, L. M., Logic Control with Logic, in Proc. First International Logic Pro-
Programming 2, pp. 43-66, 1985.
gramming Conference, pp. 9-18, Marseilles, France, 1982.
Michie, D., "Memo" Functions and Machine Learning, Nature, 218, pp. 19-22, Pereira, F. C. N. and Shieber, S., Prolog and Natural Language Analysis, CSLI
1968. Lecture Notes No. 10, CSLI, Stanford 1Tniversity, Stanford, 1987.
Miller, D. and Nadathur, G., Higher-Order Logic Programming, in Proc. Third Pereira, F. C. N. and Warren, D. H. D., Definite Clause Grammars for Language
International Conference on Logic Programming, E. Y. Shapiro, (ed.), pp. 448- Analysis-A Survey of the Formalism and a Comparison with Augmented
462, Springer-Verlag LNCS 225, 1986. Transition Networks, Artificial Intelligence 13, pp. 23 1-278, 1980.
Minsky, M., Semantic Information Processing, MIT Press, Cambridge, Massa- Peter, R., Recursive Functions, Academic Press, New York, 1967
chusetts, 1968.
Plaisted, D. )I.,The Occur-Check Problem in Prolog, New Generation Comput-
Moss, C., Cut and Paste-Defining the Impure Primitives of Prolog, in Proc. ing 2, pp. 309-322, 1984.
Third International Conference on Logic Programming, E. Y. Shapiro, (ed.),pp. Pliimer, L., Termination Proofs for Logic Programs, Springer-Verlag, New
686-694, Springer-Verlag LNCS 225, 1986.
York, 1990.
Naish, L., All Solutions Predicate in PROLOG, in Proc. IEEE Symposium on Power, A. J. and Sterling, L. S., A Notion of Map between Logic Programs, in
Logic Programming, Boston, pp. 73-77, IEEE Computer Society Press, 1985a. Proc. Seventh International Conference on Logic Programming, D. H. D. War-
ren and P. Szeredi (eds.), pp. 390-404, MIT Press, Cambridge, Massachusetts,
Naish, L., Automating Control for Logic Programs, J. Logic Programming 2, 1990.
pp. 167-184, 1985b.
Powers, D., Playing Mastermind More Logically, SIGART Newsletter 89, pp.
Naish, L., Negation and Control in Prolog, Springer-Verlag LNCS 238, 1986. 28-32, 1984.
Nakashima, H., Tomura S. and Ucda, K., What Is a Variable in Prolog? in Proc. Quintus Prolog Reference Manual, Quintus Computer Systems Ltd., 1985.
of the International Conference on Fifth Generation Computer Systems, pp.
327-332, ICOT - Institute for New Generation Computer Technology, Tokyo, Reiter, R., On Closed World Databases, in (Gallaire and Minker, 1978), pp. 55-
Japan, 1984. 76. Also in Readings in Artificial Intelligence, Webber and Nilsson (eds.),Tioga
Publishing Co., Palo Alto, California, 1981.
Nilsson, N. J., Problem Solving Methods in Artificial Intelligence, McGraw-Hill,
Robinson, J. A , , A Machine-Oriented Logic Based on the Resolution Principle,
New York, 1971.
J. ACM 12, pp. 23-41, January 1965.
Nilsson, N. J., Principles o f Artificial Intelligence, Tioga Publishing Co., Palo Robinson, J. A. and Sibert, E. E., LOGLISP: Motivation, Design and Implemen-
Alto, California, 1980. tation, in (Clark and Tarnlund, 19821, pp. 299-313.
O'Keefe, R. A,, Programming Meta-Logical Operations in Prolog, DAI Working Ross, P., Advanced Prolog, Technzques and Examples, Addison-Wesley, Read-
Paper No. 142, University of Edinburgh, Scotland, 1983. ing, Massachusetts, 1989.
Y'lm?LSlDkG DE: LA R E P n E E A
PAC1" "!'I,, : :?'SrNrBm
References References

Sahlin, D., An Automatic Partial Evaluator for Full Prolog, Ph. D. Thesis, avail- Slagle, J. and Dixon, J., Experiments with Some Programs That Search Game
able as SICS Dissertation Series 4, Swedish Institute of Computer Science, Trees, J. ACM 16, pp. 189-207, 1969.
1991.
Smith, D., Partial Evaluation of Pattern Matchng in Constraint Logic Program-
Schank, R. C. and Abelson, R. P., Scripts, Plans, Goals, and Understanding, ming Languages, Proc. Symposium on Partial Evaluation and Semantics-Based
Lawrence Erlbaum, Hillsdale, N. J., 1977. Program Manipulation, SIGPLAN Notices 26, pp. 62-71, September 1991.
Schank, R. C. and Riesbeck, C., Inside Computer Understanding: Five Pro- S~lndergaard,H., Semantics-Based Analysis and Transformation of Logic Pro-
grams Plus Miniatures, Lawrence Erlbaum, Hillsdale, N. J., 1981. grams, Ph. D. Thesis, also Tech. Report 89/2 1, University of Melbourne, 1989.
Scowen, R., Prolog: Working Draft 5.0, N72, 1991. Steele, G. L., Jr. and Sussman, G. J., The Art of the Interpreter, or the Modu-
larity Complex, Tech. Memorandum AIM-453, MIT AI-Lab, May 1978.
Sedgewick, R., Algorithms, Addison-Wesley, Reading, Massachusetts, 1983.
Sterling, L. S., Expert System = Knowledge + Meta-Interpreter, Tech. Report
Sergot, M., A Query the User Facility for Logic Programming, in Integrated (384-17, Weizmann Institute of Science, Rehovot, Israel, 1984.
Interactive Computer Systems, North-Holland, Amsterdam, 1983.
Sterling, L. S. (ed.), The Practice o f Prolog, MIT Press, Cambridge, Massachu-
Shapiro, E., Algorithmic Program Debugging, MIT Press, Cambridge, Massa- setts, 1990.
chusetts, 1983a.
Sterling, L. S. and Beer, R. D., Meta-Interpreters for Expert System Construc-
Shapiro, E., .4 Subset of Concurrent Prolog and Its Interpreter, Tech. Report tion, J. Logic Programming 6(l-2), pp. 163-178, 1989.
TR-003, ICOT-Institute for New Generation Computer Technology, Tokyo,
Japan, 1983b. Sterling, L. S. and Bundy, A., Meta-Level Inference and Program Verification, in
Proc. of the Sixth Conference on Automated Deduction, pp. 144-150, Springer-
Shapiro, E., Logic Programs with Uncertainties: A Tool for Implementing Rule- Verlag LNCS 138, 1982.
Based Systems, Proc. Eighth International Joint Conference on Artificial Intel-
ligence, pp. 5 29-532, Karlsruhe, Germany, 1983c. Sterling, L. S., Bundy, A., Byrd, L., O'Keefe, R., and Silver, B., Solving symbolic
equations with PRESS, in Computer Algebra, pp. 109-116, Springer-Verlag
Shapiro, E., Playing Mastermind Logically, SlGART Newsletter 85, pp. 28-29,
LNCS 144, 1982.
1983d.
Sterling, L. S. and Codish, M., PRESSing for Parallelism: A Prolog Pro-
Shapiro, E., Alternation and the Computational Complexity of Logic Pro-
gram Made Concurrent, J. Logic Programming 3, pp. 75-92, 1986.
grams, J. Logic Programming 1, pp. 19-33, 1984.
Shapiro, E., Systems Programming in Concurrent Prolog, in (van Caneghem Sterling, L. S. and Kirschenbaum, M., Applying Techniques to Skeletons, in
and Warren, 1986),pp. 50-74. Constructing Logic Programs, J. M. J . Jacquet (ed.), pp.127-140. Wiley, New
York, 1993.
Shapiro, E. and Takeuchi, A,, Object Oriented Programming in Concurrent
Prolog, New Generation Computing 1, pp. 25-48, 1983. Sterling, L. S. and Lalee, M., An Explanation Shell for Expert Systems, Compu-
tational Intelligence 2, pp. 136-14 1, 1986.
Shortliffe, E. H., Computer Based Medical Consultation, MYCIN, North-
Holland, New York, 1976. Sterling, L. S. and Lakhotia, A., Composing Prolog Meta-Interpreters, in Proc.
Fifth International Conference on Logic Programming, K. Bowen, and R.
Silver, B., Meta-level Inference, Elsevier Science, Amsterdam, Netherlands, Kowalski, (eds.),pp. 386-403, MIT Press, Cambridge, Massachusetts, 1988.
1986.
Sterling, L. S. and Yalqinalp, L. U., Explaining Prolog Computations llsing a
Silverman, W., Hirsch, M., Houri, A. and Shapiro, E., The Logix System User Layered Meta-Interpreter, in Proc. IJCAI-89, pp. 66-71, Morgan Kaufmann,
Manual, Weizmann Institute of Science, Rehovot, Israel, 1986. 1989.
References References

Takeuchi, A. and Furukawa, K., Partial Evaluation of Prolog Programs and Its Warren, D. H. D., Perpetual Processes-An Unexploited Prolog Technique, in
Application to Meta Programming, Information Processing 86, H. J. Kugler Proc. Prolog Programming Environments Workshop, Sweden, 1982b.
(ed.),pp. 415-420, Elsevier, New York, 1986.
Warren, D. H. D., An Abstract Prolog Instruction Set, Tech. Note 309, SRI
Tamaki, H. and Sato, T., Unfold/Fold Transformations of Logic Programs, International, Menlo Park, California, 1983.
Proc. Second International Conference on Logic Programming, pp. 127-138,
Uppsala, Sweden, 1984. Warren, D. H. D., Optimizing Tail Recursion in Prolog, in (van Caneghem and
Warren, 1986),pp. 77-90.
Tarnlund, S.-A. (ed.), Proc. of the Logic Programming Workshop, Debrecen,
Hungary, 1980. Warren, D. H. D., Pereira, F., and Pereira L. M., User's Guide to DECsystem-10
Prolog, Occasional Paper 15, Department of Artificial Intelligence, University
Tick, E., Parallel Logic Programming, MIT Press, Cambridge, Massachusetts, of Edinburgh, Scotland, 1979.
1991.
Weizenbaum, J., ELIZA-A Computer Program for the Study of Natural Lan-
Ueda, K., Guarded Horn Clauses, ICOT Tech. Report 103, ICOT, Tokyo, Japan, guage Communication between Man and Machine, CACM 9, pp. 36-45, 1966.
1985.
Weizenbaum, J., Computer Power and Human Reason, W. H. Freeman & Co.,
Ullman, J. D., Principles o f Database Systems, 2nd Edition, Computer Science 1976.
Press, 1982.
Welham, R., Geometry Problem Solving, Research Report 14, Department of
Ullman, J. D., Principles o f Database and Knowledge-Base Symbols, Volume 1, Artificial Intelligence, University of Edinburgh, Scotland, 1976.
Computer Science Press, 1989.
M'inograd, T., Language as a Cognitive Process, Volume I . Syntax, Addison-
van Caneghem, M. (ed.),Prolog-II User's Manual, 1982. Wesley, Reading, Massachusetts, 1983.
van Caneghem, M. and Warren, D. H. D. (eds.), Logic Programming and its Winston, P. H., Artificial Intelligence, Addison-Wesley, Reading, Massachu-
Applications, Ablex Publishing Co., 1986. setts, 1977.
van Emden, M., Warren's Doctrine on the Slash, Logic Programming Newslet- Yalqinalp, L.u.,Meta-Programming for Knowledge-Based Systems in Prolog,
ter, December, 1982. Ph. D. Thesis, available as Tech. Report TR 91-141, Center for Automation
and Intelligent Systems Research, Case Western Reserve University, Cleve-
van Emden, M. and Kowalski, R., The Semantics of Predicate Logic as a Pro- land, 1991.
gramming Language, Comm. ACM 23, pp. 733-742, 1976.
Yalqinalp, L. U. and Sterling, L. S., An Integrated Interpreter for Explaining
Venken, R., A Prolog Meta-Interpreter for Partial Evaluation and its Appli- Prolog's Successes and Failures, in (Abramson and Rogers, 1989), pp. 191-
cation to Source-to-Source Transformation and Query Optimization, in Proc. 203.
European Conference on Artificial Intelligence, pp. 91-100, Pisa, 1984.
Yalqinalp, L. U. and Sterling, L. S., lincertainty Reasoning in Prolog with Lay-
Warren, D. H. D., Generating Conditional Plans and Programs, Proc. AISB ered Meta-Interpreters, in Proc. Seventh Conference on Artificial Intelligence
Summer Conference, pp. 344-354, Edinburgh, Scotland, 1976. Applications, pp. 398-402, IEEE Computer Society Press, February 1991.
Warren, D. H. D., Implementing Prolog-Compiling Logic Programs 1 and 2,
DAI Research Reports 39 and 40, University of Edinburgh, Scotland, 1977.
Warren, D. H. D., Logic Programming and Compiler Writing, Soffware-Practice
and Experience 10(2),pp. 97-125, 1980.
Warren, D. H. D., Higher-Order Extensions to Prolog: Are They Needed?, Ma-
chine Intelligence 10, pp. 441-454, Hayes, Michie and Pao (eds.), Ellis Hor-
wood Publications, Wiley, New York, 1982a.
Index

Abstract interpretation, 147 Prolog programs for, 150-161


Abstract interpreter, 22-24, 91-96, Arithmetic evaluation, 150-1 51
119-123 Arithmetic expression, 151, 467
Accumulator, 146-147, 155-1 57, Arity, 13, 27, 32
166, 240, 254, 266, 287-288, Askable goals for expert systems,
309 344-346
bottom-up construction of lists, Assembler, 475-477
145-146 a s s e r t , 220-221,231-232, 304
definition, 155 Assignment, 125, 170
generalization to difference-list, Atom, 11, 27, 164
287-288 atom, 163-164
Abramson, Harvey, 388 atomic, 164
Ackermam's function, 53-5 5 , 85 Automata, 3 19-323
Ait-Kaci, Hassan, 127, 2 13
All-solutions predicates, 302-304 Backtraclung, 120-122, 125, 190,
Alpha-beta pruning, 405-407 192, 218, 231, 249-250, 280,
Alphabetic variant, 88, 200 324
Alternating Turing machne, 115 deep, 124
ANALOGY, 270,272,281 intelligent, 280
Ancestor cut, 354 shallow, 124
Anonymous variable, 234 bagof, 303, 317
APES expert system shell, 355 Bansal, Arvind, 280
append, 60-62, 93-94, 104-105, 109, Barklund, Jonas, 187
122, 131, 133, 140, 196-197, Beer, Randall, 3 73
288, 311 Benchmark, 209
Appending. See Concatenation Ben-David, Arie, 438
Apt, Krzystof, 99, 1056 Berge, Claude, 423
arg, 167-168, 173, 174 Bergman, Marc, 4 57
Argument, 13, 27, 171 Best-first search, 396-400
Arithmetic Binary predicate, 38, 44
logic programs for, 45-55 Binary tree, 72-77, 85, 295
Binding, 9 1 body, 18 compound, 163-164 Correctness of program, 26, 28,
Bips, 150 definition, 18, 27 Compound term, 13-14, 27, 35, 37, 46-49, 106, 153
Bird, R., 245 head, 18 108, 167-173 Cotta, J., 85, 282
Bloch, Charlene, 162 Horn, 18 size of, 108 Counterexample, 334
Blocks world problem, 269-2 71 indexing, 196-197, 209-2 10 Computation Credit evaluation system, 439-457
Body of a clause, 18 iterative, 18, 28 deterministic, 95, 112 Cut, 189-213, 327,449
Boolean formula, 82-83 nondeterministic choice, 24, 120 goal, 92, 96, 120 definition, 190
Bottom-up construction, 145-146, order. See Rule order nondeterministic, 112 effect of, 190-192,202-205
287 reduction, 95, 120, 326 nonterminating, 92, 106-107, 111, effect on storage space, 192,
Bottom-up evaluation, 44 unit, 18, 28 132-133, 332, 386. 208-209
Bottom-up implementation, 237 clause, 219, 324-325 of logic program, 22, 28, 92 in expressing mutually exclusive
Bottom-up parser, 388 Clause reduction level, 326 output, 91 test, 189
Bowen, Dave, 127 Clocksin, William, 85, 128 parallel, 99 -fail combination. See Cut-fail
Bowen, Ken, 85 Closed world assumption, 115 of Prolog program, 120- 123 combination
Boyer, Robert, 85 Code generation (for PL), 470-475 redundant, 138 green. See Green cut
Breadth-first search, 266, 305-307, Codish, Michael, 457 Concatenation in if-then-else statement, 205
396, 398 Coelho, Helder, 85, 282, 354 of lists, 60-61 incompleteness in implementing
Broderick, David, 3 17 Cohen, Jacques, 127, 128 of difference-lists, 284 negation as failure, 199
Bruffaerts, A., 355 Colmerauer, Alain, 99, 127, 187, 212, Conceptual dependency, 276, 281 loss of flexibility, 194, 204, 205
Bruynooghe, Maurice, 2 12, 282, 300 354, 388 Concurrent logic language, 99, 23 1 red, 195, 202-205
Builtin predicates, 150, 326-327 Coloring planar maps, 255-257 Concurrent Prolog, 120, 179, 186, restriction on rule order, 198
Bundy, Alan, 85, 99, 281, 457 Comments, 33, 235 457 rule order in negation as failure,
Burstall, Rod, 373 Common instance, 16, 88 Conjunctive goal, 92, 219 198
Byrd, Lawrence, 457 Commutative relation, 132 Conjunctive query, 16-18 scope, 195, 327
Comparison of Prolog with conven- Cons pair, 58 search tree pruning, 190- 191
c a l l , 186 tional languages, 124-126 Consing, 2 11, 284 simulation in meta-interpreter,
car, 56 Comparison operator, 153 Constant, 27, 108, 164 327
Cartesian product, in relational Compiler for PL,459-475 constant, 164 Cut-fail combination, 20 1-202
algebra, 43 Compiler for Prolog, 2 13 consult, 230-231 implementing meta-logical
cdr, 56 Complete list, 107, 133 Context-dependent information, 385 predicate, 201-202
Certainty factor, 330 Complete natural number, 107 Context, finding keywords in, 3 11- safe use, 201
Certainty threshold, 330-331 Complete structure, 133 3 13
Chain rule, for derivatives, 80-81 Complete type, 107 Context-free grammar, 369-3 71, DAG, 264, 305-306
Character I/O, 216-2 18 Completeness 375-377 Dahl, Veronica, 394
Character strings, 216, 2 18 of program, 26, 28,46,48, 106 translation to Prolog program, Darlington, John, 373
Chen, Weidong, 3 18 of recursive data structure, 107 372-373 Data abstraction, 35-38, 258
Chikayama, Takash, 2 12 Complexity, 108-109 Continuation-style meta-interpreter, Data manipulation, in Prolog, 125
Choice point, 208 depth, 109 326 Data structure, 125, 143-147, 171,
Chow, D., 99 goal-size, 109 Control flow, 124, 133, 238-240 211, 274, 283-300
Circuit, logic program, 32-34 length, 108 Conventional language, comparison creation in Prolog, 125
Circular definition, 133 time, 108, 209 with Prolog, 124-126 cyclic, 298
Clark, Keith, 115, 127, 147, 212, 282, COMPOSERS, 244 copy-term, 224 incomplete, 146, 274, 283-300
300, 355 Composition, 239, 366-368, 374 Co-routining, 147, 187, 280 in Prolog, 143-147
Clause, 18, 24, 27-28, 95, 120, 219 Composition specification, 367
Database, Differentiation, 79-81, 172 Error condition, 126, 151, 164 Function
deductive, 44 Dincbas, Mehmet, 3 54 Error handling, 126, 151 algebraic, 440
logical, 29 Directed acyclic graph, 264, 305-306 Error, runtime, 151, 153 relationship to relation, 49
relational, 42-44 Directed graph, 40-41, 264 Euclidean algorithm, 54, 152 Functional programming, 3
Davis, Ernie, 281 Disjunction, 2 1, 186 Evaluable predicate. See Builtin Functor, 4, 13-14, 167
DCG. See Definite clause grammar Disjunctive goal, 186 predicates functor, 167-174
Debugging, enhanced meta- Disjunctive relationship, 42 Evaluation of arithmetic expression, Furukawa, Koich, 373
interpreter for, 33 1-340 Disz, Terry, 127 150-151 Futamura, Y., 373
Declarative reading, 19, 57, 65-66, Divide-and-conquer, 69 Evaluation function, 395, 403
324 Divide-and-query, 3 38 Evaluation, partial, 360-365 Gallagher, John, 300, 373
Declarative semantics, 104 Domain, 106, 133, 332 Exception handling, 126, 151 Gallaire, Herve, 44, 3 54
Deductive database, 44 intended, 332 Execution mechanism, 120 Game playing framework, 402
Deep backtracking, 124 termination, 106 Execution model of Prolog, 119- 122 Game tree, 402-403
Default rule, 206-208 Don't-care nondeterminism, 263, Existential quantification, 20 Garbage collection, 197, 231, 423
Definite clause grammar, 375-388, 280 Existential query, 14-1 5 Generalization, 14
466 Don't-know nondeterminism, 263- Expert system Generate-and-test, 69, 249-262, 280
generative feature, 386 264, 280 for credit evaluation, 429-438 in game playing, 4 12
relationshp with context-free Double recursion, 73-74, 165, 287 enhanced interpreters for, 341- optimization, 2 52
grammar, 376, 377 Drabent, W., 354 3 54 Generator, 250, 252, 254
Denotational semantics, 105 Dudeney, H., 401 Evans, Thomas, 281 get-char, 217-218
Depth-bounded meta-interpreter, Dutch flag problem, 289-290 Explanation shell for expert systems, GHC, 120, 127
332-333 Dwork, Cynthia, 99 341-353 Goal
Depth-complexity, 109 Dynamic predicate, 232 Extra-logical predicate, 2 15-231 conjunctive, 22, 92
Depth-first graph traversal, 266 for i/o, 215-219 computation of, 92, 120
Depth-first search, 112, 120, 130, Editor, 223-226 for program access and manipula- definition, 12
264-266, 389, 396 Edinburgh Prolog, 120, 127, 208 tion, 2 19-22 1 derived, 23, 92
Depth-first traversal of search tree, Eggert, Paul, 99 types of, 2 15 disjunctive, 186
112 Elcock, E., 280 dynamic construction of, 3 15
Derivative, 79-81, 172 Eliza, 273-275, 281 Fact, 11-12, 15-16, 27 ground, 25-26
Dershowitz, Nachum, 354 Empty difference-list, 284 Factorial, 51-52, 155-156 invocation, 92, 128
Destructive assignment, 125, 170 Empty list, 56 Factorization, 440, 448-449 parent, 92
Deterministic computation, 95, Empty queue, 298 Failure-driven loops, 229-23 1, 423 reduction, 22, 92, 95-96
111-112 Empty tree, 72 Failure node, 110 selection, 24
Deville, Yves, 115, 147, 244, 245 Enhancement, 239-241, 326 False solution, 334-339 sibling, 92
Dictionary, 293-296, 300, 470, 477 Equation solving, 4 39-4 5 7 FCP, 457 size, 108
Difference-list, 283-292, 299-300, definition, 440 f i n d a l l , 302, 317 Goal order, 95, 129, 133, 136, 178,
304, 305, 371, 373 factorization, 440, 448-449 Finite failure, 113-1 14 209, 324
compatibility of, 284 homogenization, 441,454-456 Fixpoint of a logic program, 105 and left recursion, 136
concatenation, 284-285 isolation, 440-44 1, 449-4 52 Fixpoint semantics, 105 and nonterminating computation,
head, 283 overview, 439-441 Flattening a list, 164-166, 285-288, 135-136
tail, 284 polynomial, 4 52-454 298-299 comparison with clause order,
Difference-structure, 291-293, 457, quadratic, 441 Fold/unfold, 3 57-360, 373 135
476 simplification, 45 1-4 52 Freezing terms, 183-185 effect on termination, 135
Difference-sum, 291 Ernst, George, 281 Friiwirth, Thom, 280 effect on trace, 95
Grammar. See Context-free grammar; IC-Prolog, 120, 127, 147, 280 Jaffar, Joxan, 99, 115 empty, 56
Definite clause grammar Identity, 13 Jain, Ashish, 245 flattening, 164-166, 285-288,
Grammar rule, 369-372 If-then-else statement, 205, 212 Janson, Sverker, 127 298-299
Granularity, of a meta-interpreter, Incomplete data structure, 146, 274, Join in relational algebra, 43 head, 56
326 283-300 incomplete, 107, 133, 136, 141,
Graph, 40-41, 264-266, 305, 306 Incomplete list, 107, 133, 136, 141, Kahn, Ken, 3 17 144, 283, 293
connectivity, 40-41, 266 144,283, 293 Kahn, Gilles, 245 length, 64, 160-161, 177
cyclic, 305-306 Incomplete structure, 146, 287 Kalah, 420-427 merging, 137-138, 189-192
directed, 40-41, 264-266 Incomplete type, 107 Kaminski, Steven, 280 splitting, 61
directed acyclic, 264-266, 305-306 Indexing, 196-197,210 Kanoui, H., 457 tail, 56
Greatest common divisor, 54, 152 Infinite graph, 266, 305 Key-value pairs, 294 type definition, 57
Green cut, 184-195, 212,449 Infinite search tree, 96, 111, 131 Kirschenbaum, Marc, 244, 245, 374 Lloyd, John, 99, 115, 187, 300
effect on declarative meaning, 194 Inorder traversal of binary tree, 75 Knuth, Donald, 85 Logging facility, 2 2 7-228
Gregory, Steve, 127, 198 Input/output at the character level, Komorowski, Jan, 3 73 Logic program,
Ground 216-218 Kowalslu, Robert, 44, 115, 127, 147, definition, 20, 27
definition, 14 Input/output for reading in a list of 280 interpretation, 103-104
goal, 25-26, 93 words, 2 17-2 18 Kunen, Ken, 115 meaning, 25-26, 28
instance, 24, 26 Insertion sort, 69-70, 333 KWIC, 311-313, 318 Logic puzzles, 258-261, 280
object, 183-183 Instance, Logical consequence, 17, 20
reduction, 2 3 common, 1 6 , 8 8 Lakhotia, Arun, 245, 374 Logical deduction, 16, 20, 27
representation, 187 definition, 14, 27, 88 Lambda calculus, 1 19 Logical database, 29
term, 27 ground, 23, 26 Lambda expression, 3 16, 318 Logical disjunction, 186
query, 14 Instantiation, 16 Lambda Prolog, 324 Logical implication, 13, 16
Intelligent backtracking, 280 Lasserre, Claudine, 3 54 Logical variable, 13, 91, 126, 156,
Hammond, Peter, 3 55 Intended domain, 332 Lassez, Jean-Louis, 99 287, 383,476
Haridi, Seif, 127 Interactive Last call optimization, 196-197, 209 LOGLISP, 147
Head of a clause, 18 loop, 223 Lee algorithm, 306-3 11, 3 17
Head of a list, 58 program, 223-230 Lee, Y., 354 Maier, David, 44, 213
Heap, 208, 21 1 prompting, 346 Left recursive rule, 132 Maher, Michael, 99
Heapify a binary tree, 75-77 i n t e g e r , 163-164 Lemma, 22 1 Maintenance, 242
Heap property, 75 Interchange sort, 194-195 Length complexity, 109 Mapping of list, 143, 314-315
Heintze, Nevin, 187 Interpretation of a logic program, Length of list, 64, 160-161, 177 Marriott, Kim, 99, 300
Henin, Eric, 3 5 5 103-104 Le Pape, J., 354 Marseilles Prolog, 127-128, 2 12
Herbrand base, 102-105 Interpreter Lexical analysis, 461 Martelli, Alberto, 99
Herbrand universe, 102 abstract. See Abstract interpreter Li, Deyi, 44 Mastermind, 41 1-414,423
Heuristic search, 395 of automata, 319-322 Lim, Pierre, 187 Matsumoto, Y., 388
Hill-climbing, 396-398 meta-, 227, 323-341 Lndholm, Tim, 232 McCabe, Frank, 127, 147, 282, 355
Hill, Pat, 187 Intersection in relational algebra, 43 Linear recursive, 40 McSAM, 274, 276-278, 281
Hill, R., 1 15 Isolation, 440-44 1, 449-452 LIPS, 209 Mellish, Chris, 85, 128, 147
HiLog, 324 Isomorphism of binary trees, 74, Lisp, 119, 317, 373 Meaning, 25-26, 28
Hirschmann, Lynette, 388 264 List, 56-64, 125-126, 133, 135, declarative, 104
Homogenization, 44 1,454-456 Iteration, 154-159 137-146, 158 definition, 25
Horn clause, 18 Iterative clause, 18, 28, 154 complete, 107, 133 intended, 25, 28, 105-106, 332,
definition, 5 7 340
Meaning (cont.) Multiple solutions, 49-50, 243, 302 O'Keefe, Richard, 174, 186, 21 2, 213, best-first framework, 399-400
of logic program, 25-26 Multiple success node, 11 232,244,281, 300, 317, 354 hill-climbing framework, 397
Melting frozen terms, 184, 296 Operational semantics, 102 searchmg state space, 389-398
member, 57-58, 59, 61, 130-131, 133, N queens problem, 252-255,280 Operator, 31, 150,479-481 Procedural semantics, 102
138, 140,205, 250, 251 Nadathur, Gopalan, 3 18 Oracle, 33 5 Procedure
Memo-function, 221-222, 232, 396 Naish, Lee, 127, 147, 213, 317 defmtion, 2 1
Merge sort, 69 Nakashima,, H., 186-187 Palindrome, 3 22 invocation, 124
Merging sorted lists, 137-138, 189- Natural number, 46-5 1, 102, 104 Parent goal, 92 Program
192 NDFA, 319-32 1 PARLOG, 120,127 access and manipulation, 2 19-22 1
Meta-arguments, 373 Negation as failure, 113-1 15, 198- Parser for PL, 466-469 completeness, 26, 28,46, 48, 105
Meta-interpreter, 227, 242, 323-341 200 Parse tree, 383 complexity, 108-109
for debugging, 33 1-340, 354 Negation in logic programs, 113-1 15, Parsing with DCGs, 375-388 correctness, 26, 28, 46-49, 105-
definition, 323 199 Partial evaluation, 360, 373-374 106, 153
depth-bounded, 332-333 Nilsson, Martin, 162 Partial reduction, 360-36 5 definition, 12, 20, 27
enhanced, 324, 328-331, Nim, 41 5-420,423 Pascal, 378-379,459 development, 235-238
granularity, 326 Nonground representation, 187 Pattern matching, 273-275, 278 functional, 3, 49
proof tree, 329-330, 337-338 Nonterminal symbols in grammar, Pereira, Fernando, 128, 388 maintenance, 242
run-time overhead, 373 369-370 Pereira, Luis, 280, 354 relational, 3, 49
Meta-logical predicate, 175-1 86, 201, Nonterminating computation, 92, Permutation sort, 68-69, 2 52 termination, 106, 131-133, 147
317 111, 131-133, 153, 332-334, 386 Perpetual process, 23 1, 300 Programming
Meta-logical test, 152, 176-1 78 Nondeterminism, 24, 95-96, 112, PL, 459-460 with side-effects, 220, 231, 237
Meta-programming, 186, 3 19, 354, 249-280 Plaisted, David, 99 bottom-up, 237
366 combining with negation as failure, Pliimer, Lutz, 147 interactive, 223-230
Meta-variable facility, 155-1 56, 2 15, 303 Poker, 74, 87 style and layout, 233-235
304 combining with second-order, 306 Polynomial, 78-79, 452-454 Projection in relational algebra, 43
Mgu, 88 definition, 24, 95-96 coefficient representation, 4 52 Prolog
Michie, Donald, 232 don't-care, 263, 280 using cut, 193 comparison with conventional
Miller, Dale, 3 18 don't-know, 263-264, 280 Postorder traversal, 75, 335 languages, 124-126
Minimal model, 104 in game playing, 4 12 Power, John, 244 computation, 120- 122
Minimal recursive program, 46, 132, Nondeterministic choice, 24, 95, 119 Powers, David, 423 execution mechanism, 120
140 Nondeterministic computation, 111- Predicate higher order extension, 3 14-3 18
Minimax algorithm, 404-407 112 definition, 11 program efficiency, 208-2 11
Minker, Jack, 44 Nondeterministic finite automata, dynamic, 232 pure, 119, 235, 326, 332
Minsky, Marvin, 281 319-321 evaluable, 149 Prolog 11, 120, 127
Missing solution, 339-340 nonvar, 176, 178 extra-logical, 2 15-23 1 Proof tree, 25,47, 58, 60-61, 63, 112,
Model of a logic program, 104 not, 198-200 names, 29,233-234 329-330, 337-338
Mode of use, 243 NPDA, 322 static, 232 ProSE group, 244
Module, 244 Number, 46-51, 152 structure inspection, 163-1 74 Prototyping, 23 7
Monotonic mapping, 105 parsing, 386-387 system, 149 Pushdown automata, 321-322
Montanari, Ugo, 99 recursive structure, 152 Preorder traversal, 75 Puzzle solver. 258-261
Moore, J., 85 PRESS, 439,441,457
Moss, Chris, 212, 232 Occurs check, 89, 90-91, 99, 109, Priority of operators, 479-480 Quantification, 15, 18-20
Most general unifier, 88 179,298 Problem solving Query, 12-18, 28
MU-Prolog, 120, 127, 147 Offenders set, 441, 455 depth-first framework, 390 conjunctive, 16-18
Query (cont.) Rotating of list, 3 12 procedural, 102 Standard Prolog, 128, 150, 232, 244,
definition, 12 Roussel, Phillipe, 127 Sergot, Marek, 355 317
existential, 14-1 5 rplacd, 284-285 Set difference, in relational algebra, State space graph, 389, 402
simple, 12, 17 Rule, 18-21, 39-41, 130 42 State space search, 380-398
Queue, 297-300 body, 1 8 , 2 7 s e t o f , 303, 317 Static predicate, 232
negative elements, 299 default, 206-208 Set predicate, 303, 31 5-314 Steel, Sam, 213
Quicksort, 69-71, 122-123, 135, definition, 18, 27 implementation, 304 Stepwise refinement, 67
288-289 head, 18, 27 S-expression, 285 Stepwise enhancement, 240-242,
recursive, 39-41 Shared variable, 15, 17, 43, 126, 199, 244, 366
read, 215-216 Rule order, 129-131, 198 2 56 Sterling, Leon, 85, 244, 245, 280,
r e a l , 164 effect on solution, 129-130 in conjunctive query, 17, 43 281, 355, 373, 374,438,457
Record structure, 126 Runtime error, 151, 153 in negation as failure, 199 Stimulus/response pair, 273-2 74
Recursion, 154-162, 195-198 instantiation, 17 Stream, 23 1, 300
tail, 154-162, 192, 209, 213 Safra, Shmuel, 232 Shapiro, Ehud, 115, 127, 186, 231, String manipulation, 2 16
Recursive computation, 154 Sahlin, Dan, 3 74 281, 354,423 Structure
Recursive data structure, 107, 291 Sato, T., 373 Shell, 226-228, 341-353, 355 incomplete, 146, 287
heuristics for goal order, 136 Scheduling policy, 93, 119 Sibert, Ernie, 147 incremental buildup, 292
Recursive rule, 39-42, 132-133, 386 Schemas, 244 Sibling goal, 92 recursive, 291
left, 132 Scope of cut in meta-interpreter, 327 Sicstus Prolog, 187 Structured data, 35-38
linear, 40 Scope of variable, 17 Side effects, 215, 220, 227, Structure inspection, 163-1 74, 178,
Red cut, 195, 202-205 Script, 274, 276-278 231 3 15
Reduced term, 44 1 , 45 5 Search Silver, Bernard, 4 57 Stuckey, Peter, 187
Reduction, 22-23, 92-95, 111 best-first, 396-400 Simple query, 12-18 Subject/object number agreement in
Redundant computation, 138 breadth-first, 266, 305-307, 396, Simplification of expression, 3 74 grammar, 384-385
Redundant solution, 136-1 38, 192 398 Skeleton, 238, 240-242, 244-245, Substitution, 14, 27, 88
Redundancy, 264 depth-first, 112, 120, 130, 264- 366-368 in a list, 71
Reiter, Raymond, 115 266, 389, 396 SLD tree. See Search tree in a term, 169-171, 182-183
Relation state space, 389-398 SLD resolution, 115 in a tree, 75
definition, 11, 30 Searching game tree, 401-407 Smith, D., 374 Successor function, 46
relationshp to function, 49 Searching state space, 284-294 Snips, 2 12 Success node, 110
Relational algebra, 42-44 Search tree, 96, 110-112, 130-131, Software engineering, 242 Symbolic expression manipulation,
Relational database, 42-44 191 S~ndergaard,Harald, 300 78-81,439,457
Relational Language, 127 pruning using cut, 190-192 Sort System predicates, 149-152
Relation scheme, 29, 242 Second-order predicate, 3 14-3 18 insertion, 69-70, 333
Relocatable code, 461 Second-order programming, 30 1- interchange, 194-195 Tail of a list, 58
Renaming, 92 318 merge, 69 Tail recursion optimization, 192,
repeat, 230 combining with nondeterministic permutation, 68-69, 252 196-197,209,213, 231,423
Repeat loop, 230-231 programming, 306 quick, 69-71, 122-123, 135, 288- Tail recursive loop, 2 3 1, 42 3
Resolvent, 22, 92, 96 Selection in relational algebra, 43 289 Term, 11, 13-14, 27, 35,88,167-169,
r e t r a c t , 220-221, 231-232, 304 Semantics, 101-105 Specification, 242-243 180- 185
Reusable code, 242, declarative, 104 Specification formalism, 236 accessing, 167-168
Reversing a list, 62-64, 136 denotational, 105 Stack, 89, 166, 208-209, 321-322 building given list, 173
Robinson, Alan, 98, 1 15, 147 fixpoint, 105 overflow, 33 3 compound, 13-14, 27, 35, 37, 108,
Ross, Peter, 232, 244, 317 operational, 102 scheduling policy, 93, 119 167-173
Term (cont.) complete, 107 size, 108
copying, 183, 184 condition, 50-5 1, 58 type, 126
definition, 13 delirution, 4 5 type checking, 256
finding subterm, 168-169 incomplete, 107 Van Caneghem, Michel, 127,2 13
identity, 180 meta-logical predicate, 176 Venken, Raf, 3 73
reading, 2 15 predicate, 163-164, 176 Verification, 236
size, 108 recursive, 45
substitution, 169-1 71 WAM, 2 13
unification. See Unification Ueda, Kazunori, 1 2 7 Warren Abstract Machme, 2 13
writing, 2 15 Unary relation, type of a term, Warren, David H. D., 127, 128, 213,
Takeuchi, Alukazu, 373 163-164 232, 282, 300, 317, 318, 388,
Tarnaki, H., 373 Uncertainty reasoning, 330-33 1, 478
Tarnlund, Sten-Ake, 300 354-355 Warren, David Scott, 127, 213
Techmques, 239-242,244-245 Unfolding, 286, 288, 357-360, 373 Weak cut, 212
Tester, 250, 252, 254 Unification, 87-91, 98-99, 109, 125- Weizenbaum, Joseph, 281
Thawing terms, 184, 126, 127, 143, 179-180, 251, Welham, Bob, 457
Thompson, Henry, 281 286 Why explanation, 346
Tick, Evan, 280 algorithm, 88-90, 179-180 Wilson, Walter, 407
Time complexity, 108, 209 including occurs check, 179- 180 Winograd, Terry, 388
Top-down construction of struc- Unifier, 88 write, 215
tures, 144-145, 237, 286-288, Union in relational algebra, 42
293, 383 Unit clause, 18, 28 Yalqinalp, Umit, 354, 355
Top-down development, 65-67 univ, 171-173
Top-down evaluation, 44 Universal modus ponens, 19, 101 Zebra puzzle, 262-263,280
Towers of Hanoi, 81-82, 97-98, Universal quantification, 15, 18-19 Zero-sum game, 404
221-222 Universally quantified fact, 15-16
Trace, 22-23, 92, 96-98, 120-123,
328, 287 Van Emden, Maarten, 99, 115, 2 1 2
as meta-program, 328 Vanilla meta-interpreter, 324
of meta-interpreter, 325 var, 176, 317
of Prolog computation, 120- 123 Variable
Transformation, of recursion to anonymous, 2 34
iteration, 154-1 59 binding, 91
Transitive closure of relation, 40-41 definition, 13
Tree difference in Prolog, 13
binary, 72-77, 85, 295 identity testing, 180-182
empty, 72 logical, 13, 91, 126, 156, 287, 383,
game, 404-403 376
isomorphism of, 74, 264 mnemonics, 29
parse, 383 as object, 182-185
search, 96, 110-112, 130-131, 191 predicate names, 3 15
traversal, 75, 335 renaming, 88,92
Turing machme, 115, 323 scope, 17
Type, 45, 242 shared, 15, 17, 43, 126, 256
Logic Programming
Ehud Shapiro, editor
Koich Furukawa, Jean-Louis Lassez, Fernando Pereira, and
David H. D. Warren, associate editors
The Art o f Prolog: Advanced Programming Techniques, Leon Sterling and
Ehud Shapiro, 1986
Logic Programming: Proceedings of the Fourth lnternational Conference (vol-
umes 1 and 2), edited by Jean-Louis Lassez, 1987
Concurrent Prolog: Collected Papers (volumes 1 and 2 ) , edited by Ehud
Shapiro, 1987
Logic Programming: Proceedings of the Fifth International Conference and
Symposium (volumes 1 and 2), edited by Robert A. Kowalski and Kenneth A.
Bowen, 1988
Constraint Satisfaction in Logic Programming, Pascal Van Hentenryck, 1989
Logic-Based Knowledge Representation, edited b y Peter Jackson, Han Re-
ichgelt, and Frank van Harmelen, 1989
Logic Programming: Proceedings o f the Sixth International Conference, edited
by Giorgio Levi and Maurizio Martelli, 1989
Meta-Programming in Logic Programming, edited by Harvey Abramson and
M. H. Rogers, 1989
Logic Programming: Proceedings of the North American Conference 1989
(volumes 1 and 2 ) , edited by Ewing L. Lusk and Ross A. Overbeek, 1989
Logic Programming: Proceedings of the 1990 North American Conference,
edited by Saumya Debray and Manuel Hermenegildo, 1990
Logic Programming: Proceedings of the Seventh International Conference,
edited by David H. D. Warren and Peter Szeredi, 1990
The Craft of Prolog, Richard A. O'Keefe, 1990
The Practice of Prolog, edited by Leon S. Sterling, 1990
Eco-Logic: Logic-Based Approaches to Ecological Modelling, David Robertson,
Alan Bundy, Robert Muetzelfeldt, Mandy Haggith, and Michael Uschold, 1991
Warren's Abstract Machine: A Tutorial Reconstruction, Hassan Ai't-Kaci, 1991
Parallel Logic Programming, Evan Tick, 1991
Logic Programming: Proceedings of the Eighth International Conference,
edited by Koichi Furukawa, 1991
Logic Programming: Proceedings of the 1991 International Symposium, edited
by Vijay Saraswat and Kazunori Ueda, 1991
Foundations o f Disjunctive Logic Programming, Jorge Lobo, Jack Minker, and
Arcot Rajasekar, 1992
Types in Logic Programming, edited by Frank Pfenning, 1992
Logic Programming: Proceedings of the Joint International Conference and
Symposium on Logic Programming, edited by Krzysztof Apt, 1992
Concurrent Constraint Programming, Vijay A. Saraswat, 1993
Logic Programming Languages: Constraints, Functions, and Objects, edited by
K. R. Apt, J. W. de Bakker, and J. J. M. M. Rutten, 1993
Logic Programming: Proceedings of the Tenth International Conference on
Logic Programming, edited by David S. Warren, 1993
Constraint Logic Programming: Selected Research, edited by Frederic Ben-
hamou and Alain Colmerauer, 1993
A Grammatical View of Logic Programming, Pierre Deransart and Jan
Maluszynski, 1993
Logic Programming: Proceedings of the 1993International Symposium, edited
by Dale Miller, 1993
The Godel Programming Language, Patricia Hill and John Lloyd, 1994
The Art of Prolog: Advanced Programming Techniques, second edition, Leon
Sterling and Ehud Shapiro, 1994

You might also like