100% encontró este documento útil (2 votos)
373 vistas550 páginas

El Arte de Prolog

Introducción ala programación lógica en el lenguaje Prolog.
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF o lee en línea desde Scribd
100% encontró este documento útil (2 votos)
373 vistas550 páginas

El Arte de Prolog

Introducción ala programación lógica en el lenguaje Prolog.
Derechos de autor
© © All Rights Reserved
Nos tomamos en serio los derechos de los contenidos. Si sospechas que se trata de tu contenido, reclámalo aquí.
Formatos disponibles
Descarga como PDF o lee en línea desde Scribd
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 1986, 1994 Massachusetts Institute of Technology All rights reserved. No part of this book may be reproduced in any form by any electronic or mechanical means (including photocopying, recording, or information storage and retrieval) without permission in writing from the publisher, This book was composed and typeset by Paul C. Anagnostopoulos and Joe Snowden using ZzT3X. 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 \-heights and open Interiors that aid legibility in modern printing technology, but also echo some of the rhythms and calligraphic details of lively Renaissance handwrit Ing. Developed in the 1980s and 1990s, the extensive Lucida typeface family includes a wide varlety of mathematical and technical symbols designed to harmonize with the text faces. This book was printed and bound in the United States of America. Library of Congress 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. U, Title. Il, Serles. QA76.73.P76874 1994 005.13'3—de20 93-49494 ‘ataloging-in-Publication Data cw To Ruth, Miriam, Michal, Danya, and Sara Contents Figures xiii Programs — xvii Series Foreword xxv Foreword xxvii Preface xxi Preface to First Edition xxv Introduction 1 I Logic Programs 1 Basic Constructs 11 Ll Facts 11 1.2 Queries 12 1.3. The Logical Variable, Substitutions, and Instances 13 14 Existential Queries 14 1.5. Universal Facts 15 1.6 Conjunctive Queries and Shared Variables 16 L7 Rules 18 vill Contents 1.8 A Simple Abstract Interpreter 22 1.9. The Meaning of a Logic Program 25 1.10 Summary 27 2 Database Programming 29 Simple Databases 29 Structured Data and Data Abstraction 35 Recursive Rules 39 Logic Programs and the Relational Database Model 42 Background 44 Recursive Programming 45 3.1 Arithmetic 45 3.2 Lists 56 3.3. Composing Recursive Programs 65 34 Binary Trees 72 3.5. Manipulating Symbolic Expressions 78 3.6 Background 84 4 The Computation Model of Logic Programs 87 4.1 Unification 87 4.2. An Abstract Interpreter for Logic Programs 91 4.3. Background 98 5 Theory of Logic Programs 101 1 Semantics 101 Program Correctness 105 Complexity 108 Search Trees 110 Negation in Logic Programming 113 Background 115 ix Contents, Il The Prolog Language 117 6 Pure Prolog 119 6.1 The Execution Model of Prolog 119 6.2 Comparison to Conventional Programming Languages 124 6.3 Background 127 7 Programming in Pure Prolog 129 7.1 Rule Order 129 Termination 131 Goal Order 133 Redundant Solutions 136 Recursive Programming in Pure Prolog 139 Background 147 8 Arithmetic 149 8.1 System Pre‘ 8.2. Arithmetic Logic Programs Revisited 152 licates for Arithmetic 149 8.3 Transforming Recursion into Iteration 154 8.4 Background 162 9 Structure Inspection 163 9.1 Type Predicates 163 9.2 Accessing Compound Terms 167 9.3. Background 174 10 Meta-Logical Predicates 175 10.1 Meta-Logical Type Predicates 176 10.2. Comparing Nonground Terms 180 10.3. Variables as Objects 182 104 The Meta-Variable Facility 185 10.5 Background 186 x Contents 11 Cuts and Negation 189 11,1 Green Cuts: Expressing Determinism — 189 11.2. Tail Recursion Optimization 195 11.3 Negation 198 11.4 Red Cuts: Omitting Explicit Conditions 202 11.5 Default Rules 206 11.6 Cuts for Efficiency 208 11.7 Background 212 12 Extra-Logical Predicates 215 12.1, Input/Output 215 12.2. Program Access and Manipulation 219 12.3. Memo-Functions 221 12.4 Interactive Programs 223 12.5. Fallure-Driven Loops 229 12.6 Background 231 13° Program Development 233 13.1 Programming Style and Layout 233 13.2 Reflections on Program Development 235 13.3 Systematizing Program Construction 238 13.4 Background 244 M1 Advanced Prolog Programming Techniques 247 14 Nondeterministic Programming 249 14.1 Generate-and-Test 249 142. Don’tCare and Don't-Know Nondeterminism — 263 14.3 Artificial Intelligence Classics: ANALOGY, ELIZA, and McSAM 270 144 Background 280 15 Incomplete Data Structures 283 15.1. Difference-Lists 283 Contents 16 17 18 19 20 Difference-Structures 291 Dictionaries 293 Queues 297 Background 300 L 1 1 15, Second-Order Programming 301 16.1 All-Solutions Predicates 301 16.2 Applications of Set Predicates 305 16.3 Other Second-Order Predicates 314 164 Background 317 Interpreters 319 17.1 Interpreters for Finite State Machines 319 17.2 Metatnterpreters 323 17.3. Enhanced Meta-Interpreters for Debugging 331 174 An Explanation Shell for Rule-Based Systems 341 17.5 Background 354 Program Transformation 357 18.1 Unfold/Fold Transformations 357 18.2 Partial Reduction 360 18.3 Code Walking 366 18.4 Background 373 Logic Grammars 375 19.1 Definite Clause Grammars 375 19.2. A Grammar Interpreter 380 19.3 Application to Natural Language Understanding 382 19.4 Background 388 Search Techniques 389 20.1 Searching State-Space Graphs 389 20.2 Searching Game Trees 401 20.3 Background 407 Contents IV _ Applications 409 21 Game-Playing Programs 411 22 23 24 21.1 Mastermind 411 212 Nim 415 21.3 Kalah 420 21.4 Background 423 A Credit Evaluation Expert System 429 22.1 Developing the System 429 22.2 Background 438 ‘An Equation Solver 439 23.1 An Overview of Equation Solving 439 23.2 Factorization 448 23.3 Isolation 449 23.4 Polynomial 452 235 Homogenization 454 23.6 Background 457 ACompiler 459 24.1 Overview of the Compiler 459 24.2 The Parser 466 24.3 ‘The Code Generator 470 244 The Assembler 47 245° Background 478 Operators 479 References 483 Index 497 Figures Ll 12 13 2a 2.2 23 24 3 3.2 33 34 35 36 37 4 43 44 45 46 An abstract interpreter to answer ground queries with respect to logic programs 22 Tracing the interpreter 23 A simple proof tree 25 Defining inequality 31 A logical circuit 32 Still-life objects 34 Assimple graph 41 Proof trees establishing completeness of programs 47 Equivalent forms of lists 57 Proof tree verifying alist 58 Proof tree for appending two lists 61 Proof trees for reversing a list 63 Comparing trees for isomorphism 74 A binary tree and a heap that preserves the tree’s shape A unification algorithm 90 An abstract interpreter for logic programs 93 Tracing the appending of two lists 94 Different traces of the same solution 95 Solving the Towers of Hanoi 97 Anonterminating computation 97 xiv Figures Anonterminating computation 107 Two search trees 111 Search tree with multiple success nodes 112 Search tree with an infinite branch 113 ‘Tracing a simple Prolog computation 121 Multiple solutions for splitting a list 122 Tracing a quicksort computation 123 Anonterminating computation 132 Variant search trees 139 Tracing a reverse computation 146 Computing factorials iteratively 15: Basic system type predicates 164 Tracing the substitute predicate 171 The effect of cut 191 Template for a specification 243 A solution to the 4 queens problem 253 ‘A map requiring four colors 25' Directed graphs 265 Initial and final states of a blocks world problem 267 A geometric analogy problem 271 Sample conversation with ELIZA 273 A story filled in by McSAM_— 276 Three analogy’ problems 279 Concatenating differenceslists 285 ‘Tracing a computation using difference-lists 287 ms 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 312 Second-order predicates 315 A simple automaton 321 Unnormalized and normalized s Figures Tracing the meta-interpreter 325 Fragment of a table of builtin predicates 327, Explaining a computation 351 A context-free grammar for the language a*b"c* 371 The water jugs problem 393 Asimple game tree 405 Astarting position for Nim 415 Computing nim-sums 419 Board positions for Kalah 421 Test equations 440 Pos terms 449 APL program for computing factorials 460 Target language instructions 460 Assembly code version of a factorial program — 461 The stages of compilation 461 Output from parsing 470 ‘The generated code 475 in of subterm: The compiled object code 477 Programs 37 38a 3.8b 39 3.10 Bal A biblical family database 12 Biblical family relationships 23 Defining family relationships 31 ‘A circuit for a logical and-gate 33 The circuit database with names 36 Courserules 37 The ancestor relationship 39 Adirected 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 Arecursive definition of modulus 53 Ackermann’s function 54 The Euclidean algorithm — 54 Defining alist 57 Programs 3.12 3.13 3.14 3.15 3.16 a7 318 3.19 3.20 321 3.22 3.23 3.24 3.25 3.26 3.27 3.28 3.29 3.30 3.31 Membership of alist 58 Prefixes and suffixes of alist 59 Determining sublists of lists 60 Appending two lists 60 Reversing alist 62 Determining the length of alist 64 Deleting all occurrences of an element from alist 67 Selecting an element from alist 67 Permutation sort 69 Insertion sort 70 Quicksort 70 Defining binary trees 73 Testing tree membership 73 Determining when trees are isomorphic 74 Substituting for a term in atree 75 Traversals of a binary tree 76 Adjusting a binary tree to satisfy the heap property Recognizing polynomials 79 Derivative rules 80 Towers of Hanoi 82 Satisfiability of Boolean formulae 83 Yet another family example 102 Yet another family example 130 Merging ordered lists 138 Checking for list membership 139 Selecting the first occurrence of an element from alist. 140 Nonmembership of alist. 141 Testing for a subset 142 Testing for a subset 142 Translating word for word 143, Removing duplicates from alist 145 Programs 8.6b 87a 87b 88 89 8.10 Bal 812 ula 9.1b 9.2 93 94 95a 9.5b 10.1 10.2 10.3 10.4 10.5 10.6 107 10.8 Reversing with no duplicates 146 Computing the greatest common divisor of two integers 1 Computing the factorial of a number 153 An iterative factorial 155 Another iterative factorial 136 Generating a range of integers 157 Summing a list of integers 157 Iterative version of summing a list of integers using an accumu- lator 157 Computing inner products of vectors 158 Computing inner products of vectors iteratively 158 ‘Computing the area of polygons 159 Finding the maximum of a list of integers 160 Checking the length of a list 160 Finding the length of alist 161 Generating a list of integers in a given range 161 Flattening a list with double recursion 165, Flattening a list using a stack 166 Finding subterms of aterm 168 A program for substituting in aterm 170 Subterm defined using univ 172 Constructing a list corresponding to aterm — 173 Constructing a term corresponding to alist 174 Multiple uses for plus 176 A multipurpose length program — 177 A more efficient version of grandparent 178 Testing if a term is ground 178 Unification algorithm — 180 U Occurs in 182 ification with the occurs check 181 Numbering the variables ina term 185 Programs 10.9 a M2 13 4 rhe 116 17 118 11.94 11.9 11.10 ILMa L.11b 124 12.2 12.3 24 12.5 126 127 12.8 12.9 13.1 13.2 133 14a 143 144 145 Logical disjunction 186 Merging ordered lists 190 Merging with cuts 192 minimum with cuts 193 Recognizing polynomials 193, Interchange sort 195 Negation as failure 198 Testing if terms are variants 200 Implementing #201 Deleting elements from alist 204 Deleting elements froma list 204 If-then-else statement 205 Determining welfare payments 207 Determining welfare payments 207 Writing alist of terms 216 Reading in alist of words 217 ‘Towers of Hanoi using amemo-function 222 Basic interactive loop 223 Aline editor 224 An interactive shell 226 sion 228 Basic interactive repeat loop 230 Consulting a file 230 Finding the union of two lists 241 Finding the intersection of two lists 241 Logging a ses Finding the union and intersection of two lists 241 Finding parts of speech in a sentence 251 Naive generate-and-test program solving N queens 253, Placing one queen at atime 255 Map coloring 256 Test data for map coloring 257 Programs 146 147 148 149 14.10 1411 14.12 14.13 14.14 14.15 14, 14, 16.2 16.3 16.4 16.5 16.6 16.7 Apuzzle solver 259 A description of a puzzle 260 Connectivity in a finite DAG 265 Finding a path by depth-first search 266 Connectivity ina graph 266 Adepth-first planner 268 ‘Testing the depth-first planner 269 A program solving geometric analogies 272 Testing ANALOGY 273 ELIZA 275 McSAM 277 Testing McSAM 278 Concatenating difference-lists 285 Flatter Reverse with difference-lists 288 14 a list of lists using differencelists 286 Quicksort using difference-lists 289 A solution to the Dutch flag problem 290 Dutch flag with difference-lists 291 Normalizing plus expressions 292 Dictionary lookup from a list of tuples 294 Dictionary lookup in a binary tree 295 Melting aterm 296 Aqqueue process 297 Flattening a list using a queue 298 Sample data 302 Applying set predicates 303 Implementing an all-solutions predicate using difference-lists, assert, and retract 304 Testing connectivity breadth-first ina DAG 306 Testing connectivity breadth-first ina graph 307 Lee routing 310 Producing a keyword in context (KWIQ) index 313 Programs 16.8 17.1 18.6 Second-order predicates in Prolog 316 An interpreter for a nondeterministic finite automaton (NDFA) 320 ‘An NDEA that accepts the language (ab)* 321 An interpreter for a nondeterministic pushdown automaton (NPDA) 322 An NPDA for palindromes over a finite alphabet 322 A meta-interpreter for pure Prolog 324 A meta-interpreter for pure Prolog in continuation style 326 Atracer for Prolog 328 A meta-interpreter for building a proof tree 329 ‘A meta-interpreter for reasoning with uncertainty 330 Reasoning with uncertainty with threshold cutoff 331 A meta-interpreter detecting a stack overflow 333, Anonterminating insertion sort 334 An incorrect and incomplete insertion sort 335 Bottom-up diagnosis of a false solution 336 Top-down diagnosis of a false solution 338 Diagnosing missing solution 340 Oven placement rule-based system 342 A skeleton tworlevel rule interpreter 343 ‘An interactive rule interpreter 345 A two-level rule interpreter carrying rules 347 A two-level rule interpreter with proof trees 348 Explaining a proof 350 An explanation shell 352 A program accepting palindromes A meta-interpreter for determining a residue 361 A simple partial reduction system 362 Specializing an NPDA 363 Specializing a rule interpreter 364 Composing two enhancements of a skeleton 368 val Programs 18.7 18.8 18.9 19.1 Testing program composition 370 A Prolog program parsing the language a*b*c* 371 Translating grammar rules to Prolog clauses 372 Enhancing the language a*h*c* 377 Recognizing the language a“b%c% 377 Parsing the declarative part of a Pascal block 378 A definite clause grammar (DCG) interpreter 381 A DCG interpreter that counts words 382 ADCG context-free grammar 383 ADCG computing a parse tree 384 ADCG with subject/object number agreement 385 ACG for recognizing numbers 387 A depth-first state-transition framework for problem solving 390 Solving the wolf, goat, and cabbage problem 392 Solving the water jugs problem 394 Hill climbing framework for problem solving 397 Test data 398 Best-first framework for problem solving 399 Concise best-first framework for problem solving 400 Framework for playing games 402 Choosing the best move 403 Choosing the best move with the minimax algorithm — 406 Choosing a move using minimax with alpha-beta pruning 407 Playing mastermind 413 A program for playing a winning game of Nim 417 A complete program for playing Kalah 424 A credit evaluation system 432 Test data for the credit evaluation system 437 A program for solving equations 442 ‘A compiler from PL to machine language 462 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. This 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 this development and to nurture it. It is dedicated to the publication of high-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 looking 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. Thad 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 Rand 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 Battant 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, | 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, which 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 Foreword Work on, I typed in facts about some simple actions in a “blocks world” and an initial state of this world. I entered a description of a goal state to be achieved. Prolog spat back at me: 2 meaning it couldn't find a solution. Could it be that a solution was not deducible from the axioms I had supplied? Ab, yes, I had forgotten to enter some crucial facts. | tried again. Prolog was quiet for a long time and then responded: DEBORDEMENT DE PILE Stack overflow! I had run into a loop. Now a loop was conceivable since the space of potential plans to be considered was infinite. However, I had taken advantage of Prolog's procedural semantics to organize the axioms so that shorter plans ought to be generated first. Could something else be wrong? After a lot of head scratching, | finally realized that I had mistyped the names of some variables. I corrected the mistakes, and tried again. Lo and behold, Prolog responded almost instantly with a correct plan to achieve the goal state. Magic! Declaratively correct axioms had assured a correct result, Deduction was being harnessed before my very eyes to produce effective computation. Declarative programming was truly programming on a higher plane! I had dimly seen the advantages in theory. Now Prolog had made them vividly real in practice. Never had I experienced such ease in getting a complex program coded and running, Of course, [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. wx Foreword After more than a decade of growth of interest in Prolog, it is a great pleasure to see the appearance of this book. Hitherto, knowledge of how to use Prolog for serious programming has largely been communicated by word of mouth, This textbook sets down and explains for the first time in an accessible form the deeper principles and techniques of Prolog programming. The book is excellent for not only conveying what Prolog is but also ex: plaining how it should be used. The key to understanding how to use Prolog is to properly understand the relationship between Prolog and logic programming, This book takes great care to elucidate the relation- ship. Above all, the book conveys the excitement of using Prolog—the thrill of declarative programming. As the authors put it, “Declarative program- ming clears the mind." Declarative programming enables one to concen- trate on the essentials of a problem without getting bogged down in too much operational detail. Programming should be an intellectually rewarding activity. Prolog helps to make it so. Prolog is indeed, as the authors contend, a tool for thinking. David H. D. Warren Manchester, England, September 1986 Preface Seven years have passed since the first edition of The Art 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 typographi: cal errors have been fixed. 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, which have been used successfully in my Prolog classes, have been added. oll Preface Let us take an overview of the specific changes to each part in turn. Part IV, Applications, is unchanged apart from minor corrections and tidying. Part I, Logic Programs, is essentially unchanged. New programs have been added to Chapter 3 on tree manipulation, including heapifying abinary tree. Extra exercises are also present. Part Il, The Prolog Langauge, is primarily affected by the imminence of a Prolog standard. We have removed all references to Wisdom Prolog in the text in preparation for Standard Prolog. It has proved impossible to guarantee that this book is consistent with the standard. Reaching a stan- dard has been a long, difficult process for the members of the committee. Certain predicates come into favor and then disappear, making it difficult for the authors of a text to know what to write, Furthermore, some of the proposed 1/0 predicates are not available in current Prologs, so it is im- possible to run all the code! Most of the difficulties in reaching a Prolog standard agreeable to all interested parties have been with builtin or sys- 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 much as possible. We tend not to give detailed explanations of the con- troversial nonlogical behaviors of some of the system predicates, and we certainly do not use odd features in our code. Part Ill, Advanced Programming Techniques, is the most altered in this second edition, which 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 taking 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 Sondergaard, 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 this 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 Gunther, Ann Halloran, Sundar Iyengar, Gary Kacmarcik, Mansoor Khan, Sundeep Kumar, Arun Lakhotia, Jean- Preface Louis Lassez, Charlie Linville, Per Ljung, David Maier, Fred Mailey, Martin Marshall, Andre Mesarovic, Dan Oldham, Scott Pierce, Lynn Pierce, David Pedder, S. S. Ramakrishnan, Chet Ramey, Marty Silverstein, Bill Sloan, Ron Taylor, Rodney Topor, R. J. Wengert, Ted Wright, and Nan Yang. For the former students of CMPS411, I hope the extra marks were sufficient re~ ward. Thanks to Sarah Fliegelmann and Venkatesh Srintvasan for help with entering changes to the second edition and TeXing numerous drafts. Thanks to Phil Gannon and Zoé Sterling for helpful discussions about the figures, and to Joe Gelles for drawing the new figures. For proofreading the second edition, thanks to Kathy Kovacic, David Schwartz, Ashish Jain, and Venkatesh Srinivasan. Finally, a warm thanks to my editor, Terry Ehling, who has always been very helpful and very responsive to queries. Needless to say, the support of my family and friends is the most important and most appreciated. Leon Sterling Cleveland, January 1993 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 own 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 programming requires an understanding of the theory of logic program- ming. The book consists of four parts: logic programming, the Prolog lan- guage, advanced techniques, and applications. The first part is a self- contained introduction to logic programming. It consists of five chapters. ‘The first chapter introduces the basic constructs of logic programs. Our account differs from other introductions to logic programming by ex- plaining the basics in terms of logical deduction. Other accounts explain the basics from the background of resolution from which logic program ‘ming originated. We have found the former to be a more effective means of teaching the material, which students find intuitive and easy to under- stand, The second and third chapters of Part I introduce the two basic styles of logic programming: database programming and recursive program- ming. The fourth chapter discusses the computation model of logic pro- gramming, introducing unification, while the fifth chapter presents some theoretical results without proofs. In developing this part to enable the clear explanation of advanced techniques, we have introduced new con cepts and reorganized others, in particular, in the discussion of types and termination. Other issues such as complexity and correctness are concepts whose consequences have not yet been fully developed in the logic programming research community The second part is an introduction to Prolog. It consists of Chapters 6 through 13. Chapter @ discusses the computation model of Prolog in contrast to logic programming, and gives a comparison between Prolog. and conventional programming languages such as Pascal. Chapter 7 dis- cusses the differences between composing Prolog programs and logic programs. Examples are given of basic programming techniques. The next five chapters introduce system-provided predicates that are essential to make Prolog a practical programming language. We clas- sify Prolog system predicates into four categories: those concerned Wwith efficient arithmetic, structure inspection, meta-logical predicates that discuss the state of the computation, and extra-logical predicates that achieve side effects outside the computation model of logic pro: gramming. One chapter is devoted to the most notorious of Prolog extra-logical predicates, the cut. Basic techniques using these system predicates are explained. The final chapter of the section gives assorted pragmatic programming tips. soot Preface to First Faition The main part of the book is Part Ill. We describe advanced Prolog programming techniques that have evolved in the Prolog programming community, illustrating each with small yet powerful example programs. The examples typify the applications for which the technique is useful The six chapters cover nondeterministic programming, incomplete data structures, parsing with DCGs, second-order programming, search tech- niques, and the use of meta-interpreters. The final part consists of four chapters that show how the material in the rest of the book can be combined to build application programs. A common request of Prolog newcomers is to see larger applications. They understand how to write elegant short programs but have difficulty in building a major program. The applications covered are game-playing programs, a prototype expert system for evaluating requests for credit, a symbolic equation solver, and a compiler. During the development of the book, it has been necessary to reorga- nize the foundations and basic examples existing in the folklore of the logic programming community. Our structure constitutes a novel frame work for the teaching of Prolog, Material from this book has been used successfully for several courses on logic programming and Prolog: in Israel, the United States, and Scot land. The material more than suffices for a onc-semester course to firs year graduate students or advanced undergraduates. There is consider- able scope for instructors to particularize a course to suit a special area of interest A recommended division of the book for a 13-week course to senior un- dergraduates or first-year graduates is as follows: 4 weeks on logic pro: gramming, encouraging students to develop a declarative style of writing programs, 4 weeks on basic Prolog programming, 3 weeks on advanced techniques, and 2 weeks on applications. The advanced techniques should include some discussion of nondeterminism, incomplete data structures, baste second-order predicates, and basic meta-interpreters. Other sections can be covered instead of applications. Application areas that can be stressed are search techniques in artificial intelligence, build: ing expert systems, writing compilers and parsers, symbol manipulation, and natural language processing. There is considerable flexibility in the order of presentation. The ma: terial from Part I should be covered first. The material in Parts Ill and IV can be interspersed with the material in Part Il to show the student how soovil Preface to First Fdition larger Prolog programs using more advanced techniques are composed in the same style as smaller examples. Our assessment of students has usually been 50 percent by homework assignments throughout the course, and 50 percent by project. Our expe: rience has been that students are capable of a significant programming task for their project. Examples of projects are prototype expert systems, 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. ll the examples run on Wisdom Prolog, which 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 indt 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 Sctence, Tel Aviv University, and Case Western Reserve Unt- 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, This 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 ook Preface to First Edition prepared the appendix. The publishers, MIT Press, were helpful and sup- portive Finally, we acknowledge the support of family and friends, without which nothing would get done. Leon Sterling 1986 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 tency of one’s claims; and for verifying the validity of one’s argu ments. Computers are relatively 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 cerned 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 bis 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 a sequence of instructions to perform such operations, and an additional set of control instructions, which can affect the next instruction to be executed, possibly depending on the content of some register, As the problems of building computers were gradually understood and solved, the problems of using them mounted. The bottleneck ceased to be the inability of the computer to perform the human’s instructions but rather the inability of the human to instruct, or program, the computer. A search for programming languages convenient for humans to use be- gan. Starting from the language understood directly by the computer, the machine language, better notations and formalisms were developed. The main outcome of these efforts was languages that were easier for humans to express themselves in but that still mapped rather directly to the underlying machine language. Although increasingly abstract, the languages in the mainstream of development, starting from assembly language through Fortran, Algol, Pascal, and Ada, all carried the mark of the underlying machine—the von Neumann architecture. To the uninitiated intelligent person who is not familiar with the en gineering constraints that led to its design, the von Neumann machine Seems an arbitrary, even bizarre, device. Thinking in terms of its con- strained set of operations is a nontrivial problem, which sometimes stretches the adaptiveness of the human mind to its limits. These characteristic aspects of programming von Neumann computers led to a separation of work: there were those who thought how to solve the problem, and designed the methods for its solution, and there were the coders, who performed the mundane and tedious task of translating the instructions of the designers to instructions a computer can use. Both logic and programming require the explicit expression of one’s knowledge and methods in an acceptable formalism. The task of making one’s knowledge explicit is tedious. However, formalizing one’s knowl- edge in logic is often an intellectually rewarding activity and usually reflects back on or adds insight to the problem under consideration. In contrast, formalizing one’s problem and method of solution using the von Neumann instruction set rarely has these beneficial effects. We believe that programming can be, and should be, an intellectu: ally rewarding activity; that a good programming language is a powerful conceptual tool—a tool for organizing, expressing, experimenting with, and even communicating one’s thoughts; that treating programming as Introduction coding,” the last, mundane, intellectually trivial, time-consuming, and tedious phase of solving a problem using a computer system, is pethaps at the very root of what has been known as the “software crisis Rather, we think that programming can be, and should be, part of the problem-solving process itself; that thoughts should be organized as programs, so that consequences of a complex set of assumptions can be Investigated by “running” the assumptions; that a conceptual solution to a problem should be developed hand-in-hand with a working program that demonstrates it and exposes its different aspects. Suggestions in this direction have been made under the title “rapid prototyping.” To achieve this goal in its fullest—to become true mates of the human thinking process—computers have still a long way to go. However, we find it both appropriate and gratifying from a historical perspective that logic, a companion to the human thinking process since the early days of human intellectual history, has been discovered as a suitable stepping: stone in this long journey Although logic has been used as a tool for designing computers and for reasoning about computers and computer programs since almost their beginning, the use of logic directly as a programming language, termed logic programming, is quite recent. Logic programming, as well as its sister approach, functional program: ming, departs radically from the mainstream of computer languages Rather then being derived, by a series of abstractions and reorganiza tions, from the von Neumann machine model and instruction set, it is derived from an abstract model, which has no direct relation to ‘or de- pendence on to one machine model or another. It is based on the belief that instead of the human learning to think in terms of the operations of a computer that which some scientists and engineers at some point in history happened to find easy and cost-effective to build, the com puter should perform instructions that are easy for humans to provide In its ultimate and purest form, logic programming suggests that even explicit instructions for operation not be given but rather that the knowl edge about the problem and assumptions sufficient to solve it be stated explicitly, as logical axioms. Such a set of axioms constitutes an alterna- tive to the conventional program. The program can be executed by pro- viding it with a problem, formalized as a logical statement to be proved, called a goal statement. The execution is an attempt to solve the prob: Introduction lem, that is, to prove the goal statement, given the assumptions in the logic program. A distinguishing aspect of the logic used in logic programming is that a goal statement typically is existentially quantified: it states that there exist some individuals with some property. An example of a goal state ment is, “there exists a list X such that sorting the list [3, 1,21 gives X." ‘The mechanism used to prove the goal statement is constructive. If suc cessful, It provides the identity of the unknown individuals mentioned in the goal statement, which constitutes the output of the computation. In the preceding example, assuming that the logic program contains appro- priate axioms defining the sort relation, the output of the computation would be X = [1,2,3]. These ideas can be summarized in the following two metaphorical equations: program = set of axioms. computation = constructive proof of a goal statement from the program. The ideas behind these equations can be traced back as far as intuition- istic mathematics and proof theory of the carly twentieth century. They are related to Hilbert’s program, to base the entire body of mathemati: cal knowledge on logical foundations and to provide mechanical proofs for its theories, starting from the axioms of logic and set theory alone. It is interesting to note that the failure of this program, from which en: sued the incompleteness and undecidability results of Gédel and Turing, ‘marks the beginning of the modern age of computers. The first use of this approach in practical computing is a sequel to Robinson's unification algorithm and resolution principle, published in 1965, Several hesitant attempts were made to use this principle as a basis of a computation mechanism, but they did not gain any momentum. The beginning of logic programming can be attributed to Kowalski and Colmerauer. Kowalski formulated the procedural interpretation of Horn clause logic. He showed that an axiom Aif B, and By and... and B, can be read and executed as a procedure of a recursive programming language, where A is the procedure head and the B are its body. In Introduction addition to the declarative reading of the clause, A is true if the B; are true, it can be read as follows: To solve (execute) A, solve (execute) B; and B, and... and By. In this reading, the proof procedure of Horn clause logic is the interpreter of the language, and the unification algorithm, which is at the heart of the resolution proof procedure, performs the basic data manipulation operations of variable assignment, parameter passing, data selection, and data construction. At the same time, in the early 1970s, Colmerauer and his group at the University of Marseilles-Aix developed a specialized theorem prover, written in Fortran, which they used to implement natural language pro- cessing systems. The theorem prover, called Prolog (for Programmation en Logique), embodied Kowalski’s procedural interpretation. Later, van Emden and Kowalski developed a formal semantics for the language of logic programs, showing that its operational, model-theoretic, and. fix- point semantics are the same. In spite of all the theoretical work and the exciting ideas, the logic pro: ‘gramming approach seemed unrealistic. At the time of its inception, re~ searchers in the United States began to recognize the failure of the “nest- generation Al languages,” such as Micro-Planner and Conniver, which de Ycloped as a substitute for Lisp. The main claim against these languages was that they were hopelessly inefficient, and very difficult to control. Given their bitter experience with logic-based high-level languages, it is no great surprise that U.S. artificial intelligence scientists, when hearing about Prolog, thought that the Europeans were over-excited over what they, the Americans, had already suggested, tried, and discovered not to work. In that atmosphere the Prolog-10 compiler was almost an imaginary being. Developed in the mid to late 1970s by David H. D. Warren and his colleagues, this efficient implementation of Protog dispelled all the myths about the impracticality of logic programming. That compiler, still one of the finest implementations of Prolog around, delivered on pure list-processing programs a performance comparable to the best Lisp sys: tems available at the time. Furthermore, the compiler itself was written almost entirely in Prolog, suggesting that classic programming tasks, not just sophisticated Al applications, could benefit from the power of logic programming, Introduction The impact of this implementation cannot be overemphasized. Without it, the accumulated experience that has led to this book would not have existed. In spite of the promise of the ideas, and the practicality of their im- plementation, most of the Western computer science and Al research community was ignorant, openly hostile, or, at best, indifferent to logic programming. By 1980 the number of researchers actively engaged in logic programming were only a few dozen in the United States and about one hundred around the world. 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 eneration 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 pay money to solve problems they 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 s 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 Introduction Programming as well as in the general computer science journals and conferences, Clearly, one of the dominant areas of interest is the relation between logic programming, Prolog, and parallelism. The promise of parallel com- puters, combined with the parallelism that seems to be available in the logic programming model, have led to numerous attempts, still ongoing, to execute Prolog in parallel and to devise novel concurrent program- ming languages based on the logic programming computation model This, however, is a subject for another book. Leonardo Da V ld Man thinking. Pen and ink (slightly enlarged). About 1510. Windsor Castle, Royal Library. eel 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, which is its meaning, The art of logic programming is constructing concise and elegant programs that have the desired meaning Facts Basic Constructs ‘The basic constructs of logic programming, terms and statements, are inherited from logic. There are three basic statements: facts, rules, queries. There is a single data structure: the logical term. 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 £a~ 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, plue(0,0,0). plue(0,1,1). —plus(0,2,2). plus (0,3,3). plus(1,0,1). plus(1,1,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 1.2 Queries Chapter 1 father (serach, abraham) nale(terach) father (verach nackor) nale(abrahan) father(terach,haran) nale(nachor) father abrahan, isaac) nale(naran) father haran,t0t) . male(isazc) father (naran,s1icah) nale(iot) father (haran,yiscah) fenale(sarah) nother sarah, isaac) fonale (micah) fenale(yiscah) Program 1.1 A biblical family database of both predicates and atoms in facts begin with a lowercase letter rather 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 chapter. An example database of family relationships from the Bible ts given as Program 1.1. The predicates father, mother, male, and female express the obvious relationships. ‘The second form of statement in a logic program is a query. Queries are a means of retrieving information from a logic program. A query asks whether a certain relation holds between objects. For example, the query father (abraham, isaac)? asks whether the father relationship holds between abraham and isaac. Given the facts of Program 1.1, the answer to this query is ves Syntactically, queries and facts look the same, but they can be distin: guished by the context, When there is a possibility of confusion, a termi- nating period will indicate a fact, while a terminating question mark will indicate a query. We call the entity without the period or question mark a goal. A fact P. states that the goal P is true. A query P? asks whether the goal P is true. A simple query consists of a single goal. Answering a query with respect to a program is determining whether the query is @ logical consequence of the program. We define logical 13 Basic Constructs consequence incrementally through this chapter. Logical consequences are obtained by applying deduction rules. The simplest rule of deduction is identity: from P deduce P. A query is a logical consequence of an identical fact. Operationally, answering simple queries using a program containing facts like Program 1.1 is straightforward, Search for a fact in the program that implies the query. Ifa fact identical to the query is found, the answer ‘The answer no is given if a fact identical to the query is not found, because the fact ts 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 fenale(abrahar)? and plus(1,1,2)? will be answered no with respect to Program 1.1. 1.3. The Logical Variable, Substitutions, and Instances A logical variable stands for an unspecified individual and is used ac: 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, father(abraham,lot)?, father(abraham,milcah)?,..., father (abraham, isaac)?,... until an answer yes is given. A variable allows a better way of expressing the query as father (abrahan,x)7, to which the answer is X=isaac, Used in this way, variables are a means of sum: ‘marizing many queries. A query containing a variable asks whether there is a value for the variable that makes the query a logical consequence of the program, as explained later. Variables in logic programs behave differently from variables in con ventional programming languages. They stand for an unspecified but sin: ‘gle entity rather than for a store location in memory. Having introduced variables, we can define a term, the single data structure in logic programs. The definition is inductive. Constants and variables are terms. Also compound terms, or structures, are terms. A compound term comprises a functor (called the principal functor of the term) and a sequence of one or more arguments, which are terms. A functor is characterized by its name, which is an atom, and its arity, or number of arguments. Syntactically, compound terms have 14 Chapter 1 the form f(ti,b,--.1fg) Where the functor has name f and is of arity nn, and the fare the arguments. Examples of compound terms include (0), hot (milk), name(john,doe), List (a,1ist(b,nil)), f00(X), and tree (tree(nil,3,nil) ,5,R) Queries, goals, and more generally terms where variables do not occur are called ground. Where variables do occur, they are called nonground. For example, f00(a,) is ground, whereas bar (X) is nonground, Definition A substitution is a finite set (possibly empty) of pairs of the form X, = &, where X is a variable and is aterm, and X, # X, for every é # j, and X, does not occur in t, for any f and J. . An example of a substitution consisting of a single pair is (X-isaac} Substitutions can be applied to terms. The result of applying a substi: lution @ to a term A, denoted by AQ, is the term obtained by replacing, every occurrence of X by t in A, for every pair X =f in 0. The result of applying (X-isaac} to the term father (abraham,X) is the term father (abraham, isaac) Definition Alls an instance of B if there is a substitution such that Be. . The goal father (abraham, isaac) is an instance of father(abraham, X) by this definition. Similarly, mother (sarah, isaac) is an instance of mother(X,¥) under the substitution (X=sarah, Y=isaac}, 1.4 Existential Queries Logically speaking, variables in queries are existentially quantified, which means, intuitively, that the query father(abraham,x)? reads: “Does there exist an X such that abraham is the father of X?” More generally, a query p(T), To..Tn)?, Which contains the variables Xj,Xo...X¢ reads “Are there XiNou-aNXe such that p(T, Ts.-+Tn)?” For convenience, exis tential quantification is usually omitted. The next deduction rule we introduce is generalization. An existential query P is a logical consequence of an instance of it, P®, for any substi tution @. The fact father (abraham, isaac) implies that there exists an X such that father(abrahax,X) is true, namely, X-isaac. 15 Baste Constructs Operationally, to answer a nonground query using a program of facts, search for a fact that is an instance of the query. If found, the answer, or solution, is that instance. A solution is represented in this chapter by the substitution that, if applied to the query, results in the solution. The answer is no if there is no suitable fact in the program. In general, an existential query may have several solutions. Program L.1 shows that Haran is the father of three children. Thus the query father(haran,X)? has the solutions {X-lot], {X*milcah}, [X-yiscah}. Another query with multiple solutions is plus(X,¥,4)? for finding num: bers that add up to 4. Solutions are, for example, {X=0, Y=4} and { ¥=3}. Note that the different variables x and Y correspond to (possibly) different objects. An interesting variant of the last query is plus(X,X,4)?, which insists that the two numbers that add up to 4 be the same. It has a unique answer {X=2} 1.5 Universal Facts Varlables are also useful in facts. Suppose that all the biblical characters like pomegranates. Instead of including in the program an appropriate fact for every individual, Likes (abraham, ponegranates) Likes (sarah, pomegranates) a fact 1ikes (X,pomegranates) can say it all. Used in this way, variables are a means of summarizing many facts. The fact times (0,X,0) summa- rizes all the facts stating that 0 times some number is 0. Variables in facts are implicitly universally quantified, which means, intuitively, that the fact Likes (X,pomegranates) states that for all X, X likes pomegranates. In general, a fact (T;,...,Tm) Teads that for all Xjv--oeXi, Where the X; are variables occurring in the fact, p(T,....Tn) is true. Logically, from a universally quantified fact one can deduce any instance of it. For example, from 1ikes(%,ponegranates), deduce Likes (abraham, pomegranates). 16 Chapter | This is the third deduction rule, called instantiation, From a universally quantified statement P, deduce an instance of it, P8, for any substitution 0 As for queries, two unspecified objects, denoted by variables, can be constrained to be the same by using the same variable name. The fact plus(O,X,X) expresses that 0 is a left identity for addition. It reads that for all values of X, 0 plus X is X. A similar use occurs when translating the English statement “Everybody likes himself” to 1ikes(X,X) Answering a ground query with a universally quantified fact is straight. forward. Search for a fact for which the query is an instance. For example, the answer to plus(0,2,2)? is ves, based on the fact plus(0,X,X). An: swering a nonground query using a nonground fact involves a new def nition: a common instance of two terms, Definition Cis. common instance of and Bif itis an instance of 4 and an instance of B, in other words, if there are substitutions 0, and 0» such that C=A0, is syntactically identical (0 BO, . For example, the goals plus(0,3,¥) and plus(0,X,X) have a com: mon instance plus(0,3,3). When the substitution {Y=3} is applied to plus(0,3,¥) and the substitution {X=3) is applied to plus(0,X,X), both yield plus(0,3,3) In general, to answer a query using a fact, search for a common in: stance of the query and fact. The answer is the common instance, if one exists, Otherwise the answer is no, Answering an existential query with a universal fact using a common instance involves two logical deductions. The instance is deduced from the fact by the rule of instantiation, and the query is deduced from the instance by the rule of generalization 1.6 Conjunctive Queries and Shared Variables An important extension to the queries discussed so far is conjunctive queries. Conjunctive queries are a conjunction of goals posed as a query, for example, father (terach ,X) father (X,Y)? or in general, Qs,..,0n2. Simple queries are a special case of conjunctive queries when there is a Basie Constructs single goal. Logically, it asks whether a conjunction is deducible from the program. We use "." throughout to denote logical and. Do not confuse the comma that separates the arguments in a goal with commas used to separate goals, denoting conjunction. In the simplest conjunctive queries all the goals are ground, for exam: ple, father (abraham, isaac) male(lot)?. The answer to this query us- ing Program 1.1 is clearly yes because both goals in the query are facts in the program. In general, the query Qy,...,Qn?, where each Q, is a ground goal, is answered yes with respect to a program P if each Q, is Implied by P. Hence ground conjunctive queries are not very interesting. Conjunctive queries are interesting when there are one or more shared variables, variables that occur in two different goals of the query. An ex: ample is the query father (haran,X) ,male(X)?. The scope of a variable in a conjunctive query, as in a simple query, is the whole conjunction. Thus the query p(X),g(X)? reads: “Is there an X such that both p(X) and ay" Shared variables are used as @ means of constraining a simple query by restricting the range of a variable. We have already seen an example with the query plus(X,X,4)?, where the solution of numbers adding up to 4 was restricted to the numbers being the same. Consider the query father(haran,X),male(X)?. Here solutions to the query fa- ther(haran,X)? are restricted to children that are male, Program 1.1 shows there is only one solution, {X=1ot}. Alternatively, this query can be viewed as restricting solutions to the query male(X)? to individuals who have Haran for a father. A slightly different use of a shared variable can be seen in the query father (terach,X) ,father(X,Y)?. On the one hand, it restricts the sons of terach to those who are themselves fathers. On the other hand, it con: siders individuals ¥, whose fathers are sons of terach. There are several solutions, for example, {X-abrahaz, Y=isaac| and {X-haran, Y=lot} A conjunctive query is a logical consequence of a program P if all the goals in the conjunction are consequences of P, where 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 of P. This instance then deduces the conjuncts in the query via general ization The restriction to ground instances is unnecessary and will be lifted in Chapter 4 when we discuss the computation model of logic programs. 18 1.7 Rules Chapter 1 We employ this restriction in the meantime to simplify the discussion in the coming sections. Operationally, to solve the conjunctive query Aj,Ave An? using a pro gram P, find a substitution @ such that 40 and... and An@ are ground instances of facts in P. The same substitution applied to all the goals en- sures that instances of variables are common throughout the query. For example, consider the query father (haran,X) ,male(X)? with respect to Program 1.1. Applying the substitution {X-Lot} to the query gives the ground instance father (haran, lot) ,male(1ot)?, which is a conse quence of the program. Interesting conjunctive queries are defining relationships in their own right. The query fatier(haran x) ,male(X)? is asking for a son of Ha- ran. The query father (terach,X) , father (X,¥)? is asking about grand: children of Terach. This brings us to the third and most important state- ‘ment in logic programming, a rule, which enables us to define new rela tionships in terms of existing relationships. Rules are statements of the form: Ax Bid Br where 120. The goal A is the head of the rule, and the conjunction of goals Bi.....By is the body of the rule. Rules, facts, and queries are also called Horn clauses, or clauses for short. Note that a fact is just a special case of a rule when n= 0. Facts are also called unit clauses. We also have a special name for clauses with one goal in the body, namely, when n= 1. Such a clause is called an iterative clause. As for facts, variables appearing in rules are universally quantified, and thelr scope is the whole rule. A rule expressing the son relationship is son(X,Y) — father(Y,X), male(x) Similarly one can define a rule for the daughter relationship: daughter(X,¥) ~ father(Y,X), female(x) 19 Basie Constructs A rule for the grandfather relationship is, grandfather (X,Y) ~ father(%,Z), father(2,¥) Rules can be viewed in two ways. First, they are a means of ex: pressing new or complex queries in terms of simple queries. A query son(X,haran)? to the program that contains the preceding rule for son is translated to the query father(haran,X) ,male(X)? according to the rule, atid solved as before. A new query about the son relationship has been built from simple queries involving father and male relationships. Interpreting rules in this way is their procedural reading. The procedural reading for the grandfather rule is: “To answer a query Is X the grand- father of ¥?, answer the conjunctive query Is X the father of Z and Z the father of V2." The second view of rules comes from interpreting the rule as a logical axiom. The backward arrow —is used to denote logical implication. The son rule reads: “X is a son of Y if ¥ is the father of X and X is male.” In this view, rules are a means of defining new or complex relationships using other, simpler relationships. The predicate son has been defined terms of the predicates father and male. The associated reading of the rule is known as the declarative reading. The declarative reading of the grandfather rule is: “For all X, Y, and Z, X is the grandfather of ¥ if X is the father of Z and Z is the father of ¥. Although formally all variables in a clause are universally quantified, ‘we will sometimes refer to variables that occur in the body of the clause, but not in its head, as if they are existentially quantified inside the body. For example, the grandfather rule can be read: "For all X and Y, X is the grandfather of ¥ If there exists a Z such that X Is the father of Z and Z is the father of Y.” The formal justification of this verbal transformation will not be given, and we treat it just as a convenience. Whenever it is a source of confusion, the reader can resort back to the formal reading of a clause, in which all variables are universally quantified from the outside. To incorporate rules into our framework of logical deduction, we need the law of modus ponens. Modus ponens states that from B and A ~ B we can deduce A. Definition The law of universal modus ponens says that from the rule R=(A~ By Boye Bn) Chapter 1 and the facts. By B, By, A’ can be deduced if Vv ~ BB is an instance of R. . Universal modus ponens includes identity and instantiation as special ca We are now in a position to give a complete definition of the concept of a logic program and of its associated concept of logical consequence. Definition A logic program is a finite set of rules. . Definition An existentially quantified goal G is a logical consequence of a program P if there is a clause In P with a ground instance A ~ B;,..-, By," = 0 such that B,,....Bn are logical consequences of P, and Ais an instance of G. = Note that the goal G is a logical consequence of a program P if and only if G can be deduced from P by a finite number of applications of the rule of universal modus ponens, Consider the query son(S,haran)? with respect to Program 1.1 aug mented by the rule for son. The substitution {X-lot,Y=haran} applicd to the rule gives the instance son(lot,haran) ~father(haran, lot), male(1ot). Both the goals in the body of this rule are facts in Pro- gram 1.1. Thus universal modus ponens implies the query with answer {Selot} Operationally, answering queries reflects the definition of logical con- sequence. Guess a ground instance of a goal, and a ground instance of a rule, and recursively answer the conjunctive query corresponding to the body of that rule. To solve a goal A with program P, choose a rule Ay ~B),Byy.Bn in P, and guess substitution @ such that A= A;0, and Basic Constructs B(0 is ground for 1 < i 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 ~ By, By % 2 0,18 a ground instance of a clause in P, and the Bj are deducible from P. Deduction of a goal from an identical fact is a special case. The meaning of a program P is inductively defined using logical de- duction. The set of ground instances of facts in P are in the meaning. A ground goal G is in the meaning if there is a ground instance G ~ B},....Bn of a rule in P such that B;,...,By are in the meaning. The meaning consists of the ground instances that are deducible from the program. 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, which is the desired situation, if M = M(P). A ground goal is true with respect to an intended meaning if it is a member of it, and false otherwise Logical deduction is defined syntactically here, and hence also the ‘meaning of logic programs. In Chapter 5, alternative ways of describing the meaning of logic programs are presented, and their equivalence with the current definition is discussed. SS 2 Database Programming There are two basic styles of using logic programs: defining a logical database, and manipulating data structures. This chapter discusses data base programming. A logic database contains a set of facts and rules. We show how a set of facts can define relations, as in relational data- bases. We show how rules can define complex relational queries, as in relational algebra. A logic program composed of a set of facts and rules of a rather restricted format can express the functionalities associated with relational data 2.1 Simple Databases We begin by revising Program 1.1, the biblical database, and its aug- mentation with rules expressing family relationships. The database itself had four basic predicates, father/2, mother/2, male/1, and fe~ nale/i. We adopt a convention from database theory and give for each relation a relation scheme that specifies the role that each po- sition in the relation (or argument in the goal) is intended to repre- sent, Relation schemes for the four predicates here are, respectively, father(Father,Child), mother(Mother,Child), male(Person), and fewale(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 30 Chapter 2 underscores for predicate and function names, for example, schedule_ conflict. New relations are built from these basic relationships by defining suit- able rules. Appropriate relation schemes for the relationships introduced in the previous chapter are son(Son,Parent), daughter (Daughter, Parent), parent(Parent,Child), and grandparent (Grandparent, Grandchiid). 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 parent, male and female facts, the rules defining son and grandparent are still correct. New rules must be writ- ten for the relationships no longer defined by facts, namely, father and nother. Suitable rules are father(Dad,Child) ~ parent (Dad,Child), male (Dad) mother (Mum,Child) — parent (Mum,Child), female (Mun) 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- spring, or to use a Biblical term, procreated, This is not given explicitly in the database, but a simple rule can be written recovering the information. The relation scheme is procreated (Man, Woman) procreated(Man,Woman) father (Man,Child), mother (Woman, Child). This reads: "Man and Woman procreated if there is a Child such that Man is the father of Child and Wonan is the mother of Child.” Another example of information that can be recovered from the simple information present is sibling relationships — brothers and sisters. We give a rule for brother (Brother ,Sibling) brother (Brother ,Sib) ~ parent (Parent ,Brother), parent (Parent ,Sib), male(Brother) This reads: “Brother is the brother of Sib if Parent is a parent of both Brother and Sib, and Brother is male.” There is a problem with this definition of brother. The query brother (X,X)? is satisfied for any male child x, which is not our understanding of the brother relationship. In order to preciude such cases from the meaning of the program, 31 Database Programming abrohan # icaac. abraham # haran. abraham # lot abrahan # wilcah. abraham 4 yiscah. isaac + haran isaac # lot. isaac 4 milcah isaac # yiscah. naran # lot. aran + nilcah naran # yiscah lot # milcah lot # yiscan. milcah + yiscah. Figure 2.1 Defining inequality uncle(Uncle, Person) + brother(Uncle,Parent), parent (Parent Person) ‘aabLing(Sibt,81b2) — parent (Parent Sibl), parent (Parent,Sib2), Sibt # Sib? cousin(Cousint ,Cousin2) — parent (Parent1,Cousint), parent (Parent2,Cousin2), sibling(Parent! ,Parent2) Program 2.1 Defining family relationships we introduce a predicate # (Terni ,Tern2). It is convenient to write this, predicate as an infix operator. Thus Tere! # Term? is true if Terai and Tern? are different. For the present it is restricted to constant terms. It can be defined, in principle, by a table X 4 ¥ for every two different individuals X and Y in the domain of interest. Figure 2.1 gives part of the appropriate table for Program 1.1. ‘The new brother rule is, brother (Brother,Sib) ~ parent (Parent Brother), parent (Parent,Sib), male (Brother), Brother # Sib- ‘The more relationships that are present, the easier it is to define com: plicated relationships. Program 2.1 defines the relationships uncle(Uncle,Person), sibling(Sibi,8ib2), and cousin(Cousini, Cousin2). The definition of uncle in Program 2.1 does not define the husband of a sister of a parent to be an uncle. This may or may not be the intended meaning. In general, different cultures define these family relationships differently. In any case, the logic makes clear exactly what the programmer means by these family relationships. Chapter 2 Another relationship implicit in the family database is whether a woman is a mother. This is determined by using the mother/2 relation: ship. The new relation scheme is nother (Wonan), defined by the rule nother (Woman) ~ mother (Woman, Child) This reads: “Woman is a mother if she is the mother of some Chi14.” Note that we have used the same predicate name, nother, to describe two different mother relationships. The mother predicate takes a different number of arguments, Le., 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 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 acollection of facts, while rules describe the functional components, Program 2.2 is a database giving 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(End1,End2) and for transistors transis~ tor Gate Source Drain) Power re Figure 2.2 A logical circuit 33 Database Programming resistor (power nt) resistor (poversn2) transistor (n2,ground,nt) ‘transistor (n3,n4,n2). ‘uransistor (n5, ground,n4) inverter UInput,Output) — Output is the inversion of Input. inverter(Input Output) — traneistor (Input, ground,utput) , resistor(posor Output) nand_gate(Input1,Input2,Output) ~ ‘Output Is the logical nand of Inputt and Input2. ‘mand gate(inputt ,Topur2,Output) — transistor (Inputt ,X,dutpu), ‘transistor (nput2,ground,X) resistor(poxer, Output) and_gate Input Input2,Qutput) ‘Output is the logical and of Input! and Inpute. and_gate (Input, Input, Output) — rnand_gate(Input ,Tnput2,X), snverter(X, Output) Program 2.2 A circuit for a logical and-gate ‘The program demonstrates the style of commenting of logic programs ‘we will follow throughout the book. Fach 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, which 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(Inputi,Input2,0utput), nand_gate(Inputt, Input2, Output), and inverter (Input Output) ct Chapter 2 To appreciate Program 2.2, let us read the inverter rule. This states that 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 gate of the transistor is the input to the inverter, while the free end of the resistor must be connected to the drain of the transistor, which forms the output of the inverter. Sharing of variables is used to insist on the common connection. Consider the query and_gate(Int,Tn2,0ut)? to Program 2.2. It has the solution {Ini=n3,In2=n5,Out-n1}. This solution confirms that the circuit described by the facts is an and-gate, and indicates the inputs and output. 2.1.1 Exercises for Section 2.1 (Modify the rule for brother on page 21 to give a rule for sister, the rule for uncle in Program 2.1 to give a rule for niece, and the rule for sibling in Program 2.1 so that it only recognizes full siblings, Le., those that have the same mother and father. (i) Using a predicate married_couple(Wife, Husband), define the rela- tionships nother_in_lax, brother_in_law, and son_in_law. iil) Describe the layout of objects in Figure 2.3 with facts using the predicates left_of (Object1,Object2) and above (Object ,0b- ject2). Define predicates right of (Object1,0bject2) and below (Object1 ,Object2) in terms of Left_of and above, respectively. Figure 2.3. Stilllife objects 35 2.2. Structured Data and Data Abstraction Database Programming A limitation of Program 2.2 for describing the and-gate is the treatment of the circuit as a black box. There is no indication of the structure of the circuit in the answer to the and_gate query, even though the structure has been implicitly used in finding the answer. The rules tell us that the circuit represents an and-gate, but the structure of the and-gate is present only implicitly. We remedy this by adding an extra argument to each of the goals in the database. For uniformity, the extra argument becomes the first argument. The base facts simply acquire an identifier. Proceeding from left to right in the diagram of Figure 2.2, we label the resistors rt and r2, and the transistors t1, 2, and ¢3. Names of the functional components should reflect their structure. An inverter is composed of a transistor and a resistor. To represent this, we need structured data. The technique is to use a compound term, inv(T,R), where T and R are the respective names of the inverter’s com- ponent transistor and resistor. Analogously, the name of a nand-gate will be nand(T1,T2,R), where T1, T2, and R name the two transistors and re- sistor that comprise a nand-gate. Finally, an and-gate can be named in terms of an inverter and a nand-gate. The modified code containing the names appears in Program 2.3. The query and_gate(G,In1, In2,Out)? has solution (G-and(nand(t2, 3,12), inv(t1,r1)),Ini=n3,In2=n5,Out=n1}. Int, In2, and Out have their previous values. The complicated structure for G reflects accurately 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 Harel in the Feinberg building, room A: course(complexity,nonday,9, 11,david,harel, feinberg,a) and 36 Chapter 2 resistor(R,Nodel,Node2) — R isa resistor between Nodel and Node2 rasistor(r1,pover,a1) rosietor(r2,pouer x2) transistor T,Gate,Source,Drain) ~ T isa transistor whose gate is Gate, source is Source, and drain 's Drain. ‘traneistor(tt ,x2,ground,n1) transistor(c2)18,24,n2) ‘transistor(e3,25,ground,24) inverter C.nput, Output) ~ is an inverter that inverts Input to Output. invertor inv(T,8) Taput Output) — transistor(T, Iaput ,groutd, Output), recistor(, power Output) nand_gate(Nandyinput1,Input2,Output) ~ Nand is a gate forming the logical nand, Output, of Input! and Input2 and. gate(nand(T1 ,72,) ,Inputt ,Tnput2,Ourput) — transistor(Tt Anputl,X,Output) , transistor(T2,Taput2,ground,X), resistor (R,power Output) and_gatecAnd,Inputi,.nput2 Output), — ‘And is a gate forming the logical and, Output, of Inputt and Input2. ‘and_gate(and(Wl,1) , Input, Input2,Output) — ‘and gate(N, Input 1, Input2,X), inverter(I,X,Ourput) Program 2.3 The circuit database with names 37 Database Programming course (complexity, time(nonday,9,11) , lecturer (david,harel) , Location feinberg,a)) ‘The first fact represents course as a relation between eight items — a course name, a day, a starting hour, a finishing hour, a lecturer's first name, a lecturer’s surname, a building, and a room. The second fact makes course a relation between four items — a name, a time, a lecturer, and a location with further qualification, The time is composed of a day, a starting time, and a finishing time; lecturers have a first name and a surname; and locations are specified by a building and a room. The second fact reflects more elegantly the relations that hold. The four-argument version of course enables more concise rules to be written by abstracting the details that are irrelevant to the query. Program 2.4 contains examples. The occupied rule assumes a predicate less than or equal, represented as a binary infix operator < Rules not using the particular values of a structured argument need not “know” how the argument is structured. For example, the rules for duration and teaches represent time explicitly as time(Day,Start, Finish) because the Day or Start or Finish times of the course are de- sired. In contrast, the rule for lecturer does not. This leads to greater modularity, because the representation of time can be changed without affecting the rules that do not inspect it. We offer no definitive advice on when to use structured data. Not using, structured data allows a uniform representation where all the data are simple. The advantages of structured data are compactness of represen- tation, which more accurately reflects our perspective of a situation, and course) ~ course(Course, Tine, Lecturer, Locat Lecturer (Lecture: on) duration (Course, Length) ~ course (Course, time (Day Start Finish) ,Lecturer, Location) , plus(Start Lengeh Finish) teaches (Lecturer,Day) — course (Course, time(Day, Start,Finish) Lecturer,Location) oceupied(Roon,Day, Time) ~ course(Course, tine (Day Start, Finish) Lecturer Root Start < Tine, Tige < Finish Program 2.4 Course rules 38 Chapter 2 modularity. We can relate the discussion to conventional programming languages. Facts are the counterpart of tables, while structured data cor respond to records with aggregate fields. We believe that the appearance of a program is important, particularly when attempting difficult problems. 4 good structuring of data can make a difference when programming complex problems. Some of the rules in Program 2.4 are recovering relations between two individuals, binary relations, from the single, more complicated one. All the course information could have been written in terms of binary relations as follows: day (complexity monday) start_time (complexity,9). finish time (complexity ,11) lecturer (complexity,harel) . building(conplexity,feinberg) room(complexity,a) Rules would then be expressed differently, reverting to the previous style of making implicit connections explicit, For example, ‘teaches(Lecturer,Day) ~ Lecturer (Course,Lecturer), day(Course Day) 2.2.1 Exercises for Section 2.2 (Add rules defining the relations location (Course Building), busy(Lecturer,Time), and cannot_meet (Lecturer Lecturer?) Test with your own course facts. (i) Possibly using relations from Exercise (i), define the relation sched~ ule_conflict (Time,Place,Course1 ,Course2) (i) 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 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. 39 Database Programming 2.3. Recursive Rules The rules described so far define new relationships in terms of existing ones. An interesting extension is recursive definitions of relationships that define relationships in terms of themselves. One way of viewing recursive rules is as generalization of a set of nonrecursive rules Consider a series of rules defining ancestors — grandparents, great grandparents, etc: grandparent (Ancestor Descendant) ~ parent (Ancestor Person), parent (Person ,Descendant) greatgrandparent (Ancestor Descendant) — parent (Ancestor ,Person), grandparent (Person, Descendant) greatgreatgrandparent (Ancestor ,Descendant) ~ parent (Ancestor,Person), greatgrandparent (Person, Descendant) A clear pattern can be seen, which can be expressed in a rule defining the relationship ancestor (Ancestor Descendant) ancestor (Ancestor ,Descendant) parent (Ancestor,Person), ancestor Person, Descendant) This rule is a generalization of the previous rules A logic program for ancestor also requires a nonrecursive rule, the choice of which affects the meaning of the program. If the fact ances- tor (X,X) is used, defining the ancestor relationship to be reflexive, peo- 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~ tor relationship, where parents are considered ancestors ancestor (Ancestor,Descendant) ~ “Ancestor Is an ancestor of Descendant ancestor (Ancestor Descendant) ~ parent Ancestor Descendant) ancestor(Ancestor Descendant) — parent(Ancestor,Person), ancestor (Person,Descendant) Program 2.5 The ancestor relationship 40 Chapter 2 ‘The ancestor relationship 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 nod There are many alternative way’ 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 ‘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 Descendant) . 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 ancestor (Ancestor Descendant) ~ parent (Ancestor ,Descendant) ancestor (Ancestor ,Descendant) ~ ancestor (Ancestor ,Person), ancestor (Person, Descendant) Consider the problem of testing connectivity in a directed graph. A directed graph can be represented as a logic program by a collection of facts. A fact edge Nodet ,Node2) is present in the program if there is an edge from Nodet to Node? in the graph. Figure 24 shows a graph; Program 2.6 is its description as a logic program. Two nodes are connected if there is a series of edges that can be tra- versed to get from the first node to the second. That is, the relation con= nected (Nodel ,tiode2), which is true if Node1 and Node? are connected, is the transitive closure of the edge relation, For example, a and e are connected in the graph in Figure 2.4, but b and f are not. Program 2.7 defines the relation. The meaning of the program is the set of goals con 4 Database Programming fe g Figure 2.4 A simple graph edge(a,b). edge(a,e)._edgeb, d) edgo(c.d). edgo(4,e).—_edge(t ) Program 2.6 A directed graph connected(Node1,Node2) — Node! is connected to Node? in the graph defined by the edge/2 relation connected (Node, Node) connected Wodei ,Node2) ~ edge Nodet Link), connected(Link,Node2) Program 2.7. The transitive closure of the edge relation nected(X,¥), where X and ¥ are connected. Note that connected is a transitive reflexive relation because of the choice of base fact. 2.3.1 Exercises for Section 2.3 w A stack of blocks can be described by a collection of facts on (Block1,Block2), which is true if Block is on Block2. Define a predicate above (Block! ,Block2) that is true if Block! is above Block2 in the stack. (Hint: above Is the transitive closure of on.) 42 Chapter 2 (i) Add recursive rules for Left_of and above from Exercise 2.1(ii) on . 34, Define higher (Object, Object2), which is true if Object is ona line higher than 0bject2 in Figure 2.3. For example, the bicycle is higher than the fish in the figure. (ii) 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/? facts, how many nodes are there in a proof tree establishing that two nodes are connected by a path containing intermediate nodes? 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. Many of the concepts introduced have meaningful analogues in terms of databases. The converse is also true. The basic operations of the rela- tional algebra are easily expressed within logic programming, Procedures composed solely of facts correspond to relations, the arity 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 program, 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; Foumion s (Ki)... Kn) — Oy 2s Xn) reunions (Kip... yn) = 8p see Xn) Set difference involves negation. We assume a predicate not. Intu- itively, a goal not G is true with respect to a program P if G is not a logical consequence of P. Negation in logic programs is discussed in 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_dizt_s of arity n, where r and s are of arity mis 43 Database Programming ridiff_9 (81-6. Kn) ~ Oye. sKn)s Mot SOL, Kn) Cartesian product can be defined in a single rule. If r is a relation of arity m, and ¢ is a relation of arity 7, then r_x_¢ is a relation of arity m+n defined by TRB Km mele oo Kmen) OL, 2s sXe SCKmety <2 + aXe) 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 £13Q14%s) — 0G Ko Xs) Selection is similarly straightforward for any particular case. Consider a relation consisting of tuples whose third components are greater than. their second, and a relation where the first component is Smith or Jones. In both cases a relation r of arity 3 is used to illustrate. The first example creates a relation r1: TACK KoyKh) FO Xr) Ky > % The second example creates a relation x2, which requires a disjunctive relationship, smi th_or_jones: ¥2(K,,%0%s) ~ 7% 4K24Ks), smith_or_jones(X1) smith_or_jones(smith) smi th_or_jones (jones) Some of the derived operations of the relational algebra are more closely related to the constructs of logic programming. We mention two, intersection and the natural join. If r and s are relations of arity n, the intersection, r_meet_s is also of arity nand is defined in a single rule. rumeet shy =. -s%n) — £Oy ohms SO, Mn) A natural join is precisely a conjunctive query with shared variables. 4 2.5 Background Chapter 2 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 this book, the database community has accepted logic programs as extensions of relational databases. The term used for a data~ base extended with logical rules is logic database or deductive database. ‘There is now a wealth of material about logic databases. The rewritten version of Ullman’s text (1989) discusses logic databases and gives point- ers to the important literature. Perhaps the major difference between logic databases as taught from a database perspective and the view presented here is the way of evalu: ating queries. Here we implicitly assume that the interpreter from Figure 4.2 will be used, a top-down approach. The database community prefers a bottom-up evaluation mechanism. Various bottom-up strategies for an: swering a query with respect to a logic database are given in Ullman (1989). In general, an n-ary relation can be replaced by n + 1 binary relations, as shown by Kowalski (1979a). If one of the arguments forms a key for the relation, as does the course name in the example in Section 2.2, 1 binary relations suffice. 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 er: hancement of a logic program. The technique of developing programs by enhancement is of growing importance. More will be said about this, in Chapter 13, 3 Recursive Programming The programs of the previous chapter essentially retrieve information from, and manipulate, finite data structures. In general, mathematical power is gained by considering infinite or potentially infinite structures. Finite instances then follow as special cases. Logic programs harness this power by using recursive data types. Logical terms can be classified into types. A type is a (possibly infinite) set of terms. Some types are conveniently defined by unary relations. 4 relation p/1 defines the type p to be the set of X's such that p(X). For example, the male/1 and fenale/1 predicates used previously de: fine the male and fenale types. More complex types can be defined by recursive logic programs. Such types are called recursive types. Types defined by unary recursive pro: grams are called simple recursive types. A program defining a type ts called a type definition. In this chapter, we show logic programs defining relations over simple recursive types, such as integers, lists, and binary trees, and also pro: grams over more complex types, such as polynomials 3.1 Arithmetic ‘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 46 Chapter 3 natural_number(X) — X is a natural number natural_number(0) natural_number(e(X)) — natural _nusber(X) 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 stich a familiar area emphasizes the change in thinking 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, 5(0), (8 (0)), 8(6(5(0))),.... We adopt the convention that s"(0) denotes the integer n, that Is, m 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 P. It is correct and complete if its meaning is identical to M. Proving correctness establishes that everything deducible from the program is Intended. Proving completeness establishes that everything intended i deducible from the program. Two correctness and completeness proofs are given in this section. The simple type definition of natural numbers is neatly encapsulated in the logic program, shown as Program 3.1. The relation scheme used is natural_nunber(X), with intended meaning that X is a natural num- ber. The program consists of one unit clause and one iterative clause (a clause with a single goal in the body). Such a program is called minimal recursive Proposition Program 3.1 is correct and complete with respect to the set of goals natural _number(s'(0)), for i = 0. Proof (1) Completeness. Let n be a natural number. We show that the goal natural_number(n) is deducible from the program by giving an explicit proof tree. Either n is 0 or of the form s"(0). The proof tree for the goal natural_number(0) is trivial. The proof tree for the goal 7 Recursive Programming > Blas(s"(0) s%(0) 5°") Gata nave Ele"OS" OSD) N Carano) a) Matural_number(s"(0)) ay Figure 3.1 Proof trees establishing completeness of programs natural _number (s(...s(0)...)) contains n reductions, using the rule in Program 3.1, to reach the fact natural_number (0), as shown in the left half of Figure 3.1 (2) Correctness. Suppose that natural_nurber(X) is deducible from. Program 3.1, in n deductions, We prove that natural_number(X) is in the intended meaning of the program by induction on n. If n= 0, then the goal must have been proved using a unit clause, which implies that X =0.If 2 > 0, then the goal must be of the form natural_number (s(X")), since it is deducible from the program, and further, natural number (X’) is deducible in m ~ 1 deductions. By the induction hypothesis, X’ is in the intended meaning of the program, i., X'=s'(0) for some k = 0. . ‘The natural numbers have a natural order. Program 3.2 is a logic pro- gram defining the relation less than or equal to according to the order. We denote the relation with a binary infix symbol, or operator, =, accord. ing to mathematical usage. The goal 0 = X has predicate symbol < of arity 2, has arguments 0 and X, and is syntactically identical to ‘=? (0,X). 48 Chapter 3 Xs¥ - X and Y are natural numbers, such that X is less than or equal to Y 0 5 X ~ natural_nunber(K) 8) se XS natural_number(X) — See Program 3.1 Program 3.2 The less than or equal relation. The relation scheme is Ny < Ne, The intended meaning of Program 3.2 is all ground facts X < Y, where X and Y are natural numbers and X is less than or equal to ¥. Exercise (ii) at the end of this section is to prove the correctness and completeness of Program 3.2. The recursive definition of < is not computationally efficient. The proof tree establishing that a particular N is less than a particular M has M +2 nodes. We usually think of testing whether one number is less than another as a unit operation, independent of the size of the numbers Indeed, Prolog does not define arithmetic according to the axioms pre sented in this section but uses the underlying arithmetic capabilities of the computer directly. Addition is a basic operation defining a relation between two natural numbers and their sum. In Section 1.1, a table of the plus 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. The intended meaning of Program 3.3 is the set of facts plus (X,Y,2), where X, Y, and 2 are natural numbers and x+ Proposition Programs 3.1 and 3.3 constitute a correct and complete axiomatization of addition with respect to the standard intended meaning of plus/3. Proof (1) Completeness. Let X, Y, and Z be natural numbers such that X4Y=2, We give a proof tree for the goal plus (X,Y,2). If X equals 0, then ¥ equals 2. Since Program 3.1 is a complete axiomatization of the natural numbers, there is a proof tree for natural_number (Y), which is easily extended to a proof tree for plus (0, ¥,¥). Otherwise, X equals s" (0) for some n. If Y equals s” (0), then Z equals s"*" (0). The proof tree in the right half of Figure 3.1 establishes completeness. 49 Recursive Programming plus(X,¥Z) — X,Y ,and Z are natural numbers such that Z is the sum of X and Y, plus(0,X,X) — natural_nuaber (X) plue(s(K) ,¥,s(2)) — plus(,¥,2) natural_nusber(X) ~ See Program 3.1 Program 3.3. Addition (2) Correctness. Let plus (X,Y,Z) be in the meaning. A simple induc: tive argument on the size of X, similar to the one used in the previous proposition, establishes that X* Addition is usually considered to be a function of two argument rather than a relation of arity 3. Generally, logic programs corresponding, to functions of n arguments define relations of arity n+ 1. Computing the value of a function is achieved by posing a query with 7 arguments instantiated and the argument place corresponding to the value of the function uninstantiated. The solution to the query is the value of the function with the given arguments. To make the analogy clearer, we give a functional definition of addition corresponding to the logic program: ox = Xx s(O4¥ = 8K) One advantage that relational programs have over functional programs is the multiple uses that can be made of the program. For example, the query plus(s(0) ,5 (0) ,s(s(0)))? means checking whether I+ 1 = 2 (We feel free to use the more readable decimal notation when mentioning numbers.) As for , the program for plus is not efficient. The proof tree confirming that the sum of N and M is N + M has N + M +2 nodes. Posing the query plus(s(0),s(0),x)?, an example of the standard use, calculates the sum of | and 1. However, the program can just as eas lly be used for subtraction by posing a query such as plus(s (0) ,X,5(s (s(0))))?. The computed value of X is the difference between 3 and 1, namely, 2. Similarly, asking a query with the first argument uninstanti- ated, and the second and third instantiated, also performs subtraction. A more novel use exploits the possibility of a query having multiple so- lutions. Consider the query plus (,Y,s(s(s(0))))?. It reads: “Do there Chapter 3 exist numbers X and ¥ that add up to 3.” In other words, find a partition of the number 3 into the sum of two numbers, X and Y. There are several solutions. A query with multiple solutions becomes more interesting when the properties of the variables in the query are restricted. There are two forms of restriction: using extra conjuncts in the query, and instanti- ating variables in the query. We saw examples of this when querying a database. Exercise (i) at the end of this section requires to define a pred- cate even(X), which is true if X is an even number. Assuming such a predicate, the query plus(X,¥,N) ,even(X) ,even(¥)? gives a partition of 1 into two even numbers. The second type of restriction is exemplified by the query plus (s(s(X)),8(s(¥)) ,4)?, which insists that each of the numbers adding up to W is strictly greater than 1 Almost all logic programs have multiple uses. Consider Program 3.2 for <, for example. The query s(0) 0. Program 3.10 ‘The Fuclidean algorithm Program 3.9 is a translation of the functional definition into a logic pro: gram. The predicate ackermann(M,N,A) denotes that A-ackermann (M,N). The third rule involves two calls to Ackermann's function, one to com pute the value of the second argument. The functional definition of Ackermann's function is clearer than the relational one given in Program 3.9. In general, functional notation is more readable for pure functional definitions, such as Ackermann’ function and the factorial function (Program 3.6). Expressing constraints can also be awkward with relational logic programs. For example, Pro- gram 3.8a says less directly that X = Q- ¥ + Z. The final example in this section is the Euclidean algorithm for finding the greatest common divisor of two natural numbers, recast as a logic program, Like Program 3.8b, it is a recursive program not based on the recursive structure of numbers. The relation scheme is ged(X,¥,2), with intended meaning that Z is the greatest common divisor (or gcd) of two natural numbers X and ¥, It uses either of the two programs, 3.8a or 3.8b, for nod. The first rule in Program 3.10 is the logical essence of the Euclidean algorithm. The ged of X and ¥ is the same as the ged of Y and X mod Y. A proof that Program 3.10 is correct depends on the correctness Recursive Programming of the above mathematical statement about greatest common divisors. ‘The proof that the Euclidean algorithm is correct similarly rests on this result The second fact in Program 3.10 is the base fact. It must be specified that X is greater than 0 to preclude ged(0,0,0) from being in the mean- ing. The gcd of 0 and 0 is not well defined 3.1.1 Exercises for Section 3.1 w aw ain) wv) w wi) wit) (vii) Modify Program 3.2 for < to axiomatize the relations <, >, and = Discuss multiple uses of these programs. Prove that Program 3.2 is a correct and complete axiomatization of Prove that a proof tree for the query 8"(0) = s™(0) using Pro- gram 3.2 has m + 2 nodes. Define predicates even(X) and odd(X) for determining if a natural number is even or odd. (Hint: Modify Program 3.1 for natural_ nurber.) Write a logic program defining the relation £1b(N,F) to determine the nth Fibonacci number F. The predicate times can be used for computing exact quotients with queries such as tines(s(s(0)),X,s(s(s(s(0)))))? to find the result of 4 divided by 2. The query times(s(s(0)).x,s(a(s (©))))? to find 3/2 has no solution. Many applications require the use of integer division that would calculate 3/2 to be 1. Write a program to compute integer quotients. (Hint: Use repeated subtrac- tion.) Modify Program 3.10 for finding the gcd of two integers so that it performs repeated subtraction directly rather than use the mod function. (Hint: The program repeatedly subtracts the smaller num- ber from the larger number until the two numbers are equal.) Rewrite the logic programs in Section 3.1 using a different represen- tation of natural numbers, namely as a sum of 1's. For example, the modified version of Program 3.1 would be 3.2. Lists Chapter 3 natural number (1). natural_number(1#X) — natural_nuber(X) . Note that + is used as a binary operator, and 0 is not defined to be a natural number. 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: tion discusses the binary structure, the list The first argument of a list holds an element, and the second argument {s recursively the rest of the list. Lists are sufficient for most computa- tions — attested to by the success of the programming language Lisp, which has lists as its basic compound data structure, Arbitrarily complex structures can be represented with lists, though it is more convenient to use different structures when appropriate For lists, as for numbers, a constant symbol is necessary to terminate recursion. This “empty list,” referred to as nil, will be denoted here by the symbol [ 1. We also need a functor of arity 2. Historically, the usual functor for lists is “” (pronounced dot), which overloads the use of the period. It is convenient to define a separate, special syntax. The term (X,¥) is denoted [xI¥1. Its components have special names: X ts called the head and Y is called the tail. The term [X|¥) corresponds to a cons pair in Lisp. The corresponding words for head and tail are, respectively, car and cdr. Figure 3.2 illustrates the relation between lists written with different syntaxes. The first column writes lists with the dot functor, and ts the way lists are considered as terms in logic programs, The second column gives the square bracket equivalent of the dot syntax. The third column is an improvement upon the syntax of the second column, essentially hiding the recursive structure of lists. In this syntax, lists are written as a sequence of elements enclosed in square brackets and separated by commas. The empty list used to terminate the recursive structure is suppressed. Note the use of “cons pair notation” in the third column when the list has a variable tail. Recursive Programming al) fail fal (ato I) tallbil [abl a.tby(c D)— fallbite I label fax) [aX] laix} Co) [al.bixIT la.biX} Figure 3.2 Equivalent forms of lists istoxs) = Xs isa list aist(L 1) List(DiIXe]) + 1ist(ts) Program 3.11 Defining a list Terms built with the dot functor are more general than lists. Program 3.11 defines a list precisely. Declaratively it reads: “A list is either the empty list or a cons pair whose tail is a list.” The program is analogous to Program 3.1 defining natural numbers, and is the simple type definition of lists. Figure 3.3 gives a proof tree for the goal 1ist ([a,b,c]). Implicit in the proof tree are ground instances of rules in Program 3.11, for example, List([a,b,c]) ~ List ([b,c)). We specify the particular instance here explicitly, as instances of lists in cons pair notation can be confusing. [a,b,c] isan instance of [XIXe] under the substitution {X=a,s"[b,¢] | Because lists are richer data structures than numbers, a great variety of interesting relations can be specified with them, Perhaps the most basic operation with lists is determining whether a particular element is in a list. The predicate expressing this relation is menber (Element, List). Program 3.12 is a recursive definition of member/2. Declaratively, the reading of Program 3.12 is straightforward. X is an element of a list if it is the head of the list by the first clause, or if it is a member of the tail of the list by the second clause. The meaning. of the program is the set of all ground instances menber(X,Xs), where Chapter 3 list(t aei> Figure 3.3. Proof tree verifying a list ‘member (Element,List) Element is an element of the list List. monber (X, [x/X6]) ember (X,[YIYs]) — monber(X,¥s) Program 3.12 Membership of alist X is an element of Xs, We omit the type condition in the first clause, Alternatively, it would be written member (X, [XIXs]) ~ List (Xs) This program has many interesting applications, to be revealed throughout the book. Its basic uses are checking whether an element fs in a list with a query such as menber(b, [a,b,c])?, finding an ele- ment of a list with a query such as member (X, [a,b,c] )?, and finding a list containing an element with a query such as nenber(b,X)?. This last query may seem strange, but there are programs that are based on this use of menber. We use the following conventions wherever possible when naming vari ables in programs involving lists. If X is used to denote the head of a list, then Xs will denote its tail. More generally, plural variable names will denote lists of elements, and singular names will denote individual ele ments, Numerical suffixes will denote variants of lists. Relation schemes will still contain mnemonic names. Recursive Programming prefix Prefix.List). ~ Prefix is a prefix of List prefix({ 1,5) profix((XI%e], (KI¥s]) ~ prefix(ts,¥s) suffix(Suffix,List) Suffix 18 a Sufix of List euffix(Ke,Xe) suffix(Ke, [YI¥e]) = suffix (Xs, Ys) Program 3.13 Prefixes and suffixes of 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,c] is a sublist of [a,b, c,d, whereas [a,c] is not. 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 to define meaningful relations as auxiliary predicates. The two cases con- sidered are initial sublists, or prefixes, of a list, and terminal sublists, or suffixes, of a list. The programs are interesting in their own right. The predicate prefix (Prefix, List) Is true if Prefix is an initial sub- list of List, for example, prefix(La,b], [a,b,¢]) is true. The compan: ion predicate to prefix is suffix(Suffix,List), determining if Suffix isa terminal sublist of List. For example, suffix(Tb,c],[a,b,c]) is true, Both predicates are defined in Program 3.13. A type condition ex: 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. An arbitrary sublist can be specified in terms of prefixes and suffixes: namely, as a suffix of a prefix, or as a prefix of a suffix. Program 3.14 expresses the logical rule that Xs is a sublist of Ys if there exists Ps such that Ps is a prefix of Ys and Xs is a suffix of Ps, Program 3.L4b is the dual definition of a sublist as a prefix of a suffix. ‘The predicate prefix can also be used as the basis definition of sub1ist. This is given as Program 3.14. The base rule reads that a prefix of a list 1s a sublist of a ist. The recursive rule reads that the sublist of a tail of a list is a sublist of the list itself The predicate member can be viewed as a special case of sublist de fined by the rule member (X,%s) — sublist([X) ,Xs) 60 Chapter 3 sublist (Sub,List) Sub is a sublist of List a: Suffix of a prefix sublist(Xe,Ys) ~ prefix(Pe,Ye), suffix(Xs,Pe) b: Prefix of a suffix sublist (Xs,Ys) ~ prefix(is,Ss), suffix(ss,Ye). Recursive definition of a sublist sublist (te,Ye) ~ prefix(%s,Ye) sublist (ts, (YI¥s]) — sublists, Ys) d: Prefix of a suffix, using append sublist (Xe,As%sBs) ~ append(As,XsBs,As%sBs), append(Xs,Bs,1sBs) © Suffix of a prefix, using append _ ASKSBs) , append(ke Xs, Asks) sublist (le Ast appand( Asks: Program 3.14 Determining sublists of lists, append (Xs,Ys,Xs¥s) XSYs Is the result of concatenating the lists Xs and Ys, append 1,¥8,Ys) append( [X/s],Ys,[XI2s]) ~ append(Xe,Ys,22) Program 3.15 Appending two lists The basic operation with lists 1s concatenating two lists to give a third list. This defines a relation, append (Xs, Ys,Zs), between two lists Xs, ¥s and the result Zs of joining them together. The code for append, Pro: gram 3.15, is identical in structure to the basic program for combining two numbers, Program 3.3 for plus. Figure 3.4 gives a proof tree for the goal append( a,b] , [c,d [a,b, ¢,d]). The tree structure suggests that its size is linear in the size of the first list. In general, if Xs is a list of n elements, the proof tree for append (Xs,Ys,Zs) has n + I nodes. There are multiple uses for append similar to the multiple uses for plus. The basic use is to concatenate two lists by posing a query such 61 Recursive Programming ‘appendia,b},(c.d},{a.6.0<]) opera 7) append({ ],{c.d],{c.d]) Figure 3.4. Proof tree for appending two lists as append([a,b,c], [d,e),Xs)? with answer Xs=[a,b,¢,d,e]. A query such as append(xs, [c,d], [a,b,¢,d])? finds the difference Xs=La,b] between the lists [c,d] and [2,b,c,d]. Unlike plus, append is not sym- metric in its first two arguments, and thus there are two distinct versions of finding the difference between two lists. The analogous process to partitioning a number is splitting a list. The ‘query append(As,Bs, [a,b,c,d])?, for example, asks for lists As and Bs such that appending Bs to As gives the list [2,b,c,4]. Queries about splitting lists are made more interesting by partially specifying the na ture of the split lists. The predicates menber, sublist, prefix, and suf- fix, introduced previously, can all be defined in terms of append by viewing the process as splitting a list. The most straightforward definitions are for prefix and suffix, which Just specify which of the two split pieces are of interest: prefix(Xs,Ys) ~ append(is,As, Ys) suffix(Xs,Ys) ~ append(As,Xs, Ys) Sublist can be written using two append goals. There are two distinct variants, given as Programs 3.14d and 3.L4e, These two programs are obtained from Programs 3.14a and 3.14b, respectively, where prefix and suffix are replaced by append goals. Member can be defined using append, as follows: member (X,Ys) — append(As, [XIXs] , Ys) ‘This says that X is a member of Ys if Ys can be split into two lists where X is the head of the second list. Chapter 3 reverse(List,Tsll) » Tsil is the result of reversing the list List. a: Naive reverse reverse({ 1, 1) reverse((KIks] Zs) ~ reverse(Xe,¥s), append(¥s, (X1 ,28) b: Reverse-accumulate reverse(Ke,Ye) — revers: reverse (X/Xs] ,Ace, Y=) ~ reverse(( 1,Y5,¥s) Xs, 1,¥8). reverse (Xe, [£1 Acc] ,¥s) Program 3.16 Reversing a list A similar rule can be written to expr that two elements X and ¥ are adjacent the relation adjacent (X,Y,Z5) list 2s: adjacent (X,¥,Zs) — append(As, [X,YI¥s] ,2s) 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: Last (X,%s) ~ append(As, [10 , Xs). Repeated applications of append can be used to define a predicate reverse (List, Tsi1). 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,cl , [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 directly. We define an auxiliary predicate reverse(Xs,Ys,2s), which 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.16D. Program 3.16b is more efficient than Program 3.16a. Consider Fig: ure 3.5, showing proof trees for the goal reverse( (a,b,c) , [c,b,al) us ing both programs. In general, the size of the proof tree of Program 3.16a 63 Recursive Programming reverse([a,b,c].[c,b,al) Figure 3.5. Proof trees for reversing a list Chapter 3 length(Xs.N) ~ ‘The list Xs has N elements. Lengtnc{ 1,0) Longen (tI Ke] ,2()) — Lengen(te,y). Program 3.17. Determining the length of a list is quadratic in the number of elements in the list to be reversed, while that of Program 3.16b is linear. The insight in Program 3.16b is the use of a better data structure for representing the sequence of elements, which we discuss in more detail in Chapters 7 and 15. ‘The final program in this section, Program 3.17, expresses a rela- tion between numbers and lists, using the recursive structure of each. The predicate length(Xs,N) is true if Xs is a list of length N, that is, contains W elements, where x is a natural number. For example, Jength([a,b] ,5(s(0))), indicating that [a,b] has two elements, is in the program's meaning. Let us consider the multiple uses of Program 3.17. The query Length (a,b) ,X)? computes the length, 2, of a list [a,b]. In this way, length is regarded as a function of a list, with the functional definition Tengeh({ 1) = 0 Jength([xIXs]) = s(length(Ks)). ‘The query Length [a,b] ,s(s(0)))? checks whether the list [a,b] has length 2, The query Length (Xs,s(s(0)))? generates a list of length 2 with variables for elements, 3.2.1 Exercises for Section 3.2 (i) A variant of Program 3.14 for sublist is defined by the following three rules: subsequence([XIXs] ,[XI¥s]) ~ subsequence (Xs, Ys) subsequence (Xs, [YIYs]) ~ subsequence (Xs, Ys) subsequence([ ],Ys) Explain why this program has a different meaning from Pro- gram 3.14. 3.3 Composing Recursive Programs Recursive Programming (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 in List appears twice in ListList, eg, double({1,2,3], (1.1.2, 2,3,3]) is true, (iv) Compute the size of the proof tree as a function of the size of the input list for Programs 3.16a and 3.16b defining reverse. (W) Define the relation sus (ListOfTntegers,Sun), which holds if Sun is the sum of the ListOfIntegers, (a) Using plus/: (b) Without using any auxiliary predicate. (Hint: Three axioms are enough.) No explanation has been given so far about how the example logic pro- grams have been composed. The composition of logic programs is a skill that can be learned by apprenticeship or osmosis, and most definitely by practice. For simple relations, the best axiomatizations have an aesthetic elegance that look obviously correct when written down. Through solv ing the exercises, the reader may find, however, that there is a difference between recognizing and constructing elegant logic programs. This section gives more example programs involving lists. Their, pre- sentation, however, places more emphasis on how the programs might be composed. Two principles are illustrated: how to blend procedural and declarative thinking, and how to develop a program top-down. We have shown the dual reading of clauses: declarative and procedural How do they interrelate when composing logic programs? Pragmatically, one thinks procedurally when programming. However, one thinks declar- atively when considering issues of truth and meaning. One way to blend ‘them in logic programming is to compose procedurally and then inter- pret the result as a declarative statement. Construct a program with a 66 Chapter 3 given use in mind; then consider if the alternative uses make declarative sense, We apply this to a program for deleting elements from a 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 Li 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,L2). The natural meaning is 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 se, Consider the query delete([a.b,c,b] ,b,x)?, a typical example of finding the result of deleting an element from a list. The answer here is, fa.c]. The program will be recursive on the first argument. Let's don our procedural thinking caps. We begin with the recursive part. The usual form of the recursive ar gument for lists is [X/Xs). 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 delete ([X|Xs] ,x,Ys) — delete (Xs, x,¥s) Switching hats, the declarative reading of this rule is: “The deletion of X from [XIXs} 1s ¥s if the deletion of X from Xs is Ys." The condition that the head of the list and the element to be deleted are the same is specified by the shared variable in the head of the rule. The second case where the element to be deleted is different from X, the head of the list, is similar. The result required is a list whose head is X and whose tail is the result of recursively deleting the element. The rule is delete([x|Xs] ,2,[XI¥s]) ~ x # Z, delete(Xs,Z,Ys) The rule's declarative reading is: “The deletion of Z from (XIXs} is [xlvs] if Z is different from X and the deletion of Z from Xs is Ys." In contrast to the previous rule, the condition that the head of the list and the element to be deleted are different is made explicit in the body of the rule. The base case is straightforward. No elements can be deleted from the ‘empty list, and the required result is also the empty list. This gives the Recursive Programming deletecList,X,HasNoXs) — ‘The list HasNoXs Is the result of removing all occurrences of X from the list List. delete (XIxe},X,¥s) — dereve(xs,x,¥s) delete (kixs],2, /¥6]) ~ X#2, delere(Xs,7,¥e) deleve(( 1,x,0 1) Program 3.18 Deleting all occurrences of an element from a list select (X,HasXs,OneLessXs) ~ ‘The list OneLessXs Is the result of removing one occurrence of X from the list HasXs select (K, [X1Xe] ,X8) X,[vl¥e] ,[I28]) ~ select(X,Ys,28). Program 3.19 Selecting an element from a list fact delete({ 1,x,[ 1). The complete program is collected together as Program 3.18. Let us review the program we have written, and consider alternative formulations. Omitting the condition X42 from the second rule in Pro- gram 3.18 gives a variant of delete. This variant has a less natural mean: ing, since any number of occurrences of an element may be deleted. For example, delete([a.b.c.b] .b,[a.c]), delete([a,b,c,b].b. [a.c, bl), delete(La,b,c,b].b, [a,b,e)), and delete(Ta,b,c,b],b,[a,b, ¢,b]) are all in the meaning of the variant. Both Program 3.18 and the variant include in their meaning instances where the element to be deleted does not appear in either list, for ex- ample, delete([a) ,b, [a]) is true. There are applications where this not desired, Program 3.19 defines select (X,L1,L2), a relation that has a different approach to elements not appearing in the list. The meaning of select (X,L1,L2) is all ground instances where L2 is the list L1 where exactly one occurrence of X has been removed, The declarative reading of Program 3.19 is: “x is selected from [X|Xs] to give Xs; or X is selected from [¥lYs] to give L¥|2s} if X is selected from Ys to give Zs.” ‘A major thrust in programming has been the emphasis on a top-down design methodology, together with stepwise refinement. Loosely, the 68 Chapter 3 methodology is to state the general problem, break it down into subprob- lems, and then solve the pieces. A top-down programming style is one natural way for composing logic programs. Our description of programs throughout the book will be mostly top-down. The rest of this section de scribes the composition of two programs for sorting a list: permutation sort and quicksort. Their top-down development is stressed. ‘A logical specification of sorting a list is finding an ordered permuta- tion of a list. This can be written down immediately as a logic program, The basic relation scheme is sort (Xs,¥s), where Ys is a list containing the elements in Xs sorted in ascending order: sort (Xs,Ys) ~ permutation(Xe,Ys), ordered(Ys) ‘The top-level goal of sorting has been decomposed. We must now define permutation and ordered. 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 Is necessarily ordered. The rule says that a list is ordered if the first element is less than or equal to the second, and if the rest of the list, beginning from the second element, is ordered: ordered([X]) . ordered([X,YI¥s]) ~ X = Y, ordered([¥l¥s]) A program for permutation is more delicate. One view of the process of permuting a list is selecting an element nondeterministically to be the first element of the permuted list, then recursively permuting the rest Of the list. We translate this view into a logic program for permutation, using Program 3.19 for select. The base fact says that the empty list is its own unique permutation: permutation(Xs,[ZIZs]) — select(Z,Xs,Ys), permutation (Ys,Zs) . permutation({ J,[]) Another procedural view of generating permutations of lists is recur sively permuting the tail of the list and inserting the head in an arbitrary position. This view also can be encoded immediately. The base part is identical to the previous version: permutation([X|%s] ,Zs) — permutation(Xs,¥s), insert (X,Ys,Zs) permutation(f J,£ 1) 69 Recursive Programming sort(Xs,¥s). ~ ‘The list 5 is an ordered permutation of the list 4s. sort (Xs,Ys) ~ permutation({s,Ys), ordered(¥s) perautation(Ks,[Z12s]) ~ select (2,%s,Ys), permutation(Ys,Zs) perautation([ 1,( 1) ordered(t 1) ordered( [x]) ordered((X,YI¥el) ~ X < ¥, ordered({rive]) Program 3.20. Permutation sort The predicate insert can be defined in terms of Program 3.19 for se~ lect: insert (K,Ys,Zs) ~ select(X,Zs, Ys) Both procedural versions of permutation have clear declarative read- ings. The “naive” sorting program, which we call permutation sort, Is col- 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 base case for ordered so that the program behaves correctly for empty lists. The problem of sorting lists is well studied. Permutation sort is not a good method for sorting lists in practice. Much better algorithms come from applying a “divide and conquer” strategy to the task of sorting. The insight is to sort a list by dividing it into two pieces, recursively sorting the pieces, and then joining the two pieces together to give the sorted list. The methods for dividing and joining the lists must be specified. There are two extreme positions. The first is to make the dividing hard, and the joining easy. This approach is taken by the quicksort algorithm, The second position is making the joining hard, but the dividing easy This is the approach of merge sort, which is posed as Exercise (v) at the end of this section, and insertion sort, shown in Program 3.21 In insertion sort, one element (typically the first) is removed from the list. The rest of the list is sorted recursively; then the element is inserted, preserving the orderediness of the li The insight in quicksort is to divide the list by choosing an arbitrary it, and then to split the list into the elements smaller than the element Chapter 3 sort(Xs,¥s) The list_ YS is an ordered permutation of the list Xs. sort ((XIXs] ,¥e) ~ eort(Xs,28), ineert(X,28,¥e) sort((1,01) insere(t, (1, [X)) insert(t, (¥I¥el [YIZs]) ~ X > Y, insert(t,¥e,Zs) insert (t, (¥l¥e],,YI¥el) — x < ¥. Program 3.21 Insertion sort quicksort(Xs,¥8)_~ The list Ys is an ordered permutation of the list 3s. quicksort((t)X8} Ye) — partition(Ks,X,Littles,Bige), quicksort(Littles,Ls) quicksort(Bigs,Be), append(Ls, (11Bs] ,¥5). quicesort(( 1,01) partition([tIXe),¥,[XILs] Be) ~ X < Y, partivion(s,¥,Le,Be). partition([X!Xs] ,Y,Ls, [tIBs]) ~ X > Y, partition(ts,¥,Ls,Bs) partition(L 1,¥,01,01) Program 3.22 Quicksort chosen element and the elements larger than the chosen element. The sorted list is composed of the smaller elements, followed by the chosen clement, and then the larger elements. The program we describe chooses the first element of the list as the basis of partition, Program 3.22 defines the quicksort algorithm. The recursive rule for quicksort reads: “Ys is a sorted version of [X\Ks] if Littles and Bigs are a result of partitioning Xs according to X; Ls and Bs are the result of sorting Littles and Bigs recursively; and Ys is the result of appending [X\Bs} to Ls.” Partitioning a list ts straightforward, and is similar to the program for deleting elements. There are two cases to consider: when the current head of the list is smaller than the element being used for the parti- tioning, and when the head is larger than the partitioning element. The declarative reading of the first partition clause is: “Partitioning a lis! Whose head is X and whose tail is Xs according to an element ¥ gives the Recursive Programming lists [X\Littles) and Bigs if X isle s than or equal to ¥, and partitioning Xs according to ¥ gives the lists Littles and Bigs.” The second clause for partition has a similar reading. The base cas Is that the empty list is partitioned into two empty lists. 3.3.1 0 «iid «ity ti) Ww wi Exercises for Section 3.3 Write a program for substitute(X,Y,L1,L2), where L2 is the result of substituting Y for all occurrences of X in Li, eg, sub~ stitute(a,x, [a,b,a,c],[x,b,x,c]) is true, whereas substi~ tute(a,x, [a,b,a,c) , [a,b.x,)) is false, What is the meaning of the variant of select: select (X, [X1Xs] ,Xs) select (X, [YlYs},{¥IZs]) ~ X # Y. select (X, 5,25). Write a program for no_doubles(L1,L2), where L2 is the result of removing all duplicate elements from L1, e.g., no_doubles([a,b,¢, b),[a,c,b)) is true. (Hint: Use member.) Write programs for even_permutation(Xs, Ys) and odd_permuta~ tion(Xs, Ys) that find Ys, the even and odd permutations, respec tively, of alist Xs. For example, even_permutation([1,2,3] ,[2,3, 11) and odd_permutation([1,2,3] ,[2,1,3]) are true. Write a program for merge sort. Write a logic program for kth_largest (Xs,X) that implements the linear algorithm for finding the kth largest element K of a list Xs, The algorithm has the following steps: Break the list into groups of five elements. Efficiently find the median of each of the groups, which can be done with a fixed number of comparisons. Recursively find the median of the medians. Partition the original list with respect to the median of medians. Recursively find the kth largest element in the appropriate smaller list. Chapter 3 (vii) Write a program for the relation better_poker_hand(Hand1, Hand2,Hand) that succeeds if Hand is the better poker hand be: tween Handi and Hand2. For those unfamiliar with this card game, here are some rules of poker necessary for answering this exercise (a) The order of cards is 2, 3, 4, 5, 6, 7, 8, 9, 10, Jack, queen, king, ace, (b) Each hand consists of five cards. (©) The rank of hands in ascending order is no pairs < one pair < two pairs < three of a kind < flush < straight < full house < four of a kind < straight flush, (a) Where two cards have the same rank, the higher denomination wins, for example, a pair of kings beats a pair of 7’s (Hints: (1) Represent a poker hand by a list of terms of the form card(Suit,Value). For example a hand consisting of the 2 of clubs, the 5 of spades, the queen of hearts, the queen of dia- monds, and the 7 of spades would be represented by the list [card (clubs, 2),card (spades ,5),card(hearts,queen),card (diamonds, queen) ,card(spades,7)). (2) It may be helpful to define relations such as has_flush(Hand), which is true if all the cards in Hand are of the same sult; has_full_house (Hand), which 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), 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.) 3.4 Binary Trees We next consider binary trees, another recursive data type. These struc tures have an important place in many algorithms. Binary trees are represented by the ternary functor tree (Element, Left Right), where Element Is the element at the node, and Left and Right are the left and right subtrees respectively. The empty tree is, represented by the atom void. For example, the tree 73 Recursive Programming aS boc would be represented as tree(a,tree(b, void, void) ,tree(c,void,void)) Logic programs manipulating binary trees are similar to those manip- 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- gram is doubly recursive; that is, there are two goals in the body of the recursive rule with the same predicate as the head of the rule. This re- sults from the doubly recursive nature of binary trees and will be seen also in the rest of the programs of this section. Let us write some tree-processing programs. Our first example tests whether an element appears in a tree. The relation scheme is tree nenber (Element ,Tree). The relation is true if Element Is one of the nodes in the tree. Program 3.24 contains the definition. The declarative reading of the program is: “X is a member of a tree if it is the element at the node (by the fact) or if itis a member of the left or right subtree (by the two recursive rules).” The two branches of a binary tree are distinguishable, but for many ap- plications the distinction is not relevant. Consequently, a useful concept binary-tree(Tree) Tree is abinary tree, binary_tree(void) binary_tree(tree(Elexont ,Left ,Right)) ~ binary treo(Left), binary tree(Right) Program 3.23. Defining binary trees tree_member(Element,Tree) — Element is an element of the binary tree Tree. ‘eree.nomber (X, tree(K,Left Right)) tree neuber (X,tree(¥,Left,Right)) tree noaber (X,cree(¥,Lett Right) ) ‘tree nenber(X,Left) ‘tree _momber(X,Aight) Program 3.24 1 ing tree membership, Chapter 3 DigeGe Da: aac: Figure 3.6 Comparing trees for isomorphism isotree(Treel,Tree2) « Tree! and Tree? are isomorphic binary trees. isotree(void, void) isotree (tree (X,Leftt Right) ,tree(,Left2,Right2)) ~ ieotree(Lefti,Left2), icctree(Rightt ,Rignt2) isotree (tree (X,Leftt Rightt) ,tree(L,Left2,Right2)) « deotree(Lefti Right2), isotree(Rignt,Left2) Program 3.25 Determining when trees are isomorphic is isomorphism, which defines when unordered trees are essentially the same, Two binary trees T1 and 72 are isomorphic if T2 can be obtained by reordering the branches of the subtrees of T1. Figure 3.6 shows three simple binary trees. The first two are isomorphic; the first and third are not. Isomorphism is an equivalence relation with a simple recursive defini- tion. Two empty trees are isomorphic. Otherwise, two trees are isomor- phic if they have identical elements at the node and either both the left subtrees and the right subtrees are isomorphic; or the left subtree of one is isomorphic with the right subtree of the other and the two other sub. trees are isomorphic Program 3.25 defines a predicate isotree(treei,Tree2), which Is true if Tree1 and Tree2 are isomorphic. The predicate is symmetric in its arguments. Programs related to binary trees involve double recursion, one for each branch of the tree. The double recursion can be manifest in two ways. Programs can have two separate cases to consider, as in Program 3.24 for ‘tree_nenber. In contrast, Program 3.12 testing membership of a list has only one recursive case. Alternatively, the body of the recursive clause has two recursive calls, as in each of the recursive rules for isotree in Program 3.25. Recursive Programming substitute(X,Y,TreeX,TreeY) » ‘The binary tree Trey is the result of replacing all occurrences of X inthe binary tree TreeX by Y substitute (X,Y,void,void) subericure(X,Y,tree (llode Left Right) tree(Nodel Leftt,Rightt)) ~ replace(,¥,Node,Nodet) , substicute(x,Y,Left Leftt), substitute(X,Y Right ,Rightt) replace (K,¥,X,¥)- roplace(K,¥,2,2) — x #2 Program 3.26 Substituting for a term in a tree The task in Exercise 3.3()) 1s to write a program for substituting for el- ements in lists. An analogous program can be written for substituting, elements in binary trees. The predicate substitute (X,¥,0léTree, NewTree) Is true if NewTree is the result of replacing all occurrences of X by Y in OldTree. An axiomatization of substitute/4 is given as Program 3.26. Many applications involving trees require access to the elements ap- pearing as nodes. Central Is the idea of a tree traversal, which is a se- quence of the nodes of the tree in some predefined order. There are three possibilities for the linear order of traversal: preorder, where the value of the node is first, then the nodes in the left subtree, followed by the nodes in the right subtree; inorder, where the left nodes come first followed by the node itself and then the right nodes; and pastorder, where the node comes after the left and right subtrees. A definition of each of the three traversals is given in Program 3.27. ‘The recursive structure is identical; the only difference between the pro- grams is the order in which the elements are composed by the various append goals. The final example in this section shows interesting manipulation of trees. A binary tree satisfies the heap property if the value at each node 5 at least as large as the value at its children (if they exist), Heaps, a class of binary trees that satisfy the heap property, are a useful data structure and can be used to implement priority queues efficiently. It is possible to heapify any binary tree containing values for which an ordering exists. That is, the values in the tree are moved around so that 76 Chapter 3 preorder Tree,Pre) ~ Pre isa preorder traversal of the binary tree Tree. preorder(tree(X,L,R) ,X8) — precrder(L,Le), preorder(R,Ra), append((XILs] ,Re,Xe) preorder(void,( 1) inorder (Tree,In) ~ In is an inorder traversal of the binary tree Tree. Anorder(tree(K,L,R),Xs) — inorder(L,Ls), inorder(R,Rs), append(Ls, (XIRs] .Xs) Anorder(void, [ 1) postorder(Tree,Post) — Post is a postorder traversal of the binary tree Tree. postorder(eree(X,L,8) Xe) poetorder(L,Le) postorder(R.Rs), append (Rs, (1) ,Rst), append(Le, Ret, Xs) postorder(void, ( 1) Program 3.27. Traversals of a binary tree the shape of the tree is preserved and the heap property is satisfied. An example tree and its heapified equivalent are shown in Figure 3.7. An algorithm for heapifying the elements of a binary tree so that the heap property is satisfied is easily stated recursively. Heapify the left and right subtrees so that they both satisfy the heap property and then ad just the element at the root appropriately. Program 3.28 embodies this algorithm, The relation heapify/2 lays out the doubly recursive pro- gram structure, and adjust (X,HeapL,HeapR,Heap) produces the final 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 adjust/4 depending on the values. If the root value is larger than the root values of the left and right subtrees, then the heap is tree(X,HeaplHeapR). This is indicated in the first adjust: clause in Program 3.28. The second clause handles the case where the root node in the left heap is larger than the root node and the root of the right heap. In that case, the adjustment proceeds recursively on the left heap. The third clause handles the symmetric case where the root node of the right heap is the largest. The code is simplified by relegating the concern whether the subtree is empty to the predicate greater/2 Recursive Programming God GAR @D @ ® @ gi Ca QO & GQ Figure 3.7 A binary tree and a heap that preserves the tree’s shape heapify (Tree,Heap) — ‘The elements of the complete binary tree Tree have been adjusted to form the binary tree Heap, which has the same shape as Tree and satisfies the heap property that the value of each parent node is greater than or equal fo the values of its children, neapity (void, void) eapity(eree(X,L,R) Heap) ~ heapity(L,Hleapl), neapify(R,HeapR), adjuse(t,Heapl. Heap, Heap) ‘adjust (K,HeapL, Heap, tree(X,HoapL,Heaph)) — greater (,Hleapl), greater (K,HeapR) adjust(X,tre0(X1,L,R) Heaph,treo(Xi,Heapl,Heapa)) — X < XL, greater(Xt Heap), adjust(t,L,R,Heapl) ‘adjust(X,Heapl,tree(tt,L,R) ,tree(Xi,Heapl,Heapt)) — K€ XL, greacer(X1,Heapl), adjust(t,L,R,Heaph) greater(,void) greater(K,tree(k1,L,8)) © K =X Program 3.28 Adjusting a binary tree to satisfy the heap property 3.4.1 Exercises for Section 3.4 (i) Define a program for subtree (S,T), where $ is a subtree of T. (ii) Define the relation sun_tree (TreeOfIntegers,Suz), which holds if Sum is the sum of the integer elements in Treedf Integers, (iti) Define the relation ordered (TreeOf Integers), which holds if Tree is an ordered tree of integers, that is, for each node in the tree the elements in the left subtree are smaller than the element in 78 3.5 Manipulating Symbolic Expressions Chapter 3 the node, and the elements in the right subtree are larger than the element in the node. (Hint Define two auxiliary relations, ordered_left(X,Tree) and ordered_right(X,Tree), which hold if both Tree is ordered and X is larger (respectively, smaller) than the largest (smallest) node of Tree.) (iv) Define the relation tree_insert(X,Tree,Treet), which holds if Tree! is an ordered tree resulting from inserting X into the ordered tree Tree. If X already occurs in Tree, then Tree and Tree1 are iden: tical. (Hint: Four axioms suffice.) () Write a logie program for the relation path (X,Tree,Path), where Path is the path from the root of the tree Tree to X. 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- bolic differentiation, a program for solving the Towers of Hanoi problem, and a program for testing the satisfiability of Boolean formulae. The first example is a program for recognizing polynomials in some term X. Polynomials are defined inductively. X itself is a polynomial in X, as is any constant. Sums, differences, and products of polynomials in X are polynomials in X. So too are polynomials raised to the power of a natural number, and the quotient of a polynomial by a constant. ‘An example of a polynomial in the term x is x? ~ 3x + 2. This follows. from its being the sum of the polynomials, x? ~ 3x and 2, where x? ~ 3x. is recognized recursively A logic program for recognizing polynomials is obtained by expressing the preceding informal rules in the correct form, Program 3.29 defines, the relation polynomial (Expression, X), which is true if Expression is, a polynomial in X. We give a declarative reading of two rules from the program. ‘The fact polynomial (K,X) says that a term X is a polynomial in itself The rule Recursive Programming polynomial Expression.) — Expression is a polynomial in X. polynomial (X,X) polynomial (Ters,X) ~ constant(Ters) polynomial (Terni +Tern2,x) ~ polynomial (Termi,%), polynomial (Tern2,1) polynomial (Tera !—Tern2,x) ~ polynomial (Tern!,%), polynomial (Term2,X) polynoaial (Termt4Tern2,x) — polynosial (Tert,X), polynomial (Term2,X) polynoaial (Terai/Ters2,x) — polynomial (Ter=i,X), constant (Tern?) polynomial (Teralii,x) = natural nusber(N), polynomial (Term, X) Program 3.29 Recognizing polynomials polynomial (Term1+Term2,x) polynonial (Term1,X), polynomial (Term2,X) says that the sum Termi+Tern2 is a polynomial in X if both Termt and Tern2 are polynomials in X. Other conventions used in Program 3.29 are the use of the unary pred- fcate constant for recognizing constants, and the binary functor t to denote exponentiation. The term Xr¥ denotes X" The next example is a program for taking derivatives. The relation scheme Is derivative (Expression, X,DifferentiatedExpression) The intended meaning of derivative is that DifferentiatedExpres sion is the derivative of Expression with respect to X, As for Program 3.29 for recognizing polynomials, a logic program for differentiation is just a collection of the relevant differentiation rules, written in the correct syntax. For example, the fact derivative (X,X,s(0)) expresses that the derivative of X with respect to itself is 1. The fact derivative (sin (X) ,X,cos(X)) 80 Chapter 3 derivative Expression,X,DifferentiatedExpression) — DifferentiatedExpression is the derivative of Expression with respect to X. derivative(t,x,8(0)) derivative (His(N) ,X,8(N) +E1N) derivative (sin(K) ;X,co8(K)) derivative (cos(X) ,X,~sin(®)) derivative e!X,X,21X) derivative 1og(0 ,X,1/8) derivavive(FrG,X,DF+D6) — derivative(F,X,0F), derivative (G,X,06) derivacive(F-G,X,0F-DG) — derivative(F,X,0F), derivative (G,x,06) derivative(FeG,X,Fe0G+DFeG) — dorivative(F,X,0F), derivative(@,X,0¢) derivative(t/F,X,-D8/(F#F)) = derivative(?,x,0F) dorivative(#/6,X, (GeDF-Fe06) /(G+G)) ~ derivative(F,E,DF), derivative(G,X,0¢) Program 3.30 Derivative rules reads: “The derivative of sin(X) with respect to X is cos (X).” Natural mathematical notation can be used. A representative sample of functions and their derivatives is given in Program 3.30. Sums and products of terms are differentiated using the sum rule and product rule, respectively. The sum rule states that the derivative of a sum is the sum of derivatives. The appropriate clause is derivative(F+G,x,DF+DG) — derivative(F,X,DF), derivative (G,x,D¢) ‘The product rule is a little more complicated, but the logical clause is just the mathematical definition: derivative (F+G,X,FsDG+DFsG) ~ derivative(F,x,DF), derivative (G,X,D6) Program 3.30 also contains the reciprocal and quotient rules. ‘The chain rule is a little more delicate. It states that the derivative of Fig(x)) with respect to x is the derivative of (g(x) with respect to a(x) times the derivative of g(x) with respect to x. As stated, it involves quan- 81 Recursive Programming tification over functions, and is outside the scope of the logic programs wwe have presented. Nonetheless, a version of the chain rule is possible for each particular function. For example, we give the rule for differentiating x" and sin(X) derivative (Uis(N),X,5(N)*UIN*DU) - derivative (U,X,DU) derivative (sin(U) ,X,cos(U)*DU) ~ derivative (U,x,DU) ‘The difficulty of expressing the chain rule for differentiation arises from our choice of representation of terms. Both Programs 3.29 and 3.30 use the “natural” representation from mathematics where terms represent themselves. A term such as sin(X) is represented using a unary structure sin. Ifa different representation were used, for example, unary_term(sin,X) where the name of the structure is made accessible, then the problem with the chain rule disappears. The chain rule can then be formulated as derivative (unary_term(F,U),X,DF*DU) - derivative (unary_term(F,U),U,DF), derivative(U,x,DU) 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. People take for granted the automatic simplification of expressions when differentiating expresstons. Simplification is missing from Program, 3.30. The answer to the query derivative (3x+2,x,D)? is D=(3¥ 140% x)40, We would immediately simplify D to 3, but itis not specified in the logic program. The next example is a solution to the Towers of Hanoi problem, a standard introductory example in the use of recursion. The problem is to move a tower of n disks from one peg to another with the help of an. auxiliary peg. There are two rules. Only one disk can be moved at a time, and a larger disk can never be placed on top of a smaller disk. There is a legend associated with the game. Somewhere hidden in the surroundings of Hanoi, an obscure Far Eastern village when the legend was first told, is a monastery. The monks there are performing a assigned to them by God when the world was created — solving the preceding problem with three golden pegs and 64 golden disks. At the moment they complete their task, the world will collapse into dust. Since the optimal solution to the problem with m disks takes 2" ~ 1 moves, we Chapter 3 hanoi(N.A,B,C.Moves) ~ Moves is a sequence of moves for solving the Towers of Hanoi puzzle with N disks and three pegs, A, B, and C. nano’ (@(0) ,A,B,C, [A to B]) ynanod (8(N) ,A,B,C, Moves) ~ anos (W,A,C,B,Met), tpanod (W,C,B,4,§62), ‘append(tel, (k to BlMe2]) Moves) Program 3.31 Towers of Hanoi need not lose any sleep over this possibility. The number 2° is comfort. ingly big. ‘The relation scheme for solving the problem is hanoi(W,A,B,C, Moves). It is true if Moves Is the sequence of moves for moving a tower of N disks from peg & to peg B using peg ¢ as the auxiliary peg. This 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 the top disk on peg X is moved to peg Y. The program for solving the problem is given in Program,3.31. ‘The declarative reading of the heart of the solution, the recursive rule in Program 3.31, is: “Moves ts the sequence of moves of s(N) disks from peg A to peg B using peg C as an auxiliary, if Ms1 is the solution for moving W disks from & to C using B, Ms2 is the solution for moving ¥ disks from C to B using A, and Moves is the result of appending [A to BiMs2] toms ‘The recursion terminates with moving one disk. A slightly neater, but Jess intuitive, base for the recursion is moving no disks. The appropriate fact is hanoi(0,A,B,C,[ 1) ‘The final example concerns Boolean formulae. A Boolean formula is a term defined as follows: The constants true and false are Boolean formulae; if X and Y are Boolean formulae, XaY, and ~X, where v and are binary infix operators for disjunction and conjunction, respectively, and ~ tion, 83 Recursive Programming satisfiable(Formuta) ~ There Is a true instance of the Boolean formula Formula. satiefiable(srue) satiefiable(KAY) ~ gatiefiable(X), entisfiable(y) satisfiable(KVY) ~ satistiable(x) satisfiable(KvY) ~ satistiable(Y) satisfiable(~X) ~ invalid(X) invalid (Formula) ~ There is a false instance of the Boolean formula Formula. anvalid(ealee) snvalid(RVY) ~ iavalid(X), invalia@?) snvalidQAY) ~ invalid svalid (KAY) ~ invalid(Y) anvalid(-¥) — satistiable(¥). Program 3.32. Satisfiability of Boolean formulae A Boolean formula F is true if F="true’ F = Xa¥, and both X and ¥ are true. F = XvY, and either X or ¥ (or both) are true. F ~ ~X, and X is false. A Boolean formula F is false if F = 'false’. F = XaY, and either X or ¥ (or both) are false. F = XvY, and both X and Y are false. F = ~X, and X is true. Program 3.32 ts a logic program for determining the truth or falsity of a Boolean formula, Since it can be applied to Boolean formulae with variables, itis actually more powerful than it seems. A Boolean formula with variables is satisfiable if it has a true instance. It is invalid if it has a false instance. These are the relations computed by the program. 84 Chapter 3 w wi, cay 0) o wi 3.6 Background .5.1 Exercises for Section 3.5, Write a program to recognize if an arithmetic sum is normalized, that is, has the form A +B, where A is a constant and B is a normal- ized sum. Write a type definition for Boolean formulae. Write a program for recognizing whether a logical formula is in conjunctive normal form, namely, is a conjunction of disjunctions of literals, where a literal is an atomic formula or its negation. Write a program for the relation negation_invards(F1,,F2), which true if F2 is the logical formula resulting from moving all nega- tion operators occurring in the formula Fi inside conjunctions and disjunctions, Write a program for converting a logical formula into conjunctive normal form, that is, a conjunction of disjunctions. Consider the following representation of a bag, that is, a list of elements with multiplicities. The function symbol bag(Elenent., Multiplicity,Rest0fBag) 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; (©) Substitute for an element in a bag, (4) Convert a list into a bag; (e) Convert a binary tree into a bag Many of the programs in this chapter have been floating around the logic programming community, and their origins have become obscure. For Recursive Programming 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 et al, (1980), The latter book has been updated as Coelho and Cotta (1988) and a source for other simple examples. The exercise on describing poker hands is due to Ken Bowen. The classic reference for binary trees is Knuth (1968) and for sorting Knuth (1973) A discussion of the linear algorithm for the kth largest algorithms can be found in most textbooks on algorithms, for example, Horowitz and Sahni (1978). The discussion of the heap property is taken from Horowitz and Sahni (1978). Many of the basic programs for arithmetic and list processing have a simple structure that allows many correctness theorems to be proved automatically, see, for example, Boyer and Moore (1979) and Sterling and Bundy (1982). Ackermann's function is discussed by Peter (1967). 4 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, This chapter presents a general computation model of logic programs. The first section presents a unification algorithm that removes the gues 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. 88 Chapter + Recall that a term ¢ is a common instance of two terms, t, and b, if there exist substitutions 0 and @> such that ¢ equals t@; and 82. A term $ is more general than a term ¢ if t is an instance of s but s is not an instance of t. A term s is an alphabetic variant of a term t if both 5 is an instance of t and ¢ is an instance of s. Alphabetic variants are related by the renaming of variables that occur in the terms, For exam: ple, menber(X,tree (Left ,X,Right)) and menber(¥,tree(Left,Y,Z)) are alphabetic variants. A unifier of two terms is a substitution making the terms identical. If two terms have a unifier, we say they unify. There is a close relation be- ‘tween unifiers and common instances. Any unifier determines a common instance, and conversely, any common instance determines a unifier. For example, append((1,2,3] ,[3,4] ,List) and append([xiXs] ,Ys, [X\Zs]) unify. A unifying substitution is (X=1,Xs-[2,3], Ys=[3,41, List=[1)Zs} |, Their common instance, determined by this unifying sub- stitution, is append([1,2,3] , (3,41, [112s]) A most general unifier, or mgu, of two terms is a unifier such that the associated common instance is most general. It can be shown that if two terms unify, all mgus are equivalent. Making that statement precise is beyond the scope of this book, but we give pointers in Section 4.3. We proceed by giving an algorithm that computes a most general unifier of two terms if one exists The algorithm for unification presented here is based on solving equa tions, The input for the algorithm is two terms, 7; and >. The output of the algorithm is an mgu of the two terms if they unify, or failure if the terms do not unify. The algorithm uses a pushdown stack for storing the equations that need to be solved and a location, 8, for collecting the substitution comprising the output. The location @ is initially empty, and the stack is initialized to contain the equation T; = T:, The algorithm consists of a loop of popping an equation from the stack and processing it. The loop terminates when the stack becomes empty or if failure occurs in processing an invalid equation, We consider the possible actions for dealing with a popped equation S=T. The simplest case is if S and Tare identical constants or var lables. This equation is correct, and nothing further needs to be done. The computation continues by popping the next equation from the stack. 89 The Computation Model of Logic Programs If S tsa variable, and T is a term not containing S, the following hap. pens. The stack is searched for all occurrences of $, which are replaced by T. Similarly, all occurrences of $ in @ are replaced by T. Then the sub- stitution $= Tis added to 6. It is significant that 5 does not occur in The test embodied by the phrase “not containing” is known as the occurs check If T is a variable, and S is a term not containing 7, i. the occurs check with respect to 5, the symmetric sequence of actions happens. Equations are added to the stack if § and T are compound terms with the same principal functor and arity, f(S,...Sn) and f(Tij....T) Say For the terms to unify, each of the argument pairs must simultaneously unify. This is achieved by pushing the n equations, S, = T;, onto the stack. In any other case, failure is reported, and the algorithm terminates. If the stack is emptied, the terms unify, and the unifier can be found in @. The complete algorithm is given as Figure 4.1. The occurs check is embodied in the phrase “that does not occur in.” We do not prove the correctness of this algorithm, nor analyze its com- plexity, The interested reader is referred to the literature in Section 4.3, Consider attempting to unify the terms append([a,b] , [ed] Ls) and append [XIXs] ,¥s5 [X/2s]). The stack is initialized to the equation append([a,b], [c,d] ,Ls) = append((XIXs] ,Ys, [XIZs]) . These two terms have the same functor, append, and arity, 3, so we add the three equations relating the subterms of the two terms. These are [a,b]=[xiXs], [c,d] =¥s, and Ls=(X/Zs]. ‘The next equation, (a, b]=[Xixs], is popped from the stack. These two compound terms have the same functor, *.”, and arity, 2, so two equa- tions, a=X and [b]=% are added to the stack. Continuing, the equation a=X is popped. This Is covered by the second case in Figure 4.1. x is a variable not occurring in the constant, a. All occurrences of X in the stack are replaced by a. One equation is affected, namely Ls=[XIZs], which becomes Ls [a/Zs]. The equation X=a is added to the initially empty sub- stitution, and the algorithm continues. The next equation to be popped is [b] =Xs. Again this is covered by the second case. Xs=[b] is added to the set of substitutions, and the stack is checked for occurrences of Xs. There are none, and the next equation is popped. Chapter 4 Input Two terms 7; and T: to be unified Output 6, the mau of 7; and T,, of failure Algorithm: tnitialize the substitution @ te be empty, the stack to contain the equation T\ = T;, and failure to false. while stack not empty and no failure do op 2X isa Variable that does not oceur in ¥ substitute ¥ for X in the stack and in @ add X=¥ 100 Y is variable that does not aceur in X substitute X for ¥ in the stack and in 0 add Y= X00 X and ¥ ate identical constants ot variables: XS FAX woo oXwd aN YAS Fy. Yad for some functor f and n> 0: push X, = ¥,i=1--.m,om the stack from the stack otherwise, failure is true If failure, then output failure else output 0. Figure 4.1 A unification algorithm ‘The second case also covers [c,d]=¥s. Another substitution, Ys=Ce, dl, Is added to the collection, and the final equation, Ls=CaiZs], i popped. This is handled by the symmetric first case. Ls does not occur in [aiZs], so the equation is added as is to the unifier, and the algorithm terminates successfully. The unifier is (X=a,Xs=[b] , Ys=[c,d], Ls=LaiZs] }. The common instance produced by the unifier is append( [a,b] , [c,d] , [aiZs]). Note that in this unification, the substi- tutions were not updated. ‘The occurs check is necessary to prevent the unification of terms such as s(X) and X. There is no finite common instance of these terms. How: 91 The Computation Model of Logic Programs ever, most Prolog implementations omit the occurs check from the unifi- cation algorithm, for pragmatic reasons. When implementing this unification algorithm for a particular logic programming language, the explicit substitution in both the equations on the stack and the unifier is avoided. Instead, logical variables and other terms are represented by memory cells with different values, and variable binding is implemented by assigning to the memory cell representing @ logical variable a reference to the cell containing the representation of the term the variable is bound to. Therefore, Substitute Y for X in stack and in 8. Add X = ¥ to substitutions. is replaced by Make X a reference to Y. 4.1.1 Exercises for Section 4.1 (0) Use the algorithm in Figure 4.1 to compute an mgu of append(b] [c,d] ,L) and append( [XiXs] ,Ys, [X)Zs]). (i) Use the algorithm in Figure 4.1 to compute an mgu of hanoi(s(N) , AB,C.Ms) and hanoi (s(s(0)) ,a,b,c Xs). 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- grams. All the concepts introduced previously, such as goal reductions and computation traces, have thetr analogues in the full model. A computation of a logic program can be described informally as fol- ows. It starts from some initial (possibly conjunctive) query G and, if it terminates, has one of two results: success or failure. If a computation succeeds, the instance of G proved is conceived of as the output of the computation. A given query can have several successful computations, each resulting ina different output. In addition, it may have nontermi nating computations, to which we associate no result Chapter 4 ‘The computation progresses via goal reduction, At each stage, there is some resolvent, a conjunction of goals to be proved. A goal in the resol- vent and clause in the logic program are chosen such that the clause’s head unifies with the goal. The computation proceeds with a new resol- vent, obtained by replacing the chosen goal by the body of the chosen clause in the resolvent and then applying the most general unifier of the head of the clause and the goal. The computation terminates when the resolvent is empty. In this case, we say the goal is solved by the program. To describe computations more formally, we introduce some useful concepts. A computation of a goal Q = Qo by a program P is a (possibly infinite) sequence of triples (Q;,G,,C;). Qi is a (conjunctive) goal, goal occurring in Q,, and C, is a clause A~B,..,By in P renamed so that it contains new variable symbols not occurring in Q,,0 0, Quer is the result of replacing G, by the body of C; in Q,, and applying the substitution 6, the most general unifier of G; and A,, the head of C; or the constant true if Gis the only goal in Q, and the body of C, is empty; or the constant fail if Gy and the head of C, do not unify ‘The goals B,0, are said to be derived from G, and C,. A goal G) = Bu, where By occurs in the body of clause C,, is said to be invoked by G, and Cx Gis the parent of any goal it invokes. Two goals with the same parent goal are sibling goals A trace of a computation of a logic program (,,G,,C;) is the sequence of pairs (G,,0,), where 6; is the subset of the mgu 8, computed at the ith reduction, restricted to variables in G, We present an abstract interpreter for logic programs. It is an adap- tation of the interpreter for ground goals (Figure 1.1). The restriction to using ground instances of clauses to effect reductions is lifted, Instead, 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: vent. Care needs to be taken with the variables in rules to avoid name Clashes. Variables are local to a clause. Hence variables in different clauses that have the same name are, in fact, different. This is ensured by renaming the variables appearing in a clause each time the clause is chosen to effect a reduction. The new names must not include any of the variable names used previously in the computation. The 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 93 The Computation Model of Logic Programs Input: ‘A goal G and a program P Output: A instance of G that isa logical consequence of P, or no otherwise Algorithm: Initialize the resolvent to G. while the resolvent Is not empty do ‘choose a goal A from the resolvent choose a (renamed) clause A’ ~B,..yBy from P such that A andi A” unify with mgu 0 (Eno such goal and clause exist, exit the while loop) replace A by By. .By ifthe resolvent apply 0 to the resolvent and to G If the resolvent is empty, then output G, else output no. Figure 4.2 An abstract interpreter for logic programs instance of G if a proof of such an instance is found, or no if a failure has occurred during the computation. Note that the interpreter may also fail to terminate. An instance of a query for which a proof is found is called a solution 10 the query. The policy for adding and removing goals from the resolvent is called the scheduling policy of the interpreter. The abstract interpreter leaves the scheduling policy unspecified. Consider solving the query append({a,b], [c,d] Ls)? by Program 3.15 for append using the abstract interpreter of Figure 4.2. The resol vent is initialized to be append(Ta,b], [c,d] ,Ls). It is chosen as the goal to reduce, being the only one. The rule chosen from the program is, append((XIXs] ,¥s,[XIZs]) — append(Xs,¥s,2Zs) . The unifier of the goal and the head of the rule is {X-a,Xs=[b], ¢,d], Le=[alZs]}. A detailed calculation of this unifier appeared in the previous section. The new resolvent is the instance of ap- pend(Xs,¥s,Zs) under the unifier, namely, append ([b] , [ca] 25). Thi goal is chosen in the next iteration of the loop. The same clause for append is chosen, but variables must be renamed to avoid a clash of variable names. The version chosen is append([X1|Xst] ,Ys1, (X1|Zs1]) ~ append(Xst,¥s1,251). 4 Chapter 4 append( a,b], (e,a] Ls) ‘append (bl, [ey d] 28) append(( 1, [c,d 281) Ourput: Le=[a,b,eyd] Figure 4.3. Tracing the appending of two lists The unifier of the head and goal is {Xt~b, Xst=[ 1, Ystelc,dl, Zs=[blZs1]}. The new resolvent is append([ 1, {c,d] ,2s1). This time the fact append({ ],2s2,282) is chosen; we again rename variables as, necessary. The unifier this time is (Zs2[c,d], Zs1=[c,d]}. The new resolvent is empty and the computation terminates. ‘To compute the result of the computation, we apply the relevant part of the mgu’s calculated during the computation. The first unification instantiated Ls to [alZs]. Zs was instantiated to [biZs1] in the second unification, and Zs1 further became [c,d]. Putting it together, Ls has the value (a| [b!£c,d]]J, or more simply, [a,b,c,4 ‘The computation can be represented by a trace. The trace of the fore: going append computation Is presented in Figure 4.3. To make the traces clearer, goals are indented according to the indentation of their parent. A goal has an indentation depth of d+ if its parent has indentation depth 4, As another example, consider solving the query son(S,haran)? by Program 1.2, It is reduced using the clause son(X.Y) ~ father(Y.0), nale(i), A most general unifier is [X=8,Yeharan}. Applying the sub stitution gives the new resolvent father(haran,S), male(S). This is a conjunctive goal, There are two choices for the next goal to reduce. Choosing the goal father (haran,S) leads to the following computation. The goal, unifies with the fact father(haran, lot) in the program, and the computation continues with S instantiated to lot. ‘The new resolvent is maie(1ot), which is reduced by a fact in the program, and the compu- tation terminates. This is illustrated in the left trace in Figure 4.4. The other possibility for computing Sharan Is choosing to reduce the goal male(S) before father(haran,S). This goal is reduced by the fact male(1ot) with $ instantiated to 1ot. The new resolvent is fa~ ther (haran,1ot), which is reduced to the empty goal by the correspond ing fact. This is the right trace in Figure 4.4 The Computation Model of Logic Programs son(S,haran) son(S,haran) father(baran,S) Slot nale(3) Slot mala(lot) father(haran,lot) true true Figure 4.4 Different traces of the same solution Solutions to a query obtained using the abstract interpreter may con- tain variables. Consider the query member(a,Xs)? with respect to Pro: gram 3.12 for nenber. This can be interpreted as asking what list Xs has the element a as a member. One solution computed by the abstract inter preter is Xs*Cal¥s], namely, a list with a as its head and an unspecified ail. Solutions that Contain variables denote an infinity of solutions—all thelr ground instanc ‘There are two choices in the interpreter of Figure 4.2: choosing the goal to reduce, and choosing the clause to effect the reduction, These must be resolved in any realization of the computation model. The nature of the choices is fundamentally different. The choice of goal to reduce is arbitrary; it does not matter which chosen for the computation to succeed. If there is a successful computa. tion by choosing a given goal, then there is a successful computation by choosing any other goal. The two traces in Figure 4.4 illustrate two suc cessful computations, where the choice of goal to reduce at the second step of the computation differs. ‘The choice of the clause to effect the reduction is nondeterministic. Not every choice will lead to a successful computation. For example, in both traces in Figure 4.4, we could have gone wrong. If we had chosen to reduce the goal father (haran,) with the fact father(haran,yiscah), Wwe would not have been able to reduce the invoked goal male(yiscah) For the second computation, had we chosen to reduce maie(S) with male(isaac), the invoked goal father(haran, isaac) could not have been reduced. For some computations, for example, the computation illustrated in Figure 4.3, there is only one clause from the program that can reduce each goal. Such a computation is called deterministic, Deterministic com- putations mean that we do not have to exercise our nondeterministic imagination, The alternative choices that can be made by the abstract interpreter when trying to prove a goal implicitly define a search tree, as described Chapter 4 more fully in Section 5.4, The interpreter “guesses” a successful path in this 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 1s to search this tree breadth-first, that is, to explore all possible choices in parallel, This will guarantee that if there is a finite proof of the goal (ie, 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, Itis a deterministic 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 Gy = true or fail for some n= 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 nonterminating computa- tions. The query append (Ks, [c,d] ,¥s)? with respect to append can be reduced arbitrarily many times using the rule for append. In the proce Xs becomes a list of arbitrary length. This corresponds to solutions of the query appending [e,d] to an arbitrarily long list. The nonterminat- ing computation is illustrated in Figure 4.6. 97 ‘The Computation Model of Logic Programs hhanosiss(0)).a,b.cMs) hhanois(s(0),a,¢.b.Ms1) hhano\s(0).9.0,¢MS11) Ms} =fa to b| hhano\s(0)b.caMs12) Msi2=[b to ¢] appendila to bl{a t0 Gb 10 cLMsi)——_-Ms=la to BINS} appendi{ hla 10 cb t0 ch.Xs) Xsela to cb tod Inano\(ss(0),.¢,b,4.Ms2) hhanoi(s(),¢.b.Ms21) hhanoi(s(0)a,b.cs22) appencl({c to a},{c to ba to bh.Ms2) append I to ba to bL,Ys) appendilc to a,c to ba to bla 10 B,C 10a, Ms24[6 10 a} Ms22=Ia to bl to alYs} Yse[cto ba to bl to bya to bhMs) Ms-Ic to alZs} appendifc 10 ba to ja to By 10a, to ba to blzs) Zs-[e to bIZs1] appencila to bla to bye to a, 10 ba to bl2s1) append Ia to be 10a, sila to BIzs21 10 ba to b1Z82) Zs2{at0 be 10 a, ctobarob] Figure 4.5 Solving the Towers of Hanoi appendiXs,cdh,)s) Ns-ININSIL, VS=IXI¥SI1 appendixst {edlYs1) XsU={NIINS2), Y81=[X1/¥52] append(Xs2.{c,dh¥2) appendiXs3{c.dh,¥s3) Xs2-{N2INS3}, Y52=IN21YS3] SINS], Ys3=IN31YS4] Figure 4.6 A nonterminating computation 98 Chapter 4 All the traces presented so far have an important feature in common. If two goals G; and G, are invoked from the same parent, and G, appears before G; in the trace, then all goals invoked by G; will appear before G; in the trace, This scheduling policy makes traces easier to follow, by solving queries depth-first. The scheduling policy has another important effect: instantiating vari ables before their values are needed for other parts of the computation. A good ordering can mean the difference between a computation being deterministic or not. Consider the computation traced in Figure 4.5. The goal hanoi (s(s(s(0))) ,a,b,¢,s) is reduced to the following conjunction hanoi(s(s(0)),a,¢,b,Ms1), hanoi(s(s(0)),c,b,a,Ms2), append(Ms1, [a to b/Ns2),Ns) If the append goal is now chosen, the append fact could be used (incor- rectly) to reduce the goal. By reducing the two hanoi goals first, and all the goals they invoke, the append goal has the correct values for Ms1 and Ms2. 4.2.1 Exercises for Section 4.2 (Trace the query sort([3,1,2],%s)? using the permutation sort (3.20), insertion sort (3.21), and quicksort (3.22) programs in turn, (ii) Give a trace for the goal derivative (3+sin(x)~44c0s(x) ,x,D) using Program 3,30 for derivative. (ili) Practice tracing your favorite computations 4.3 Background Unification plays a central role in automated deduction and in the use of logical inference in artifical intelligence. It was first described in the landmark paper of Robinson (1965). Algorithms for unification have been 99) The Computation Model of Logie Programs the subject of much investigation: see, for example, Martelli and Monta- nari (1982), Paterson and Wegman (1978), and Dwork et al. (1984). Typ cal textbook descriptions appear in Bundy (1983) and Nilsson (1980) The definition of unification presented here is nonstandard, Readers wishing to learn more about unifiers are referred to the definitive dis- cussion on unification in Lassez, Maher, and Marriott (1988). This paper points out inconsistencies of the various definitions of unifiers that have been proposed in the literature, including the version in this book, Es sentially, we have explained unifiers based on terms to avold technical 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 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 given in Section 6.3. Another bias of our computation model is the central place of unifi cation. An exciting development within logie programming has been the realization that unification is just one instance of constraint solving. New computation models have been presented where the solution of equal- ity constraints, ic. unification, in the abstract interpreter of Figure 4.2 s replaced by solving other constraints. Good starting places to read about the new constraint-based models are Colmerauer (1990), Jaffar and Lassez (1987), and Lasse7 (1991) A proof that the choice of goal to reduce from the resolvent is arbitrary can be found in Apt and van Emden (1982) or in the text of Lloyd (1987) A method for replacing the runtime occurs check with compile-time analysis Was suggested by Plaisted (1984). Attempts have been made to make unification without the occurs check more than a necessary expedient for practical implementations of Prolog. In particular, Colmerauer (1982b) proposes a theoretical model for such unifications that incorporates computing with infinite terms, A novel use of unification without the occurs check appears in Eggert and Chow (1983), where Escher-like drawings that gracefully tend to in- finity are constructed, 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, which 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: matical logic with experience from computer science and engineering. Giving a complete account is way beyond the scope of this book. In this 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. 102 Chapter 5 parent (terach abraham). parent(abrahaa,ieaac) parent (isaac, jacob) arent (jacob, bonjanin) ancestor (X,Y) ~ parent (X,Y) ancestor(%,2) — parent (X,Y), ancestor(¥,2) Program 5.1 Yet another family example ‘The operational semantics is a way of describing procedurally the meaning of a program. The operational meaning of a logic program P is the set of ground goals that are instances of queries solved by P using the abstract interpreter given in Figure 4.2. This is an alternative for- mulation of the previous semantics, which defined meaning in terms of logical deduction. The declarative semantics of logic programs is based on the standard ‘model-theoretic semantics of first-order logic. In order to define it, some new terminology is needed Definition Let P be a logic program. The Herbrand universe of P, denoted U(P), is the set of all ground terms that can be formed from the constants and function symbols appearing in P. . In this section, we use two running examples—yet another family data base example, given as Program 5.1; and Program 3.1 defining the natural numbers, repeated here natural _nunber(0) natural_number(s(X)) — natural_number(X) The Herbrand universe of Program 5.1 is the set of all constants appear- ing in the program, namely, {terach, abraham, isaac, jacob, benjamin} If there are no function symbols, the Herbrand universe is finite. In Pro- gram 3.1, there is one constant symbol, 0, and one unary function sym- bol, s. The Herbrand universe of Program 3.1 is [0,5(0),8(8(0)), } If no constants appear in a program, one is arbitrarily chosen Definition The Herbrand base, denoted B(P), is the set of all ground goals that can be formed from the predicates in P and the terms in the Herbrand universe. . 103 Theory of Logic Programs There are two predicates, parent /? and ancestor/2, in Program 5.1 The Herbrand base of Program 5.1 consists of 25 goals for each predi- cate, where each constant appears as each argument: {parent (terach,terach), parent (terach, abraham), parent(terach,isaac), parent (terach, jacob), parent (terach, benjamin), parent (abraham, terach), parent (abrahan,abraham), parent (abraham, isaac), parent (abraham, jacob), parent (abraham, benjamin) , parent (isaac,terach), parent (isaac,abrahan), parent(isaac, isaac), parent (isaac, jacob), parent (isaac, benjamin), parent (jacob, terach) , parent(jacob, abraham), parent (jacob, isaac), parent (jacob, jacob), parent (jacob,benjamin), parent (benjamin, terach), parent (benjamin,abrahan) , parent (benjamin, isaac), parent (benjamin, jacob), parent(benjamin, benjamin), ancestor(terach,terach) , ancestor (terach,abraham), ancestor(terach, isaac), ancestor (terach, jacob), ancestor (terach, benjamin), ancestor (abraham, terach), ancestor(abraham,abrahar), ancestor (abraham, ieaac), ancestor (abraham, jacob) , ancestor (abraham, benjamin), ancestor(isaac,terach), ancestor (isaac,abraham), ancestor (isaac, isaac), ancestor(isaac, jacob), ancestor (isaac, benjamin), ancestor (jacob,terach), ancestor (jacob, abraham), ancestor (jacob, isaac), ancestor (jacob, jacob) , ancestor (jacob, benjamin), ancestor (benjamin, terach), ancestor (benjamin, abraham), ancestor (benjamin, isaac), ancestor(benjamin, jacob), ancestor(benjamin, benjamin) } The Herbrand base is infinite if the Herbrand universe is. For Pro- gram 3.1, there is one predicate, natural_number. The Herbrand base equals {natural_number (0) ,natural_number(s(0)),... } Definition An interpretation for a logic program is a subset of the Herbrand base. An interpretation assigns truth and falsity to the elements of the Her- brand base. 4 goal in the Herbrand base is true with respect to an inter pretation if it is a member of it, false otherwise. 104 Chapter 5 Definition An interpretation J is a model for a logic program if for each ground instance of a clause in the program A~Bj,....By, A is in Lif Bj,-...Bn are in 1. . Intuitively, models are interpretations that respect the declarati reading of the clauses of a program. For Program 3.1, natural_number(0) must be in every model, and natural_number(s(X)) is in the model if natural_number(X) is. Any ‘model of Program 3.1 thus includes the whole Herbrand base For Program 5.1, the facts parent (terach,abraham), parent (abra~ ham,isaac), parent(isaac, jacob), and parent (jacob,benjamin) must be in every model. A ground instance of the goal ancestor (X,¥) is in the model if the corresponding instance of parent (X,¥) ts, by the first clause. So, for example, ancestor (terach, abraham) is in every model By the second clause, ancestor(X,2) isin the model if parent (x,¥) and ancestor(¥,2) are It is easy to see that the intersection of two models for a logic program. Ps again a model. This property allows the definition of the intersection of all models. Definition The model obtained as the intersection of all models is known as the ‘minimal model and denoted M(P), The minimal model is the declarative ‘meaning of a logic program. . The declarative meaning of the program for natural nunber, its min: imal model, is the complete Herbrand base {natural_nunber (0) ,natu- ral_number(s(0)) natural mumber(s(s(0))),... The declarative meaning of Program 5.1 is {parent (terach ,abrahan) , parent(abraham,isaac), parent (isaac, jacob), parent (jacob, benjamin), ancestor(terach,abraham), ancestor (abraham, isaac), ancestor(isaac. jacob), ancestor(jacob,benjamin), ancestor (verach,isaac), ancestor(terach, jacob), ancestor (terach, benjamin), ancestor(abrahan,jacob), ancestor (abraham, ben- jamin), ancestor(isaac,benjamin)} Let us consider the declarative meaning of append, defined as Pro gram 3.15 and repeated here: append( [x|Xs] ,Ys, [|Zs]) — append(Xs,¥s,Zs) append({ J, Ys, Ys) 105 Theory of Logic Programs The Herbrand universe is [}{{ Ill Jl... namely, all lists that can be built using the constant [ J. The Herbrand base is all combinations of lists with the append predicate, The declarative meaning is all ground in- stances of appond([ J,Xs,Xs), that is, append(C J, 3.01), append(£ 1,001), £0 J), .-., together with goals such as append (CC13,C1,C0 13), which are logically implied by application(s) of the rule, This is only a subset of the Herbrand base. For example, append 1,[ 1, [0 1]) is not in the meaning of append but is in the Herbrand base Denotational semantics assigns meanings to programs based on asso- ciating with the program a function over the domain computed by the program. The meaning of the program is defined as the least fixpoint of the function, if it exists. The domain of computations of logic programs 1s interpretations Definition Given a logic program P, there is a natural mapping ‘Tp from interpreta~ tions to interpretations, defined as follows Toll) = [A in BPA ~By,Bs,....By, 2 > 0,18 a ground instance of a clause in P, and B,...,By are in I) . The mapping is monotonic, since whenever an interpretation I is con: tained in an interpretation J, then Tp(/) is contained in Ty() This mapping gives an alternative way of characterizing models. An interpretation I is a model if and only if Tp{d) is contained in Besides being monotonic, the transformation is also continuous, a no- tion that will not be defined here. These two properties ensure that for every logic program P, the transformation Tp has a least fixpoint, which is the meaning assigned to P by its denotational semanti Happily, all the different definitions of semantics are actually d ing the same object. The operational, denotational, and declarative se- mantics have been demonstrated to be equivalent. This allows us to de: fine the meaning of a logic program as its minimal model. Program Correctness Every logic program has a well-defined meaning, as discussed in Sec tion 5.1. This meaning is neither correct nor incorrect. 106 Chapter 5 The meaning of the program, however, may or may not be what was intended by the programmer, Discussions of correctness must therefore 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 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 ‘M(P) Is contained in M. A program P is complete with respect to an in- tended meaning if M is contained in M(P). A program ts thus correct and. complete with respect to an intended meaning if the two meanings coin- cide exactly Another important aspect of a logic program is whether it terminates. Definition 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 Aisin Das well, . Definition A termination domain of a program P is a domain D such that every computation of P on every goal in D terminates: . Usually, a useful program should have a termination domain that in- cludes its intended meaning. However, since the computation model of logic programs is liberal in the order in which goals in the resolvent can bbe reduced, most interesting logic programs will not have interesting ter mination domains. This situation will improve when we switch to Prolog. The restrictive model of Prolog allows the programmer to compose non trivial programs that terminate over useful domains Consider Program 3.1 defining the natural numbers. This program is terminating over its Herbrand base. However, the program is nonter minating over the domain {natural number (X)}. This is caused by the possibility of the nonterminating computation depicted in the trace in Figure 5.1 For any logic program, it is useful to find domains over which it is terminating, This is usually difficult for recursive logic programs, We 107 Theory of Logic Programs atural_nunber (10) ¥es(X1) atural_nunber (X1) Xie (x2) ratural_number (2) X2e8(X3) Figure 3.1 A nontermit ing computation need to describe recursive data types in a way that allows us to discuss termination. Recall that a type, introduced in Chapter 3, is a set of terms, Definition A type is complete if the set 1s closed under the instance relation. With every complete type T we can associate an incomplete type IT, which is the set of terms that have instances in T and instances not in T . We illustrate the use of these definitions to find termination domains for the recursive programs using recursive data types in Chapter 3. Spe- cific instances of the definitions of complete and incomplete types are given for natural numbers and lists, A (complete) natural number is el: ther the constant 0, or a term of the form s(X). An incomplete natural number is either a variable, X, or a term of the form s"(0), where X is a variable. Program 3.2 for = is terminating for the domain consisting, of goals where the first and/or second argument is a complete natural number. Definition A list is complete if every instance satisfies the definition given in Pro- gram 3.11. A list 1s incomplete if there are instances that satisfy this definition and instances that do not. . For example, the list [a,b,c] is complete (proved in Figure 3.3), while the variable X is incomplete. Two more interesting examples: [a,X,c] is a complete list, although not ground, whereas [a,b/Xs) is incomplete, A termination domain for append is the set of goals where the first and/or the third argument Is a complete list. We discuss domains for other list-processing programs in Section 7.2, on termination of Prolog programs. 108 5.3. Complexity Chapter 5 5.2.1 Exercises for Section 5.2 (Give a domain over which Program 3.3 for plus is terminating. (i) Define complete and incomplete binary trees by analogy with the definitions for complete and incomplete lists. We have analyzed informally the complexity of several logic prograr for example, < and plus (Programs 3.2 and 3.3) in the section on arith- metic, and append and the two versions of reverse in the section on lists (Programs 3.15 and 3.16). In this section, we briefly describe more formal complexity measures. The multiple uses of logic programs slightly change the nature of com: plexity measures. Instead of looking at a particular use and specifying complexity in terms of the sizes of the inputs, we look at goals in the meaning and see how they were derived. 4 natural measure of the com: plexity of a logic program is the length of the proofs it generates for goals in its meaning Definition The size of a termis the number of symbols in its textual representation, Constants and variables, consisting of a single symbol, have size 1 The size of a compound term ts 1 more than the sum of the sizes of its arguments. For example, the list [b] has size 3, [a,b] has size and the goal append( [a,b] , [c,d] ,Xs) has size 12. In general, a list of nelements has size 2+ 1 Definition 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 equal to Ln, . Length complexity is related to the usual complexity measures in com- puter science. For sequential realizations of the computation model, it corresponds to time complexity. Program 3.15 for append has linear 109 Theory of Logic Programs length complexity. This is demonstrated in Exercise (i) at the end of this section, The applicability of this measure to Prolog programs, as opposed to logic programs, depends on using a unification algorithm without an oc- curs check. Consider the runtime of the straightforward program for ap- 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([XiXs] ,¥s, [XiZs]). At least three unifications, matching vari- ables against (possibly incomplete) lists, will be necessary. If the occurs check must be performed for each, the argument lists must be searched. This is directly proportional to the size of the input goal. However, if the ‘occurs check is omitted, the unification time will be bounded by a con: stant. The overall complexity of append becomes quadratic in the size of the input lists with the occurs check, but only linear without it. We introduce other useful measures related to proofs. Let R be a proof. We define the depth of R to be the deepest invocation of a goal in the associated reduction. The goal-size of Ris the maximum size of any goal reduced. Definition 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 less than or equal to G(r. . Definition A logic program P is of depth-complexity Din) if for any goal A in the meaning of P of size n, there is a proof of G with respect to P of depth LS © Figure 5.2. Two search trees reduction is the first goal. The edges are labeled with substitutions that are applied to the variables in the leftmost goal. These substitutions are computed as part of the unification algorithm. Search trees correspond closely to traces for deterministic computa- tons, The traces for the append query and hanoi query given, respec tively, in Figures 4.3 and 4.5 can be easily made into search trees. This is Exercise (i) at the end of this section. Search trees contain multiple success nodes if the query has mul- tiple solutions. Figure 5.3 contains the search tree for the query ap- pend(As,Bs, [2,b,c])? with respect to Program 3.15 for append, asking to split the list [a,b,¢] into two. The solutions for As and Bs are found by collecting the labels of the edges in the branch leading to the success node. For example, in the figure, following the leftmost branch gives the solution {As=[a,b,cl,Bs*C 1} The number of success nodes goal with respect to a program. Search trees can have infinite branches, which correspond to nonter- minating computations. Consider the goal append (Ks, [c,d) ,Ys) with respect to the standard program for append. The search tree is given in Figure 5.4. The infinite branch is the nonterminating computation given in Figure 46. is the same for any search tree of a given M2 Chapter 5 ‘ppend(As,Bs,(a,b.c] i) {As2=[),8s-lc)) : ©) Figure 5.3. Search tree with multiple success nodes Complexity measures can also be defined in terms of search trees. Pro- log programs perform a depth-first traversal of the search tree. There fore, measures based on the size of the search tree will be a more real- istic measure of the complexity of Prolog programs than those based on 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 search trees is the relation between nondeterministic computations and deterministic computations. Whether the complexity classes defined via proof trees are equivalent to complexity classes defined via search trees is a reformulation of the classic P=NP question in terms of logic program: ming. 5.4.1 Exercises for Section 5.4 (Transform the traces of Figure 4.3 and 4.5 into search trees (i) Draw a search tree for the query sort ([2,4,1] ,Xs)? using permu tation sort Theory of Logic Programs ‘append(xs,c,d), V5 rs) Xs], ¥sepAl¥sH < true) a Figure 5.4 Search tree with an infinite branch Negation in Logic Programming Logic programs are collections of rules and facts describing what is true. Untrue facts are not expressed explicitly; they are omitted. When writing rules, it is often natural to include negative conditions. For example, defining a bachelor as an unmarried male could be written as bachelor(X) — male(X), not married(X) if negation were allowed. In this section, we describe an extension to the logic programming computation model that allows a limited form of negation, Researchers have investigated other extensions to logic programming 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 We define a relation not. G and give a semantics. The essence of logic programming is that there is an efficient procedural semantics. There ts a natural way to adapt the procedural semantics to negation, namely by negation as failure. A goal G fails, (not. G succeeds), if 6 cannot be derived by the procedural semantics. nda Chapter 5 The relation not G is only a partial form of negation from first-order logic. The relation not uses the negation as failure rule. A goal not. G will be assumed to be a consequence of a program P if G is not a consequence of. Negation as failure can be characterized in terms of search trees, Definition A search tree of a goal G with respect to a program P is finitely failed if it has no success nodes or infinite branches. The finite failure set of a logic program P is the set of goals G such that G has a finitely failed search, tree with respect to P. . A goal not G is implied by a program P by the “negation as failure” rule if Gis in the finite failure set of P Let us see a simple example. Consider the program consisting of two facts: Likes (abraham ,pomegranates) Likes (isaac, ponegranates) ‘The goal not 1ikes(sarah, pomegranates) follows from the program by negation as failure. The search tree for the goal Likes (sarah, ponegran- ates) has a single failure node Using negation as failure allows easy definition of many relations, For example, a declarative definition of the relation disjoint (Xs, Ys) that two lists, Xs and Ys, have no elements in common is possible as follows. disjoint (Xs,Ys) — not (member(X,Xs), member (X,¥s)) ‘This reads: “Xs is disjoint from Ys if there is no element X that is a member of both Xs and Ys." ‘An intuitive understanding of negation as failure is fine for the pro- grams in this book using negation. There are semantic problems, how- ever, especially when integrated with other issues such as completeness and termination. Pointers to the literature are given in Section 5.6, and Prolog's implementation of negation as failure is discussed in Chap- ter I Theory of Logic Programs 5.6 Background ‘The classic paper on the semantics of logic programs is of van Emden and Kowalski (1976). Important extensions were given by Apt and van Emden (1982). In particular, they showed that the choice of goal to re- duce from the resolvent is arbitrary by showing that the number of suc cess nodes is an invariant for the search trees. Textbook accounts of the theory of logic programming discussing the equivalence between the declarative and procedural semantics can be found in Apt (1990), Deville (1990), and Lloyd (1987) 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, the product of length and goal-size is linearly related to alternating tree- ize, and the product of depth and goal-size is linearly related to alter nating time. The classic name for search trees in the literature is SLD trees, The name SLD was coined by research in automatic theorem proving, which preceded the birth of logic programming. SLD resolution is a particu- lar refinement of the resolution principle introduced in Robinson (1965) Computations of logic programs can be interpreted as a series of reso- lution steps, and in fact, SLD resolution steps, and are still commonly described thus in the literature. ‘The acronym SLD stands for Selecting a literal, using a Linear strategy, restricted to Definite clauses. The first proof of the correctness and completeness of SLD resolution, albeit under the name LUSH-resolution, was given by Hill (1974), ‘The subject of negation has received a large amount of attention and interest since the inception of logic programming. The fundamental work on the semantics of negation as failure ts by Clark (1978). Clark's results, establishing soundness, were extended by Jaffar et al. (1983), who proved the completeness of the rule. ‘The concept of negation as failure is a restricted version of the closed world assumption as discussed in the database world. For more infor- mation see Reiter (1978). There has been extensive research on charac: terizing negation in logic programming that has not stabilized. at this time, The reader should look up the latest logic programming conference proceedings to find current thinking. A good place to start reading to un- derstand the issue is Kunen (1989). II 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 how Prolog, the most developed language based on logic programming, han- dles each of these issues. 6 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 poss!- ble such realization. However, itis 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, 120 Chapter 6 Several logic programming languages exist, reflecting different choices Prolog and its extenstons (Prolog-ll, IC-Prolog, and MU-Prolog, for exam- ple) are based on sequential execution. Other languages, such as PAR- 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 tween Prolog and its extensions is in the choice of goal to reduce. Protog’s execution mechanism is obtained from the abstract interpreter by choosing the leftmost goal instead of an arbitrary one and replacing the non- deterministic choice of a clause by sequential search for a unifiable clause and backtracking. In other words, Prolog adopts a stack scheduling policy, It maintains 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 choice of reducing clause by sequential search and backtracking. When 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 A computation of a goal G with respect to a Prolog program P is the generation of all solutions of G with respect to P. In terms of logic programming concepts, a Prolog computation of a goal complete depth-first traversal of the particular search tree of G obtained by always choosing the leftmost goal. Many different Prolog implementations exist with differing syntax and programming facilities. Recently, there has been an attempt to reach a Prolog standard based on the Edinburgh dialect of Prolog. At the time of writing, the standard has not been finalized. However a complete draft exists, which we essentially follow. We refer to the Prolog described in that document as Standard Prolog. The syntax of logic programs that ‘we have been using fits within Standard Prolog except that we use some characters not available on a standard keyboard. We give the standard equivalent of our special characters. Thus :- should be used instead of in Prolog programs to separate the head of a clause from its body Al the programs in this book run (possibly with minor changes) in all Edinburgh-compatible Prologs. A trace of a Prolog computation is an extension of the trace of a com: putation of a logic program under the abstract interpreter as described Pure Prolog father(abraham,icaac). male(isaac) father(haran,1ot) ate(iot) father(haran,milcah). female(yiscah) father(haran,yiscah). female (milcah) eon(X,¥) ~ father(¥,1), male() daughter(X,) ~ father(¥,X), fenale( son(X,haran)? father (haran,X) zale(iot) true Output: xer0% fachor(naren,X) Kemileah male(ailcah) father haran,X) ‘scah male(yiscah) ro (more) solutions Figure 6.1. Tracing a simple Prolog computation, in Section 4.2. We revise the computations of Chapters 4 and 5, indicat ing the similarities and differences. Consider the query son(X,haran)? with respect to Program 1.2, biblical family relationships, repeated at the top of Figure 6.1. The computation is given in the bulk of Figure 6.1. It corresponds to a depth-first traversal of the first of the search trees in Figure 5.2, It is an extension of the first trace in Figure 4.4, since the whole search tree is searched, The notation previously used for traces must be extended to handle failure and backtracking, An f after a goal denotes that a goal fails, that is there is no Clause whose head unifies with the goal. The next goal af- ter a failed goal is where the computation continues on backtracking. It already appears as a previous goal in the trace at the same depth of indentation and can be identified by the variable names. We adopt the Edinburgh Prolog convention that a *;” typed after a solution denotes a continuation of the computation to search for more solutions. Unifica- tions are indicated as previously Trace facilities and answers provided by particular Prolog implementa: tions vary from our description. For example, some Prolog implementa: tions always give all solutions, while others wait for a user response after each solution. Chapter 6 append((XIXs) Ys, 1251) ~ append(xe,¥s,28) append(( 1,¥e,¥5) append(xs,¥s, [a,b,c1) alXet] append (Xst Ys, [b,¢]) Xs1=[b1521 append (Xe2,¥8, [c]) Ke2=(e|Xe3] append(Xs3,¥s, ( 1) Xs2e ],Ye=L 1 true Output (Xs append (xs2, 9, [e]) Ke" 1, Yerlel rue Output: (ta*(a,8),Yet0) ) append(Xst,¥s,[b,c)) : Xsi=( ],Yse[b,c] true Output: (seta append(Xs,Ys, [2,b,<1) 1, YeeLa,bycl true Output; (Ke 1,¥e=La,b,<1) no (more) solutions Figure 6.2. Multiple solutions for splitting a list, ‘The trace of append fa,b] , [c,4] ,Ls)? giving the answer Ls=[a,b,c, 4] is precisely the trace given in Figure 4.3. Figure 4.5, giving the trace for solving the Towers of Hanoi with three disks, is also a trace of the hanoi program considered as a Prolog program solving the query hanoi (s(s(s(0))),a,b,c.Ms)?. The trace of a deterministic computa: tion is the same when considered as a logic program or a Prolog program, provided the order of goals is preserved. The next example is answering the query append(Xs, Ys, a,b,c])? with respect to Program 3.15 for append. There are several solutions of the query. The search tree for this goal was given as Figure 5.3. Figure 6.2 ives 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 quicksort are essentially deterministic and show the algorithmic behavior of a Prolog program. Figure 6.3 gives a trace of the query quicksort ([2,1,3] ,Xs)?. Arithmetic comparisons 123 Pure Prolog quicksort((f1%s],Ys) ~ partition(Ke,%,Littios Bigs), quicksort(Litties,Ls), quickeort(Bige.Be), appendiLs, [£1Be] ,6) quicksort(1.01) parcition((tIXs],¥, (CIL],Bs) ~ XS ¥, partition(Xs,¥,Ls,Bs) pareition((XIXe] ,¥,Ls, [X1Be]) X> ¥, partition(ts,¥,Ls,Bs) pareition(( L.Y,01,0)- quicksors((2,3,31 ,Qs) partition({1,3] ,2,Ls,Bs) 1s2 partition( (3) ,2,Ls1,Bs) 3s2 1 partition([3] ,2,Le1,B2) 3>2 partition({ 1,2,Lst,8s1) quicrsor+( [1] gst) partition([ 1,1,Ls2,Bs2) quicksore({ 1,962) quicksort(T 1,053) append(( 1, £11,921) quicksort( [3] 464) partition(( 1,3,L83,Be3) quicksors({ 1,985) quicksort({ 1,086) append 1, (31,084) append (11 , (2,31 ,98) append(( 1, (2,31 ,¥s) true Output: (as (2,2,31) Lse(sitat] Lete(sizea) Bse(316611 Latel IeBat Ls2L 1=Bs2 620) 53-0 1 Qst=(11 Ls3eL 1=Be3 Qes=E 1 as6-C 1 Qs4=(3) Qs=(11¥s) Ys (2,3) Figure 6.3. Tracing a quicksort computation 124 Chapter 6 are assumed to be unit operations, and the standard program for append is used We introduce a distinction between shallow and deep backtracking. 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 control returns to another goal in the computation tree. It is sometimes convenient to include, for the purpose of this defint tion, test predicates that occur first in the body of the clause as part of unification, and to classify the backtracking that occurs as a result of their failure as shallow. An example in Figure 6.3 is the choice of a new Clause for the goal part.ition([3] ,2,Ls1,Bs) 6.1.1 Exercises for Section 6.1 (i) Trace the execution of daughter(x,haran)? with respect to Pro: gram 1.2 (ii) Trace the execution of sort ({3,1,2),Xs)? with respect to Pro- gram 3.21 (ii) Trace the execution of sort ([3,1,2],Xs)? with respect to Pro- gram 3.20. 6.2 Comparison to Conventional Programming Languages A programming language is characterized by its control and data ma- nipulation mechanisms. Prolog, as a general-purpose programming lan- guage, can be discussed in these terms, as are conventional languages. In this section, we compare the control flow and data manipulation of Prolog to that of Algol-like languages. ‘The control in Prolog programs is like that in conventional procedural languages as long as the computation progresses forward. Goal invoca: tion corresponds to procedure invocation, and the ordering of goals in the body of clauses corresponds to sequencing of statements. Specifi- cally, the clause A ~ B,,....By can be viewed as the definition of a pro: cedure A as follows: Pure Prolog procedure A call Bi, call call Bn, end. Recursive goal invocation in Prolog is similar in behavior and imple- mentation to that of conventional recursive languages. The differences show when backtracking occurs. In a conventional language, if a compu- tation cannot proceed (e.g. all branches of a case statement are false), @ runtime error occurs. In Prolog, the computation is simply undone to the last choice made, and a different computation path is attempted. ‘The data structures manipulated by logic programs, terms, correspond to general record structures in conventional programming languages. The handling of data structures is very flexible in Prolog. Like Lisp, Prolog is a declaration-free, typeless language. The major differences between Prolog and conventional languages in the use of data structures arise from the nature of logical variables. Log- ical variables refer to individuals rather than to memory locations. Con sequently, having once beed specified to refer to a particular individual, 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 cation algorithm. Unification subsumes ® Single assignment = Parameter passing *# Record allocation ® Read/write-once field-access in records We discuss the trace of the quicksort program in Figure 6.3, point: ing out the various uses of unification. The unification of the initial goal quicksort([2,1,3] Qs) with the head of the procedure definition quicksort([X|Xs] ,s) illustrates several features. The unification of [2,1,3] with the term [Xs] achieves record access to the list and also selection of its two fields, the head and tail Chapter 6 The unification of [1,3] with Xs achieves parameter passing to the partition procedure, because of the sharing of the variables. This gives the first argument of partition. Similarly, the unification of 2 with % passes the value of the second parameter to partition. Record creation can be seen with the unification of the goal parti- tion( [1,3] ,2.Ls,Bs) with the head of the partition procedure parti~ tion( [1X8] ,Z,(X1Ls1],Bs1). As a result, Ls is instantiated to [1ILs1]. Specifically, Ls is made into a list and its head ts assigned the value 1, namely, record creation and field assignment via unifica~ tion. The recursive algorithm embodied by the quicksort program can be easily coded in a conventional programming language using linked lists and pointer manipulation. As discussed, unification is achiev- ing the effect of the necessary pointer mantpulations. Indeed, the ma- nipulation of logical variables via unification can be viewed as an abstraction of low-level manipulation of pointers to complex data structures These analogies may provide hints on how to implement Prolog effi- ciently on avon Neumann machine. Indeed, the basic idea of compilation of Prolog is to translate special cases of unification to conventional mem- ory manipulation operations, as specified previously Conventional languages typically incorporate error-handling or excep: tion-handling mechanisms of various degrees of sophistication. Pure Pro log does not have an error or exception mechanism built into its defi- nition, The pure Prolog counterparts of nonfatal errors in conventional programs, ¢.g,, a missing case in a case statement, or dividing by zero, cause failure in pure Prolog. Full Prolog, introduced In the following chapters, includes system predicates, such as arithmetic and 1/0, which may cause errors. Current Prolog implementations do not have sophisticated error handling mechanisms. Typically, on an error condition, a system pred- icate prints an error message and either fails or aborts the computa tion. This brief discussion of Prolog’s different way of manipulating data does not help with the more interesting question: How does program: ming in Prolog compare with programming in conventional program: ming languages? That is the major underlying topic of the rest of this book. 127 Pure Prolog 6.3. Background ‘The origins of Prolog are shrouded in mystery. All that is known is that the two founders, Robert Kowalski, then at Edinburgh, and Alain Colmer: auer at Marseilles worked on similar ideas during the early 1970s, and even worked together one summer. The results were the formulation of the logic programming philosophy and computation model by Kowalski (1974), and the design and implementation of the first logic program- ming language Prolog, by Colmerauer and his colleagues (1973). Three recent articles giving many more details about the beginnings of Prolog and logic programming are Cohen (1988), Kowalski (1988), and Colmer: auer and Roussel (1993). ‘A major force behind the realization that logic can be the basis of a practical programming language has been the development of efficient implementation techniques, as pioneered by Warren (1977). Warren's compiler identified special cases of unification and translated them into efficient sequences of conventional memory operations. Good accounts of techniques for Prolog implementation, both interpretation and compi- lation, can be found in Maier and Warren (1988) and Ait-Kaci (1991). Variations of Prolog with extra control features, such as IC-Prolog. (Clark and McCabe, 1979), have been developed but have proved too costly in runtime overhead to be seriously considered as alternatives to Prolog. We will refer to particular interesting variations that have been proposed in the appropriate sections Another breed of logic programming languages, which indirectly emerged from IC-Prolog, was concurrent logic languages. The first was the Relational Language (Clark and Gregory, 1981), followed by Concur- rent Prolog (Shapiro, 1983b), PARLOG (Clark and Gregory, 1984), GHC (eda, 1985), and a few other proposals. References for the variations mentioned in the text are, for Prolog: It (van Caneghem, 1982), IC-Prolog (Clark et al, 1982), and MU-Prolog (Naish, 1986). Aurora-Prolog is described in Disz et al. (1987), while a starting place for reading about AKL, a language emerging from Andorra- Prolog is Janson and Haridi (1991). The syntax of Prolog stems from the clausal form of logic due to Kowalski (1974). The original Marseilles interpreter used the terminol ogy of positive and negative literals from resolution theory. The clause A= By... By Was written +A — By» ~ By, Chapter 6 David H. D. Warren adapted Marseilles Prolog for the DEC-10 at the Unt versity of Edinburgh, with help from Fernando Pereira. Their decisions have been very influential. Many systems adopted most of the conven tions of Prolog-10 (Warren et al., 1979), which has become known more generically as Edinburgh Prolog, Its essential features are described in the widespread primer on Prolog (Clocksin and Mellish, 1984). This book 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. 7 Programming in Pure Prolog A major aim of logic programming is to enable the programmer to pro- gram at a higher level. Ideally one should write axioms that define the desired relations, maintaining ignorance of the way they are going 10 be used by the execution mechanism. Current logic programming lan- guages, Prolog in particular, are still far away from allowing this 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, 130 Chapter 7 parent(terach abraham). parent (abraham, isaac) parent (isaac, jacob) parent jacob, benjamin) ancestor(1,¥) ancestor (X,2) ~ parent (X,Y) - parent (X,Y), ancestor(¥,2) Program 7.1 Yet another family example The rule order determines the order in which solutions are found. Changing the order of rules in a procedure permutes the branches in any search tree for a goal using that procedure. The search tree is traversed depth-first. So permuting the branches causes a different order of traversal of the search tree, and a different order of finding solutions. The effect is clearly seen when using facts to answer an existential query. With our biblical database and a query such as father (X,¥)?, changing the order of facts will change the order of solutions found by Prolog Deciding how to order facts is not very important. The order of solutions of queries solved by recursive programs is also determined by the clause order. Consider Program 5.1, a simple bibli: cal database together with a program for the relationship ancestor, re- peated here as Program 7.1 For the query ancestor(terach, x)? with respect to Program 7.1, the solutions will be given in the order, Xeabrahar, Xeisaac, X=jacob, and Xebenjamin, If the rules defining ancestor are swapped, the solutions will appear in a different order, namely, X-benjamin, X=jacob, and X-abrahan, The different order of ancestor clauses changes the order of searching the implicit family tree. In one order, Prolog outputs solutions as it goes along. With the other order, Prolog travels to the end of the family tree and gives solutions on the way back. The desired order of solutions is determined by the application, and the rule order of ancestor is chosen 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 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 nenber (X, [1,2,3])?. In the standard order, the order of solutions is 7.2 Termination Programming in Pure Prolog Intuitive: Xe1, X=2, X=3. With the rules swapped, the order is X=3, Xe1. The order of Program 3.12 is more intuitive and hence preferable When the search tree for a given goal has an infinite branch, the or der of clauses can determine if any solutions are given at all. Consider the query append(Xs, [c,d] ,Y¥s)? with respect to append. As can be seen 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 Xs,Ys satisfying the query would be given. There is no consensus as to how to order the clauses of a Prolog pro- cedure. Clearly, the standard dictated in more conventional language: of testing for the termination condition before proceeding with the iter ation of recursion is not mandatory In Prolog. This is demonstrated in Program 3.15 for append as well as in other programs in this book. The reason is that the recursive or iterative clause tests its applicability by unification. This test is done explicitly and independently of the other clauses in the procedure. Clause order is more important for general Prolog programs than it is for pure Prolog programs. Other control features, notably the cut to be discussed in Chapter 11, depend significantly on the clause order. When such constructs are used, clauses lose their independence and modularity, and clause order becomes significant. In this chapter, for the most part, the convention that the recursive clauses precede the base clauses Is adopted, 7.11 Exercises for Section 7.1 (Verify the order of solutions for the query ancestor (abraham,X)? with respect to Program 7.1, and its variant with different rule order for ancestor, claimed in the text. (li) What is the order of solutions for the query ancestor (X, benja~ min)? with respect to Program 7.1? What if the rule order for ancestor were swapped? Prolog's depth-first traversal of search trees has a serious problem. If the search tree of a goal with respect to a program contains an infinite Chapter 7 branch, the computation will not terminate. Prolog may fail to find a solution to a goal, even though the goal has a finite computation. Nontermination arises with recursive rules. Consider adding a relation- ship narried(Male,Fenale) to our database of family relationships. A sample fact from the biblical situation is married (abraham,sarah). A user querying the married relationship should not care whether males obvious” or females are first, as the relationship is commutative. The Way of overcoming the commutativity is adding a recursive rule mar~ ried(X,Y) ~ married(Y,X). If this is added to the program, no com- putation involving narried would ever terminate. For example, the trace of the query narried(abrahan, sarah)? is given in Figure 7.1. Recursive rules that have the recursive goal as the first goal in the body are known as left recursive rules. The problematic married axiom is an example. Left recursive rules are inherently troublesome in Prolog, They cause nonterminating computations if called with inappropriate arguments, The best solution to the problem of left recursion is avoidance, The married relationship used a left recursive rule to express commutativity Commutative relationships are best handled differently, by defining a new predicate that has a clause for each permutation of the arguments of the relationship. For the relationship married, a new predicate, are. narried(Personi ,Person2), say, would be defined using two rules: are_married(X,Y) ~ married(x,Y) are_narried(X,Y) ~ married(¥,X) Unfortunately, it is not generally possible to remove all occurrences of left recursion, All the elegant minimal recursive logic programs shown in Chapter 3 are left recursive, and can cause nontermination. However, married(K,Y) ~ married(¥,X) narried(abrahan,earan) married(abrahan, sarah) narried(sarah,abrahan) married(abrahsn, sarah) arried(sarah,abrahan) Figure 7.1 A nonterminating computation 7.3. Goal Order Programming in Pure Prolog the appropriate analysis, using the concepts of domains and complete structures introduced in Section 5.2, can determine which queries will terminate with respect to recursive programs. Let us consider an example, Program 3.15 for appending two lists. The program for append is everywhere terminating for the set of goals whose first and/or last argument is a complete list. Any append query whose first argument is a complete list will terminate. Similarly, all queries where the third argument is a complete list will terminate. The program will also terminate if the first and/or third argument is a ground term that is not a list. The behavior of append is best summed up by consid- ering the queries that do not terminate, namely, when both the first and third arguments are incomplete lists that are unifiable. The condition for when a query to Program 3.12 for member terminates is also stated in terms of incomplete lists. A query does not terminate if, the second argument is an incomplete list. If the second argument of a query to member is a complete list, the query terminates. Another guaranteed means of generating nonterminating computa- to overlook, is circular definitions. Consider the pair of rules tions, ea! parent (X,Y) ~ child(¥,x) child(X,¥) ~ parent (¥,X) Any computation involving parent or child, for example, parent (haran,Lot)?, will not terminate. The search tree necessarily contains an Infinite branch, because of the circularity 7.2.1 Exercises for Section 7.2 (Discuss the termination behavior of both programs in Program 3.13 determining prefixes and suffixes of lis (i) Discuss the termination of Program 3.14c for sublist. 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 order to indicate the sequence of steps in the sorting algorithms. 134 Chapter 7 We first discuss goal order from the perspective of database program- ming. The order of goals can affect the order of solutions. Consider the query daughter(X,haran)? with respect to a variant of Program 1.2, where the order of the facts female(milcah) and female (yiscah) is interchanged. The two solutions are given in the order Xemilcah, iscah. If the goal order of the daughter rule were changed to be daughter(X,Y) ~ fenale(K) , father(¥,X)., the order of the solutions to the query, given the same database, would be Xeyiscah, Kenilcah. The reason that the order of goals in the body of a clause affects the order of solutions to a query is different from the reason that the order of rules in a procedure affects the solution order. Changing rule order does not change the search tree that must be traversed for a given query. The tree is just traversed in a different order. Changing goal order changes the search tree. Goal order determines the search tree. Goal order affects the amount of searching the program does in solv- ing a query by determining which search tree is traversed. Consider the two search trees for the query son(X,haran)?, given in Figure 5.2, They represent two different ways of finding a solution, In the first case, solu- tions are found by searching for children of haran and checking if they are male. The second case corresponds to the rule for son being written with the order of the goals in its body swapped, namely, son(x,¥) ~ male(K), parent (¥,x). Now the query is solved by searching through all the males in the program and checking if they are children of ha~ ran. If there were many male facts in the program, more search would be involved. For other queries, for example, son(sarah,Xx)?, the reverse order has advantages. Since sarah is not male, the query would fail more quickly The optimal goal order of Prolog programs varies with different uses. Consider the definition of grandparent. There are two possible rules: grandparent(x,2) ~ parent (X,Y), parent (¥,Z) grandparent (X,2) — parent (¥,2), parent (x,¥) If you wish to find someone's grandson with the grandfather relation ship with a query such as grandparent (abrahan,X)?, the first of the rules searches more directly. If looking for someone’s grandparent with Programming in Pure Prolog a query such as grandparent (K,isaac)?, the second rule finds the solu tion more directly. If efficiency is important, then it is advisable to have two distinct relationships, grandparent and grandchild, to be used ap: propriately at the user’s discretion, In contrast to rule order, goal order can determine whether computa- tions terminate. Consider the recursive rule for ancestor: ancestor (X,¥) — parent (X,2), ancestor(2,Y) If the goals in the body are swapped, the ancestor program becomes left recursive, and all Prolog computations with ancestor are nontermi nating, The goal order is also algorithm in Program 3.22 portant in the recursive clause of the quicksort quicksort ([XIXs] Ys) ~ partition (Xs,X,Littles,Bigs) , quicksort (Littles,Ls), quicksort (Bigs,Bs), append(Ls, [XIBs], Ys) . ‘The list should be partitioned into its two smaller pieces before recur- sively sorting the pieces. If, for example, the order of the partition goal and the recursive sorting goal Is swapped, no computations terminate. We next consider Program 3.16a for reversing a list: reverse({ J,{ 1) reverse((XIXs],Zs) — reverse(Xs,Ys), append(Ys,[X],Zs) The goal order is significant. As written, the program terminates with goals where the first argument is a complete list. Goals where the first argument is an incomplete list give nonterminating computations. If the goals in the recursive rule are swapped, the determining factor of the ter- mination of reverse goals is the second argument. Calls to reverse with the second argument a complete list terminate. They do not terminate if, the second argument is an incomplete list. A subtler example comes from the definition of the predicate sub- List in terms of two append goals, specifying the sublist as a suf- fix of a prefix, as given in Program 3.14e. Consider the query sub 1ist({2,2], [1,2,3,4])? with respect to the program. The query is reduced to append(AsXs,Bs, [1,2.3,4]), append(As, [2,3] ,ASKs)?. 136 7.4 Redundant Solutions Chapter 7 This has a finite search tree, and the initial query succeeds. If Pro- gram 3.14e had its goals reversed, the initial query would be reduced to append(As, [2,3] ,ASXs) append (AsKs Bs, [1,2,3,4])?. This leads to a nonterminating computation because of the first goal, as illustrated in Figure 5.4, A useful heuristic for goal order can be given for recursive programs with tests such as arithmetic comparisons, or determining whether two constants are different. The heuristic is to place the tests as early as possible. An example comes in the program for partition, which is part of Program 3.22. The first recursive rule is partition([XIXs],¥, [XILs],Bs)~ X Y, merge((Xixe] ,¥8,Z2) merge 1, (1X2) , (1X8) norge(Xs, } ,Xe) 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([ 1, { 1, [ 1) is covered by only one fact, the bottom one. Redundant computations occur when using menber 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 example, the search tree for the query nember(a, [a,b,a,c]) would have two success nodes, The redundancy of previous programs was removed by a careful con- sideration of the logic. In this case, the member program is correct. If we want a different behavior, the solution is to compose a modified version of nenber, Program 7.3 defines the relation menber_check(X,%s) which checks whether an element X is a member of a list Xs. The program is a varl- 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 ently. Figure 7.2 shows the difference between the search trees for the identical query to the two programs. The left tree is for the goal nea~ ber (a, [a,b,a,c]) with respect to Program 3.12. Note there are two suc cess nodes. The right tree is for the goal menber_check(a,[a,b,a,¢]) ‘with respect to Program 7.3. It has only one success node. 139 Programming in Pure Prolog ‘member check(X,Xs)_~ X isa member of the list Xs. check (X, [X1X8]) noaber_checke(K, (Y|Y¥s]) ~ X # Y, member_check(X, Ye) smenber Program 7.3 Checking for list membership —

También podría gustarte