Prolog Programming in Depth PDF
Prolog Programming in Depth PDF
(AUTHORS MANUSCRIPT)
Michael A. Covington
Donald Nute
Andr Vellino e
Articial Intelligence Programs The University of Georgia Athens, Georgia 306027415 U.S.A. September 1995
Authors manuscript
Contents
9
1 1 2 4 4 9 10 14 16 18 19 20 22 24 25 27 28 30
1
Authors manuscript 693 ppid September 9, 1995 Prolog Programming in Depth
2
2 Constructing Prolog Programs 2.1 Declarative and Procedural Semantics : : : 2.2 Output: write, nl, display : : : : : : : : : 2.3 Computing versus Printing : : : : : : : : : 2.4 Forcing Backtracking with fail : : : : : : : 2.5 Predicates as Subroutines : : : : : : : : : : 2.6 Input of Terms: read : : : : : : : : : : : : : 2.7 Manipulating the Knowledge Base : : : : : 2.8 Static and Dynamic Predicates : : : : : : : 2.9 More about consult and reconsult : : : : 2.10 File Handling: see, seen, tell, told : : : : 2.11 A Program that Learns : : : : : : : : : : 2.12 Character Input and Output: get, get0, put 2.13 Constructing Menus : : : : : : : : : : : : : 2.14 A Simple Expert System : : : : : : : : : : : Data Structures and Computation 3.1 Arithmetic : : : : : : : : : : : : : : : 3.2 Constructing Expressions : : : : : : 3.3 Practical Calculations : : : : : : : : 3.4 Testing for Instantiation : : : : : : : 3.5 Lists : : : : : : : : : : : : : : : : : : 3.6 Storing Data in Lists : : : : : : : : : 3.7 Recursion : : : : : : : : : : : : : : : 3.8 Counting List Elements : : : : : : : 3.9 Concatenating (Appending) Lists : : 3.10 Reversing a List Recursively : : : : : 3.11 A Faster Way to Reverse Lists : : : : 3.12 Character Strings : : : : : : : : : : : 3.13 Inputting a Line as a String or Atom 3.14 Structures : : : : : : : : : : : : : : : 3.15 The Occurs Check : : : : : : : : : 3.16 Constructing Goals at Runtime : : : 3.17 Data Storage Strategies : : : : : : : : 3.18 Bibliographical Notes : : : : : : : :
: : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :
31 31 32 34 34 37 38 40 42 43 45 46 48 51 54 61 61 63 65 67 69 71 72 74 75 77 78 79 81 83 85 85 87 89 91 91 92 94 96 97 98 99 99 101 103
: : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :
Expressing Procedural Algorithms 4.1 Procedural Prolog : : : : : : : : : : : : : : : : 4.2 Conditional Execution : : : : : : : : : : : : : : 4.3 The Cut Operator (!) : : : : : : : : : : : : : 4.4 Red Cuts and Green Cuts : : : : : : : : : : : : 4.5 Where Not to Put Cuts : : : : : : : : : : : : : : 4.6 Making a Goal Deterministic Without Cuts : : 4.7 The IfThenElse Structure (- ) : : : : : : : : 4.8 Making a Goal Always Succeed or Always Fail 4.9 Repetition Through Backtracking : : : : : : : : 4.10 Recursion : : : : : : : : : : : : : : : : : : : : :
: : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :
Authors manuscript
3
4.11 4.12 4.13 4.14 4.15 4.16 4.17 More About Recursive Loops : : : : : : Organizing Recursive Code : : : : : : : Why Tail Recursion is Special : : : : : : Indexing : : : : : : : : : : : : : : : : : : Modularity, Name Conicts, and Stubs : How to Document Prolog Predicates : : Supplement: Some Hand Computations 4.17.1 Recursion : : : : : : : : : : : : : : 4.17.2 Saving backtrack points : : : : : : 4.17.3 Backtracking : : : : : : : : : : : : 4.17.4 Cuts : : : : : : : : : : : : : : : : : 4.17.5 An unexpected loop : : : : : : : : 4.18 Bibliographical Notes : : : : : : : : : : 5
: : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :
104 107 108 111 113 114 116 116 117 118 120 122 127 129 129 129 133 134 135 136 139 140 143 143 144 148 153 153 155 157 159 160 163 165 167 170 172 173 173 175 176 177 181 182 183
Reading Data in Foreign Formats 5.1 The Problem of FreeForm Input : : : : : : : : : 5.2 Converting Strings to Atoms and Numbers : : : 5.3 Combining Our Code with Yours : : : : : : : : : 5.4 Validating User Input : : : : : : : : : : : : : : : 5.5 Constructing Menus : : : : : : : : : : : : : : : : 5.6 Reading Files with get byte : : : : : : : : : : : : 5.7 File Handles (Stream Identiers) : : : : : : : : : 5.8 FixedLength Fields : : : : : : : : : : : : : : : : 5.9 Now What Do You Do With the Data? : : : : : : 5.10 CommaDelimited Fields : : : : : : : : : : : : : 5.11 Binary Numbers : : : : : : : : : : : : : : : : : : 5.12 Grand Finale: Reading a Lotus Spreadsheet : : : : 5.13 Language and Metalanguage : : : : : : : : : : : 5.14 Collecting Alternative Solutions into a List : : : : 5.15 Using bagof and setof : : : : : : : : : : : : : : 5.16 Finding the Smallest, Largest, or Best Solution 5.17 Intensional and Extensional Queries : : : : : : : 5.18 Operator Denitions : : : : : : : : : : : : : : : : 5.19 Giving Meaning to Operators : : : : : : : : : : : 5.20 Prolog in Prolog : : : : : : : : : : : : : : : : : : 5.21 Extending the Inference Engine : : : : : : : : : : 5.22 Personalizing the User Interface : : : : : : : : : : 5.23 Bibliographical Notes : : : : : : : : : : : : : : : 7.1 7.2 7.3 7.4 7.5 7.6 7.7 Advanced Techniques Structures as Trees : : : : : : : : : : : : Lists as Structures : : : : : : : : : : : : How to Search or Process Any Structure Internal Representation of Data : : : : : Simulating Arrays in Prolog : : : : : : : Difference Lists : : : : : : : : : : : : : : Quicksort : : : : : : : : : : : : : : : : :
: : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :
: : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :
Authors manuscript
4
7.8 7.9 7.10 7.11 7.12 7.13 7.14 Efciency of Sorting Algorithms : : : : : : : : Mergesort : : : : : : : : : : : : : : : : : : : : : Binary Trees : : : : : : : : : : : : : : : : : : : : Treesort : : : : : : : : : : : : : : : : : : : : : : Customized Arithmetic: A Replacement for is Solving Equations Numerically : : : : : : : : : Bibliographical Notes : : : : : : : : : : : : : :
: : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :
205
209 209 213 215 216 216 219 221 223 226 228 229 229 233 236 243 245 251 255 257 258 261 263 265 267 267 268 269 269 271 272 281 283 285 288
A Simple Expert System Shell Expert systems : : : : : : : : : : : : : : : : : : : : Expert consultants and expert consulting systems : Parts of an expert consulting system : : : : : : : : Expert system shells : : : : : : : : : : : : : : : : : Extending the power of Prolog : : : : : : : : : : : 9.5.1 Listing of XSHELL.PL : : : : : : : : : : : : : 9.6 XSHELL: the main program : : : : : : : : : : : : : 9.7 Asking about properties in XSHELL : : : : : : : : 9.8 Asking about parameters in XSHELL : : : : : : : : 9.9 XSHELLs explanatatory facility : : : : : : : : : : :
: : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :
Authors manuscript
5
9.10 9.11 9.12 9.13 9.14 10 CICHLID: a sample XSHELL knowledge base 9.10.1 Listing of CICHLID.PL : : : : : : : : : : A consultation with CICHLID : : : : : : : : : PAINT: a sample XSHELL knowledge base : 9.12.1 Listing of PAINT.PL : : : : : : : : : : : Developing XSHELL knowledge bases : : : : Bibliographical notes : : : : : : : : : : : : : :
: : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :
290 293 298 300 302 309 313 315 315 316 318 320 324 325 327 328 338 339 343 345 346 347 347 348 350 353 354 355 358 365 366 368 370 373 386 387 388 389 389 390 391 392 392 396 397
An Expert System Shell With Uncertainty 10.1 Uncertainty, probability, and condence : : : : : : : 10.2 Representing and computing condence or certainty 10.3 Condence rules : : : : : : : : : : : : : : : : : : : : 10.4 The CONMAN inference engine : : : : : : : : : : : 10.5 Getting information from the user : : : : : : : : : : 10.6 The CONMAN explanatory facilities : : : : : : : : : 10.7 The main program : : : : : : : : : : : : : : : : : : : 10.7.1 Listing of CONMAN.PL : : : : : : : : : : : : : 10.8 CONMAN knowledge bases : : : : : : : : : : : : : 10.8.1 Listing of MDC.PL : : : : : : : : : : : : : : : : 10.8.2 Listing of PET.PL : : : : : : : : : : : : : : : : : 10.9 No condence in condence : : : : : : : : : : : : : 10.10 Bibliographical notes : : : : : : : : : : : : : : : : : : Defeasible Prolog 11.1 Nonmonotonic reasoning and Prolog : : : : : 11.2 New syntax for defeasible reasoning : : : : : 11.3 Strict rules : : : : : : : : : : : : : : : : : : : : 11.4 Incompatible conclusions : : : : : : : : : : : 11.5 Superiority of rules : : : : : : : : : : : : : : : 11.6 Specicity : : : : : : : : : : : : : : : : : : : : 11.6.1 Listing of DPROLOG.PL : : : : : : : : : 11.7 Dening strict derivability in Prolog : : : : : 11.8 d-Prolog: preliminaries : : : : : : : : : : : : 11.9 Using defeasible rules : : : : : : : : : : : : : 11.10 Preemption of defeaters : : : : : : : : : : : : 11.10.1Listing of DPUTILS.PL : : : : : : : : : : 11.11 Defeasible queries and exhaustive responses 11.12 Listing defeasible predicates : : : : : : : : : : 11.13 Consulting and reconsulting d-Prolog les : : 11.14 The d-Prolog Dictionary : : : : : : : : : : : : 11.15 Rescinding predicates and knowledge bases : 11.16 Finding contradictions : : : : : : : : : : : : : 11.17 A special explanatory facility : : : : : : : : : 11.18 A suite of examples : : : : : : : : : : : : : : : 11.18.1Listing of KBASES.DPL : : : : : : : : : 11.19 Some feathered and non-feathered friends : : 11.20 Inheritance reasoning : : : : : : : : : : : : :
: : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :
11
: : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :
Authors manuscript
6
11.21 11.22 11.23 11.24 12 Temporal persistence : : : : : : : : : : : : : The Election Example : : : : : : : : : : : : d-Prolog and the Closed World Assumption BIBLIOGRAPHICAL NOTES : : : : : : : :
: : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :
400 402 406 407 409 409 410 411 412 418 421 424 427 430 433 435 437 450 452 455 457 458 458 458 458 459 460 460 462 463 463 463 463 463 465 465 465 466 466 467 467 467 467 467 467
Natural Language Processing 12.1 Prolog and Human Languages 12.2 Levels of Linguistic Analysis : 12.3 Tokenization : : : : : : : : : : 12.4 Template Systems : : : : : : : 12.5 Generative Grammars : : : : : 12.6 A Simple Parser : : : : : : : : : 12.7 Grammar Rule (DCG) Notation 12.8 Grammatical Features : : : : : 12.9 Morphology : : : : : : : : : : : 12.10 Constructing the Parse Tree : : 12.11 Unbounded Movements : : : : 12.12 Semantic Interpretation : : : : 12.13 Constructing Representations : 12.14 Dummy Entities : : : : : : : : 12.15 Bibliographical Notes : : : : :
: : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :
Summary of ISO Prolog A.1 Syntax of Terms : : : : : : : : : : : : : : : : : : A.1.1 Comments and Whitespace : : : : : : : : A.1.2 Variables : : : : : : : : : : : : : : : : : : : A.1.3 Atoms (Constants) : : : : : : : : : : : : : A.1.4 Numbers : : : : : : : : : : : : : : : : : : A.1.5 Character Strings : : : : : : : : : : : : : : A.1.6 Structures : : : : : : : : : : : : : : : : : : A.1.7 Operators : : : : : : : : : : : : : : : : : : A.1.8 Commas : : : : : : : : : : : : : : : : : : : A.1.9 Parentheses : : : : : : : : : : : : : : : : : A.2 Program Structure : : : : : : : : : : : : : : : : A.2.1 Programs : : : : : : : : : : : : : : : : : : A.2.2 Directives : : : : : : : : : : : : : : : : : : A.3 Control Structures : : : : : : : : : : : : : : : : A.3.1 Conjunction, disjunction, fail, and true : A.3.2 Cuts : : : : : : : : : : : : : : : : : : : : : A.3.3 Ifthenelse : : : : : : : : : : : : : : : : : A.3.4 Variable goals, call : : : : : : : : : : : : A.3.5 repeat : : : : : : : : : : : : : : : : : : : : A.3.6 once : : : : : : : : : : : : : : : : : : : : : A.3.7 Negation : : : : : : : : : : : : : : : : : : A.4 Error Handling : : : : : : : : : : : : : : : : : : A.4.1 catch and throw : : : : : : : : : : : : : : A.4.2 Errors detected by the system : : : : : : :
: : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :
Authors manuscript
7
Flags : : : : : : : : : : : : : : : : : : : : Arithmetic : : : : : : : : : : : : : : : : : A.6.1 Where expressions are evaluated : A.6.2 Functors allowed in expressions : : A.7 Input and Output : : : : : : : : : : : : : A.7.1 Overview : : : : : : : : : : : : : : A.7.2 Opening a stream : : : : : : : : : : A.7.3 Closing a stream : : : : : : : : : : A.7.4 Stream properties : : : : : : : : : : A.7.5 Reading and writing characters : : A.7.6 Reading terms : : : : : : : : : : : A.7.7 Writing terms : : : : : : : : : : : : A.7.8 Other inputoutput predicates : : A.8 Other BuiltIn Predicates : : : : : : : : A.8.1 Unication : : : : : : : : : : : : : A.8.2 Comparison : : : : : : : : : : : : : A.8.3 Type tests : : : : : : : : : : : : : : A.8.4 Creating and decomposing terms : A.8.5 Manipulating the knowledge base A.8.6 Finding all solutions to a query : : A.8.7 Terminating execution : : : : : : : A.9 Modules : : : : : : : : : : : : : : : : : : A.9.1 Preventing name conicts : : : : : A.9.2 Example of a module : : : : : : : : A.9.3 Module syntax : : : : : : : : : : : A.9.4 Metapredicates : : : : : : : : : : : A.9.5 Explicit module qualiers : : : : : A.9.6 Additional builtin predicates : : : A.9.7 A word of caution : : : : : : : : : B B.1 B.2 A.5 A.6
: : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :
468 470 470 470 471 471 471 472 472 473 473 476 476 476 478 478 479 479 480 481 482 483 483 483 483 484 485 485 485 487 487 488 488 488 488 488 488 489 489 489 489 490 490 490 490 491
Some Differences Between Prolog Implementations Introduction : : : : : : : : : : : : : : : : : : : : : : Which Predicates are BuiltIn? : : : : : : : : : : : B.2.1 Failure as the symptom : : : : : : : : : : : : B.2.2 Minimum set of builtin predicates : : : : : : B.2.3 The Quintus library : : : : : : : : : : : : : : B.3 Variation In Behavior of BuiltIn Predicates : : : : B.3.1 abolish and retractall : : : : : : : : : : : B.3.2 name: numeric arguments : : : : : : : : : : : B.3.3 functor: numeric arguments : : : : : : : : : B.3.4 op, operators, and current op : : : : : : : : : B.3.5 findall, setof, and bagof : : : : : : : : : : B.3.6 listing : : : : : : : : : : : : : : : : : : : : : B.4 Control Constructs : : : : : : : : : : : : : : : : : : B.4.1 Negation : : : : : : : : : : : : : : : : : : : : B.4.2 Scope of cuts : : : : : : : : : : : : : : : : : : B.4.3 Ifthenelse : : : : : : : : : : : : : : : : : : :
: : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :
Authors manuscript
8
B.5 B.4.4 Tail recursion and backtrack points : : : : : : : : B.4.5 Alternatives created by asserting : : : : : : : : : Syntax and Program Layout : : : : : : : : : : : : : : : B.5.1 Syntax selection : : : : : : : : : : : : : : : : : : : B.5.2 Comments : : : : : : : : : : : : : : : : : : : : : : B.5.3 Whitespace : : : : : : : : : : : : : : : : : : : : : B.5.4 Backslashes : : : : : : : : : : : : : : : : : : : : : B.5.5 Directives : : : : : : : : : : : : : : : : : : : : : : B.5.6 consult and reconsult : : : : : : : : : : : : : : B.5.7 Embedded queries : : : : : : : : : : : : : : : : : Arithmetic : : : : : : : : : : : : : : : : : : : : : : : : : B.6.1 Evaluable functors : : : : : : : : : : : : : : : : : B.6.2 Where expressions are evaluated : : : : : : : : : B.6.3 Expressions created at runtime in Quintus Prolog Input and Output : : : : : : : : : : : : : : : : : : : : : B.7.1 Keyboard buffering : : : : : : : : : : : : : : : : : B.7.2 Flushing output : : : : : : : : : : : : : : : : : : : B.7.3 get and get0 : : : : : : : : : : : : : : : : : : : : B.7.4 File handling : : : : : : : : : : : : : : : : : : : : B.7.5 Formatted output : : : : : : : : : : : : : : : : : : Denite Clause Grammars : : : : : : : : : : : : : : : : B.8.1 Terminal nodes : : : : : : : : : : : : : : : : : : : B.8.2 Commas on the left : : : : : : : : : : : : : : : : : B.8.3 phrase : : : : : : : : : : : : : : : : : : : : : : : :
: : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : : :
B.6
B.7
B.8
491 491 492 492 492 492 492 493 493 493 493 493 494 494 494 494 494 495 495 495 496 496 497 497
Authors manuscript
Preface
Prolog is an up-and-coming computer language. It has taken its place alongside Lisp in articial intelligence research, and industry has adopted it widely for knowledgebased systems. In this book, we emphasize practical Prolog programming, not just theory. We present several ready-to-run expert system shells, as well as routines for sorting, searching, natural language processing, and even numerical equation solving. We also emphasize interoperability with other software. For example, Chapter 5 presents techniques for reading Lotus spreadsheets and other special le formats from within a Prolog program. There is now an ofcial ISO standard for the Prolog language, and this book follows it while retaining compatibility with earlier implementations. We summarize the ISO Prolog standard in Appendix A. It is essentially what has been called Edinburgh Prolog. Our programs have been tested under Quintus Prolog, Arity Prolog, ALS Prolog, LPA Prolog, and a number of other commercial implementations, as well as freeware Prologs from ESL and SWI. (We do not cover Turbo [PDC] Prolog, nor Colmerauers Prolog II and III, which are distinctly different languages.) An earlier version of this book was published by Scott, Foresman in 1987. Since then, we have used the book in our own courses every year, and the present version reects numerous renements based on actual classroom experience. We want to thank all our students and colleagues who made suggestions, especially Don Potter, Harold Dale, Judy Guinan, Stephen McSweeney, Xun Shao, Joerg Zeppen, Joerg Grau, Jason Prickett, Ron Rouhani, Ningyu Chen, and Feng Chen.
9
Authors manuscript 693 ppid September 9, 1995 Prolog Programming in Depth
10
When necessary, Chapter 7 can be skipped since the remainder of the book does not rely on them. Those who are not preparing for work in the software industry may take Chapter 5 somewhat lightly. A Prolog course for experienced AI programmers should cover Chapters 1-7 and 12 but may skip Chapters 8-11, which cover basic AI topics. The programs and data les from this book are available on diskette from the publisher and by anonymous FTP from ai.uga.edu. From the same FTP site you can also obtain freeware Prolog compilers and demonstrations of commercial products. We are always interested in hearing from readers who have questions or suggestions for improvement. Athens, Georgia September 1995
Authors manuscript
Part I
11
Authors manuscript 693 ppid September 9, 1995 Prolog Programming in Depth
Authors manuscript
Chapter 1
Introducing Prolog
1
Authors manuscript 693 ppid September 9, 1995 Prolog Programming in Depth
Introducing Prolog
Chap. 1
in 1982 to create a new generation of knowledgebased computers. Commercially, Prolog is often used in expert systems, automated helpdesks, intelligent databases, and natural language processing programs. Prolog has much in common with Lisp, the language traditionally used for articial intelligence research. Both languages make it easy to perform complex computations on complex data, and both have the power to express algorithms elegantly. Both Lisp and Prolog allocate memory dynamically, so that the programmer does not have to declare the size of data structures before creating them. And both languages allow the program to examine and modify itself, so that a program can learn from information obtained at run time. The main difference is that Prolog has an automated reasoning procedure an INFERENCE ENGINE built into it, while Lisp does not. As a result, programs that perform logical reasoning are much easier to write in Prolog than in Lisp. If the built in inference engine is not suitable for a particular problem, the Prolog programmer can usually use part of the builtin mechanism while rewriting the rest. In Lisp, on the other hand, if an inference engine is needed, the programmer must supply it. Is Prolog objectoriented? Not exactly. Prolog is a different, newer, and more versatile solution to the problem that object orientation was designed to solve. Its quite possible to organize a Prolog program in an objectoriented way, but in Prolog, thats not the only option available to you. Prolog lets you talk about properties and relations directly, rather than approaching them indirectly through an inheritance mechanism.
We will call a collection of information such as this a KNOWLEDGE BASE. We will call item [1] a RULE because it enables us to infer one piece of information from another, and we will call item [2] a FACT because it does not depend on any other information. Note that a rule contains an if and a fact does not. Facts and rules are the two types of CLAUSES. A fact need not be a true statement about the real world; if you said Minneapolis was in Florida, Prolog would believe you. Facts are sometimes called GROUND CLAUSES because they are the basis from which other information is inferred. Suppose we want to know whether Atlanta is in the United States. Clearly, [1] and [2] can be chained together to answer this question, but how should this chaining be implemented on a computer? The key is to express [1] and [2] as denitions of procedures:
Authors manuscript
Sec. 1.2. 10 20
To prove that X is in the United States, prove that X is in Georgia. To prove that Atlanta is in Georgia, do nothing.
We ask our question by issuing the instruction: Prove that Atlanta is in the United States. This calls procedure [10 ], which in turn calls procedure [20 ], which returns the answer yes. Prolog has its own notation for representing knowledge. Our sample knowledge base can be represented in Prolog as follows:
in_united_statesX :- in_georgiaX. in_georgiaatlanta.
Here in_georgia and in_united_states are PREDICATES that is, they say things about individuals. A predicate can take any xed number of ARGUMENTS (parameters); for example,
femalesharon.
might mean Melody is the mother of Sharon. A predicate that takes N arguments (for any number N ) is called an N PLACE PREDICATE; thus we say that in_georgia, in_united_states, and female are ONEPLACE PREDICATES, while mother is a TWO PLACE PREDICATE. A oneplace predicate describes a PROPERTY of one individual; a two-place predicate describes a RELATION between two individuals. The number of arguments that a predicate takes is called its ARITY (from terms like unary, binary, ternary, and the like). Two distinct predicates can have the same name if they have different arities; thus you might have both mothermelody, meaning Melody is a mother, and mothermelody,sharon, meaning Melody is the mother of Sharon. We will avoid this practice because it can lead to confusion. In some contexts a predicate is identied by giving its name, a slash, and its arity; thus we can refer to the two predicates just mentioned as mother 1 and mother 2.
Exercise 1.2.1 Give an example, in Prolog, of: a fact; a rule; a clause; a oneplace predicate; a predicate of arity 2. Exercise 1.2.2 In the example above, we represented in Georgia as a property of Atlanta. Write a Prolog fact that represents in as a relation between Atlanta and Georgia. Exercise 1.2.3 How would you represent, in Prolog, the fact Atlanta is at latitude 34 north and longitude 84 west? (Hint: More than one approach is possible. Second hint: Its OK to use numbers as constants in Prolog.)
Authors manuscript
Introducing Prolog
Chap. 1
An important goal of this book is to teach you how to write portable Prolog code. Accordingly, we will stick to features of the language that are the same in practically all implementations. The programs in this book were developed in Arity Prolog and ALS Prolog on IBM PCs and Quintus Prolog on Sun workstations. Most of them have also been tested in SWIProlog, LPA Prolog, Cogent Prolog, and Expert Systems Limiteds Public Domain Prolog2.1 For many years, the de facto standard for Prolog was the language described by Clocksin and Mellish in their popular textbook, Programming in Prolog (1981, second edition 1984). This is essentially the language implemented on the DEC-10 by D. H. D. Warren and his colleagues in the late 1970s, and is often called Edinburgh Prolog or DEC10 Prolog. Most commercial implementations of Prolog aim to be compatible with it. In 1995 the International Organization for Standardization (ISO) published an international standard for the Prolog language (Scowen 1995). ISO Prolog is very similar to Edinburgh Prolog but extends it in some ways. Our aim in this book is to be as compatible with the ISO standard as possible, but without using features of ISO Prolog that are not yet widely implemented. See Appendix A for more information about ISO Prolog. Finally, we must warn you that this book is not about Turbo Prolog (PDC Prolog), nor about Colmerauers Prolog II and Prolog III. Turbo Prolog is Prolog with data type declarations added. As a result, programs run faster, but are largely unable to examine and modify themselves. Colmerauers Prolog II and III are CONSTRAINT LOGIC PROGRAMMING languages, which means they let you put limits on the value of a variable before actually giving it a value; this makes many new techniques available. The concepts in this book are certainly relevant to Turbo (PDC) Prolog and Prolog II and III, but the details of the languages are different.
Exercise 1.3.1 If you have not done so already, familiarize yourself with the manuals for the version of Prolog that you will be using. Exercise 1.3.2 In the Prolog that you are using, does the query ?- help. do anything useful? Try it and see.
Authors manuscript
Sec. 1.4.
File GEO.PL Sample geographical knowledge base * * * * * * * * * Clause Clause Clause Clause Clause Clause Clause Clause Clause 1 2 3 4 5 6 7 8 9 * * * * * * * * * located_inatlanta,georgia. located_inhouston,texas. located_inaustin,texas. located_intoronto,ontario. located_inX,usa :- located_inX,georgia. located_inX,usa :- located_inX,texas. located_inX,canada :- located_inX,ontario. located_inX,north_america :- located_inX,usa. located_inX,north_america :- located_inX,canada.
Figure 1.1
Atlanta is located in Georgia, Houston is located in Texas, and the like, plus rules such as X is located in the United States if X is located in Georgia. Notice that names of individuals, as well as the predicate located_in, always begin with lowercase letters. Names that begin with capital letters are variables and can be given any value needed to carry out the computation. This knowledge base contains only one variable, called X. Any name can contain the underscore character (_). Notice also that there are two ways to delimit comments. Anything bracketed by * and * is a comment; so is anything between and the end of the line, like this:
* This is a comment * So is this
Comments are ignored by the computer; we use them to add explanatory information and (in this case) to number the clauses so we can talk about them conveniently. Its not clear whether to call this knowledge base a program; it contains nothing that will actually cause computation to start. Instead, the user loads the knowledge base into the computer and then starts computation by typing a QUERY, which is a question that you want the computer to answer. A query is also called a GOAL. It looks like a Prolog clause except that it is preceded by ?- although in most cases the Prolog implementation supplies the ?- and you need only type the goal itself. Unfortunately, we cannot tell you how to use Prolog on your computer, because there is considerable variation from one implementation to another. In general, though, the procedure is as follows. First use a text editor to create a le of clauses such as GEO.PL in Figure 1. Then get into the Prolog interpreter and type the special query:
?- consult'geo.pl'.
(Remember the period at the end if you dont type it, Prolog will assume your query continues onto the next line.) Prolog replies
yes
Authors manuscript
Introducing Prolog
Chap. 1
to indicate that it succeeded in loading the knowledge base. Two important notes: First, if you want to load the same program again after escaping to an editor, use reconsult instead of consult. That way you wont get two copies of it in memory at the same time. Second, if youre using a PC, note that backslashes ( ) in the le name may have to be written twice (e.g., consult'c: myprog.pl' to load C:nMYPROG.PL). This is required in the ISO standard but not in most of the MSDOS Prologs that we have worked with. As soon as consult has done its work, you can type your queries. Eventually, youll be through using Prolog, and you can exit from the Prolog system by typing the special query
?- halt.
Most queries, however, retrieve information from the knowledge base. You can type
?- located_inatlanta,georgia.
to ask whether Atlanta is in Georgia. Of course it is; this query matches Clause 1 exactly, so Prolog again replies yes. Similarly, the query
?- located_inatlanta,usa.
can be answered (or, in Prolog jargon, SOLVED or SATISFIED) by calling Clause 5 and then Clause 1, so it, too, gets a yes. On the other hand, the query
?- located_inatlanta,texas.
gets a no because the knowledge base contains no information from which the existence of an Atlanta in Texas can be deduced. We say that a query SUCCEEDS if it gets a yes answer, or FAILS if it gets a no answer. Besides answering yes or no to specic queries, Prolog can ll in the blanks in a query that contains variables. For example, the query
?- located_inX,texas.
means Give me a value of X such that inX,texas succeeds. And here we run into another unique feature of Prolog a single query can have multiple solutions. Both Houston and Austin are in Texas. What happens in this case is that Prolog nds one solution and then asks you whether to look for another. This continues until all alternatives are found or you stop asking for them. In some Prologs, the process looks like this:
?- located_inX,texas. X = houston More y n? y X = austin More y n? y no
Authors manuscript
Sec. 1.4.
The no at the end means there are no more solutions. In Arity Prolog, the notation is more concise. After each solution, the computer displays an arrow (- ). You respond by typing a semicolon (meaning look for more alternatives) or by hitting Return (meaning quit), like this:
?- located_inX,texas. X = houston - ; X = austin - ; no
In Quintus Prolog and many others, there isnt even an arrow; the computer just pauses and waits for you to type a semicolon and then hit Return, or else hit Return by itself:
?- located_inX,texas. X = houston ; X = austin ; no
Also, youll nd it hard to predict whether the computer pauses after the last solution; it depends partly on the way the user interface is written, and partly on exactly what you have queried. From here on, we will present interactions like these by printing only the solutions themselves and leaving out whatever the user had to type to get the alternatives. Sometimes your Prolog system may not let you ask for alternatives (by typing semicolons, or whatever) even though alternative solutions do exist. There are two possible reasons. First, if your query has performed any output of its own, the Prolog system will assume that youve already printed out whatever you wanted to see, and thus that youre not going to want to search for alternatives interactively. So, for example, the query
?- located_inX,texas, writeX.
displays only one answer even though, logically, there are alternatives. Second, if your query contains no variables, Prolog will only print yes once no matter how many ways of satisfying the query there actually are. Regardless of how your Prolog system acts, heres a surere way to get a list of all the cities in Texas that the knowledge base knows about:
?- located_inX,texas, writeX, nl, fail.
The special predicate write causes each value of X to be written out; nl starts a new line after each value is written; and fail forces the computer to backtrack to nd all solutions. We will explain how this works in Chapter 2. For now, take it on faith. We say that the predicate located_in is NONDETERMINISTIC because the same question can yield more than one answer. The term nondeterministic does not mean that computers are unpredictable or that they have free will, but only that they can produce more than one solution to a single problem. Another important characteristic of Prolog is that any of the arguments of a predicate can be queried. Prolog can either compute the state from the city or compute the city from the state. Thus, the query
Authors manuscript
8
?- located_inaustin,X.
Introducing Prolog
Chap. 1
retrieves the names of cities that are in Texas. We will call this feature REVERSIBILITY or INTERCHANGEABILITY OF UNKNOWNS. In many but not all situations, Prolog can ll in any argument of a predicate by searching the knowledge base. In Chapter 3 we will encounter some cases where this is not so. We can even query all the arguments of a predicate at once. The query
?- located_inX,Y.
means What is in what? and each answer contains values for both X and Y. (Atlanta is in Georgia, Houston is in Texas, Austin is in Texas, Toronto is in Ontario, Atlanta is in the U.S.A., Houston is in the U.S.A., Austin is in the U.S.A., Toronto is in Canada, and so forth.) On the other hand,
?- located_inX,X.
means What is in itself? and fails both occurrences of X have to have the same value, and there is no value of X that can successfully occur in both positions at the same time. If we were to add New York to the knowledge base, this query could succeed because the city has the same name as the state containing it.
Exercise 1.4.1 Load GEO.PL into your Prolog system and try it out. How does your Prolog system respond to each of the following queries? Give all responses if there is more than one.
????located_inaustin,texas. located_inaustin,georgia. located_inWhat,texas. located_inatlanta,What.
Exercise 1.4.2 Add your home town and state (or region) and country to GEO.PL and demonstrate that the modied version works correctly. Exercise 1.4.3 How does GEO.PL respond to the query ?- located_intexas,usa.? Why? Exercise 1.4.4 (For PC users only)
Does your Prolog require backslashes in le names to be written double? That is, to load C:nMYDIRnMYPROG.PL, do you have to type consult'c: mydir myprog.pl'? Try it and see.
Authors manuscript
Sec. 1.5.
unies with the head of Clause 8 by instantiating X as austin. The right-hand side of Clause 8 then becomes the new goal. Thus: Goal: Clause 8: Instantiation: New goal: Goal: Clause 6: Instantiation: New query:
?- located_inaustin,north_america. located_inX,north_america :- located_inX,usa. X = austin ?- located_inaustin,usa.
This query matches Clause 3. Since Clause 3 does not contain an if, no new query is generated and the process terminates successfully. If, at some point, we had had a query that would not unify with any clause, the process would terminate with failure. Notice that we have to instantiate X two times, once when we call Clause 8 and once again when we call Clause 6. Although called by the same name, the X in Clause 8 is not the same as the X in Clause 6. Theres a general principle at work here: Likenamed variables are not the same variable unless they occur in the same clause or the same query. In fact, if we were to use Clause 8 twice, the value given to X the rst time would not affect the value of X the second time. Each instantiation applies only to one clause, and only to one invocation of that clause. However, it does apply to all of the occurrences of that variable in that clause; when we instantiate X, all the Xs in the clause take on the same value at once. If youve never used a language other than Prolog, youre probably thinking that this is obvious, and wondering why we made such a point of it; Prolog couldnt possibly work any other way. But if youre accustomed to a conventional language, we want to make sure that you dont think of instantiation as storing a value in a variable. Instantiation is more like passing a parameter. Suppose you have a Pascal procedure such as this:
procedure px:integer; begin writeln'The answer is ',x end; This is Pascal, not Prolog!
Authors manuscript
10
If you call this with the statement
p3
Introducing Prolog
Chap. 1
you are passing 3 to procedure p as a parameter. The variable x in the procedure is instantiated as 3, but only for the duration of this invocation of p. It would not be correct to think of the value 3 as being stored in a location called x; as soon as the procedure terminates, it is gone. One uninstantiated variable can even be unied with another. When this happens, the two variables are said to SHARE, which means that they become alternative names for a single variable, and if one of them is subsequently given a value, the other one will have the same value at the same time. This situation is relatively uncommon, but there are programs in which it plays a crucial role. We will discuss unication and instantiation at greater length in Chapters 3 and 13.
Exercise 1.5.1 What would happen to GEO.PL if Clauses 5 and 6 were changed to the following?
located_inY,usa :- located_inY,georgia. located_inZ,usa :- located_inZ,texas.
Exercise 1.5.2 Disregarding the wisdom of this section, a beginning Prolog student loads GEO.PL and has the following dialogue with the computer:
?- located_inaustin,X. X = texas ?- writeX. X is uninstantiated
Why didnt the computer print texas the second time? Try this on your computer. What does your computer print when you try to write out an uninstantiated variable?
1.6. BACKTRACKING
If several rules can unify with a query, how does Prolog know which one to use? After all, if we unify
?- located_inaustin,usa.
which succeeds. From the viewpoint of our query, Clause 5 is a blind alley that doesnt lead to a solution. The answer is that Prolog doesnt know in advance which clause will succeed, but it does know how to back out of blind alleys. This process is called BACKTRACKING.
Authors manuscript
Sec. 1.6.
Backtracking
11
Prolog tries the rules in the order in which they are given in the knowledge base. If a rule doesnt lead to success, it backs up and tries another. Thus, the query ?- located_inaustin,usa. will rst try to unify with Clause 5 and then, when that fails, the computer will back up and try Clause 6. A good way to conceive of backtracking is to arrange all possible paths of computation into a tree. Consider the query:
?- located_intoronto,north_america.
Figure 1.2 shows, in tree form, all the paths that the computation might follow. We can prove that Toronto is in North America if we can prove that it is in either the U.S.A. or Canada. If we try the U.S.A., we have to try several states; fortunately, we only know about one Canadian province. Almost all of the paths are blind alleys, and only the rightmost one leads to a successful solution. Figure 1.3 is the same diagram with arrows added to show the order in which the possibilities are tried. Whenever the computer nds that it has gone down a blind alley, it backs up to the most recent query for which there are still untried alternatives, and tries another path. Remember this principle: Backtracking always goes back to the most recent untried alternative. When a successful answer is found, the process stops unless, of course, the user asks for alternatives, in which case the computer continues backtracking to look for another successful path. This strategy of searching a tree is called DEPTHFIRST SEARCH because it involves going as far along each path as possible before backing up and trying another path. Depthrst search is a powerful algorithm for solving almost any problem that involves trying alternative combinations. Programs based on depthrst search are easy to write in Prolog. Note that, if we use only the features of Prolog discussed so far, any Prolog query gives the same answers regardless of the order in which the rules and facts are stated in the knowledge base. Rearranging the knowledge base affects the order in which alternative solutions are found, as well as the number of blind alleys that must be tried before nding a successful solution, but it does not affect the actual answers given. This is one of the most striking differences between Prolog and conventional programming languages.
Exercise 1.6.1 Make a diagram like Figure 1.3 showing how GEO.PL handles the query ?- located_inaustin,north_america. Exercise 1.6.2 With GEO.PL, which is faster to compute, ?- located_inatlanta,usa. or ?- located_inaustin,usa.? Why? Exercise 1.6.3 Without using the computer, predict the order in which the Prolog system will nd the various solutions to the query ?- located_inX,usa. Then use the computer to verify your prediction.
Authors manuscript
12
Introducing Prolog
Chap. 1
?- located_in(toronto,north_america).
Clause 8 ?- located_in(toronto,usa).
Clause 9 ?- located_in(toronto,canada).
Clause 5 ?- located_in(toronto,georgia).
Clause 6 ?- located_in(toronto,texas).
Clause 7 ?- located_in(toronto,ontario).
Clause 4
Figure 1.2
The solution to the query lies somewhere along one of these paths.
Authors manuscript
Sec. 1.6.
Backtracking
13
?- located_in(toronto,north_america).
Clause 8 ?- located_in(toronto,usa).
Clause 9 ?- located_in(toronto,canada).
Clause 5 ?- located_in(toronto,georgia).
Clause 6 ?- located_in(toronto,texas).
Clause 7 ?- located_in(toronto,ontario).
Clause 4
Figure 1.3
Authors manuscript
Introducing Prolog
Chap. 1
The fundamental units of Prolog syntax are atoms, numbers, structures, and variables. We will discuss numbers and structures further in Chapter 3. Atoms, numbers, structures, and variables together are known as TERMS. Atoms are used as names of individuals and predicates. An atom normally begins with a lower-case letter and can contain letters, digits, and the underscore mark (_). The following are examples of atoms:
x georgia ax123aBCD abcd_x_and_y_a_long_example
If an atom is enclosed in single quotes, it can contain any characters whatsoever, but there are two points to note. First, a quote occurring within single quotes is normally written double. Second, in some implementations, a backslash within an atom has special signicance; for details see Appendix A and your manual. Thus, the following are also atoms:
'Florida' 'a very long atom with blanks in it' '12$12$' ' a' slashes' 'don''t worry' 'back
In fact, '32' is an atom, not equal to the number 32. Even '' is an atom (the empty atom), although it is rarely used. Atoms composed entirely of certain special characters do not have to be written between quotes; for example, -- (without quotes) is a legitimate atom. (We will explore this feature further in Chapter 6.) There is usually no limit on the length of an atom, with or without quotes, but check the manual for your implementation to be sure. A structure normally consists of an atom, an opening parenthesis, one or more arguments separated by commas, and a closing parenthesis. However, an atom by itself is, strictly speaking, also a structure, with no arguments. All of the following are structures:
ab,c,d located_inatlanta,texas located_inX,georgia mother_ofcathy,melody 'a Weird!?! Atom'xxx,yyy,zzz i_have_no_arguments
Authors manuscript
Sec. 1.7.
Prolog Syntax
15
The atom at the beginning is called the FUNCTOR of the structure. (If some of the arguments are also structures, then the functor at the beginning of the whole thing is called the PRINCIPAL FUNCTOR.) So far we have used structures only in queries, facts, and rules. In all of these, the functor signied the name of a predicate. Functors have other uses which we will meet in Chapter 3. Actually, even a complete rule is a structure; the rule
aX :- bX.
The functor :- is called an INFIX OPERATOR because it is normally written between its arguments rather than in front of them. In Chapter 6 we will see how to create other functors with this special feature. Variables begin with capital letters or the underscore mark, like these:
A _howdy Result _12345 Which_Ever Xx
A variable name can contain letters, digits, and underscores. Prolog knowledge bases are written in free format. That is, you are free to insert spaces or begin a new line at any point, with two restrictions: you cannot break up an atom or a variable name, and you cannot put anything between a functor and the opening parenthesis that introduces its arguments. That is, in place of
located_inatlanta,georgia.
but not
located_in at lanta,georgia. two syntax errors!
Most implementations of Prolog require all the clauses for a particular predicate to be grouped together in the le from which the clauses are loaded. That is, you can say
mothermelody,cathy. mothereleanor,melody. fathermichael,cathy. fatherjim,melody.
but not
Authors manuscript
16
mothermelody,cathy. fathermichael,cathy. mothereleanor,melody. fatherjim,melody.
Introducing Prolog
wrong!
Chap. 1
The results of violating this rule are up to the implementor. Many Prologs do not object at all. Quintus Prolog gives warning messages, but loads all the clauses properly. A few Prologs ignore some of the clauses with no warning. See Appendices A and B for more information about discontiguous sets of clauses.
Exercise 1.7.1 Identify each of these as an atom, number, structure, variable, or not a legal term:
asdfasdf Xy,z 234 in_out_ fa,b 'X'XX _on 'X'
Exercise 1.7.2 What are the two syntax errors in the following?
located_in at lanta,georgia.
Exercise 1.7.3 What does your Prolog system do if the clauses for a predicate are not grouped together? Does it give an error or warning message? Does it ignore any of the clauses? Experiment and see.
More importantly, we can dene other relations in terms of the ones already dened. For example, lets dene parent. A parent of X is the father or mother of X. Since there are two ways to be a parent, two rules are needed:
parentX,Y :- fatherX,Y. parentX,Y :- motherX,Y.
Authors manuscript
Sec. 1.8.
Dening Relations
17
File FAMILY.PL Part of a family tree expressed in Prolog In father 2, mother 2, and parent 2, first arg. is parent and second arg. is child. fathermichael,cathy. fathermichael,sharon. fathercharles_gordon,michael. fathercharles_gordon,julie. fathercharles,charles_gordon. fatherjim,melody. fatherjim,crystal. fatherelmo,jim. fathergreg,stephanie. fathergreg,danielle. mothermelody,cathy. mothermelody,sharon. motherhazel,michael. motherhazel,julie. mothereleanor,melody. mothereleanor,crystal. mothercrystal,stephanie. mothercrystal,danielle. parentX,Y :- fatherX,Y. parentX,Y :- motherX,Y.
Figure 1.4
Authors manuscript
18
Introducing Prolog
Chap. 1
These two rules are alternatives. The computer will try one of them and then, if it doesnt work or if alternative solutions are requested, back up and try the other. If we ask
?- parentX,michael.
we get X=charles_gordon, using the rst denition of parent, and then X=hazel, using the second denition.
Exercise 1.8.1 Make a diagram like Figure 1.3 showing how Prolog answers the query
?- parentX,danielle.
using FAMILY.PL as the knowledge base. Exercise 1.8.2 Make a modied copy of FAMILY.PL using information about your own family. Make sure that queries to mother, father, and parent are answered correctly.
In English: Find F and G such that F is the father of Michael and G is the father of F . The computers task is to nd a single set of variable instantiations that satises both parts of this compound goal. It rst solves fatherF,michael, instantiating F to charles_gordon, and then solves fatherG,charles_gordon, instantiating G to charles. This is consistent with what we said earlier about variable instantiations because F and G occur in the same invocation of the same clause. Well get exactly the same answer if we state the subgoals in the opposite order:
?- fatherG,F, fatherF,michael. F = charles_gordon G = charles
In fact, this is intuitively easier to follow because G, F, and michael are mentioned in chronological order. However, it slows down the computation. In the rst subgoal, G and F are both uninstantiated, so the computer can instantiate them by using any clause that says someone is someones father. On the rst try, it uses the very rst clause in the knowledge base, which instantiates G to michael and F to cathy. Then it gets to the second subgoal and discovers that Cathy is not Michaels father, so it has to back up. Eventually it gets to fathercharles_gordon,charles and can proceed. The way we originally stated the query, there was much less backtracking because the computer had to nd the father of Michael before proceeding to the second
Authors manuscript
Sec. 1.10.
19
subgoal. It pays to think about the search order as well as the logical correctness of Prolog expressions. We will return to this point in Chapter 4. We can use compound goals in rules, as in the following denition of grandfather:
grandfatherG,C :- fatherF,C, fatherG,F. grandfatherG,C :- motherM,C, fatherG,M.
The comma is pronounced and in fact, there have been Prolog implementations that write it as an ampersand (&).
Exercise 1.9.1 Add the predicates grandfather, grandmother, and grandparent to FAMILY.PL. (Hint: You will nd parent useful.) Verify that your new predicates work correctly.
However, the normal way to express an or relation in Prolog is to state two rules, not one rule with a semicolon in it. The semicolon adds little or no expressive power to the language, and it looks so much like the comma that it often leads to typographical errors. In some Prologs you can use a vertical bar, |, in place of a semicolon; this reduces the risk of misreading. If you do use semicolons, we advocate that you use parentheses and/or distinctive indentation to make it crystalclear that they arent commas. If there are no parentheses to indicate otherwise, the semicolon has wider scope than the comma. For example,
fX :- aX, bX; cX, dX.
is equivalent to
fX :- aX, bX; cX, dX.
and means, To satisfy fX, nd an X that satises either aX and bX, or else cX and dX. The parentheses make it easier to understand. OKeefe (1990:101) recommends that, instead, you should write:
fX :- aX, bX ; cX, dX .
Authors manuscript
20
Introducing Prolog
Chap. 1
to make the disjunction really prominent. In his style, the parentheses call attention to the disjunction itself, and the scope of the ands and ors is represented by rows and columns. But as a rule of thumb, we recommend that instead of mixing semicolons and commas together in a single predicate denition, you should usually break up the complex predicate into simpler ones.
Exercise 1.10.1 Go back to GEO.PL and add the predicate eastern 1, dened as follows: a place is eastern if it is in Georgia or in Ontario. Implement this predicate two different ways: rst with a semicolon, and then without using the semicolon. Exercise 1.10.2 Dene a predicate equivalent to
fX :- aX, bX; cX, dX.
Notice that + does not require parentheses around its argument. The behavior of + is called NEGATION AS FAILURE. In Prolog, you cannot state a negative fact (Cathy is not Michaels father); all you can do is conclude a negative statement if you cannot conclude the corresponding positive statement. More precisely, the computer cannot know that Cathy is not Michaels father; all it can know is that it has no proof that she is his father. Rules can contain +. For instance, non-parent can be dened as follows:
non_parentX,Y :+ fatherX,Y, + motherX,Y.
That is, X is a non-parent of Y if X is not the father of Y and X is also not the mother of Y. In FAMILY.PL, the nonparents of Cathy are everyone except Michael and Melody. Sure enough, the following queries succeed:
Authors manuscript
Sec. 1.11.
21
And non_parent fails if its arguments are in fact a parent and his or her child:
?- non_parentmichael,cathy. no ?- non_parentmelody,cathy. no
So far, so good. But what happens if you ask about people who are not in the knowledge base at all?
?- non_parentdonald,achsa. yes
Wrong! Actually, Donald (another of the authors of this book) is the father of Achsa, but FAMILY.PL doesnt know about it. Because the computer cant prove fatherdonald,achsa nor motherdonald,achsa, the non_parent query succeeds, giving a result that is false in the real world. Here we see a divergence between Prolog and intuitively correct thinking. The Prolog system assumes that its knowledge base is complete (e.g., that there arent any fathers or mothers in the world who arent listed). This is called the CLOSEDWORLD ASSUMPTION. Under this assumption, + means about the same thing as not. But without the closedworld assumption, + is merely a test of whether a query fails. Thats why many Prolog users refuse to call + not, pronouncing it cannotprove or failif instead. Note also that a query preceded by + never returns a value for its variables. You might think that the query
?+ fatherX,Y.
would instantiate X and Y to two people, the rst of which is not the father of the second. Not so. To solve + fatherX,Y, the computer attempts to solve fatherX,Y and then fails if the latter goal succeeds or succeeds if the latter goal fails. In turn, fatherX,Y succeeds by matching a clause in the knowledge base. So + fatherX,Y has to fail, and because it fails, it does not report variable instantiations. As if this were not enough, the order of subgoals in a query containing + can affect the outcome. Lets add the fact
blue_eyedcathy.
to the knowledge base. Now look at the results of the following queries:
Authors manuscript
22
?- blue_eyedX,non_parentX,Y. X = cathy yes ?- non_parentX,Y,blue_eyedX. no
Introducing Prolog
Chap. 1
The rst query succeeds because X gets instantiated to cathy before non_parentX,Y is evaluated, and non_parentcathy,Y succeeds because there are no clauses that list Cathy as a mother or father. But in the second query, X is uninstantiated when non_parentX,Y is evaluated, and non_parentX,Y fails as soon as it nds a clause that matches fatherX,Y. To make negation apply to a compound goal, put the compound goal in parentheses, and be sure to leave a space after the negation symbol. Heres a whimsical example:2
blue_eyed_non_grandparentX :blue_eyedX, + parentX,Y, parentY,Z.
That is, youre a blueeyed nongrandparent if you are blueeyed, and you are not the parent of some person Y who is in turn the parent of some person Z. Finally, note that + (with its usual Prolog meaning) can appear only in a query or on the right-hand side of a rule. It cannot appear in a fact or in the head of a rule. If you say
+ fathercathy,michael. wrong!
you are not denying that Cathy is Michaels father; you are merely redening the builtin predicate +, with no useful effect. Some Prolog implementations will allow this, with possibly unpleasant results, while others will display an error message saying that + is a builtin predicate and you cannot add clauses to it.
Exercise 1.11.1 Dene non_grandparentX,Y, which should succeed if X is not a grandparent of Y. Exercise 1.11.2 Dene young_parentX, which should succeed if X has a child but does not have any grandchildren. Make sure it works correctly; consider the case of someone who has two children, one of whom in turn has a child of her own while the other one doesnt.
Authors manuscript
Sec. 1.12.
23
If we put this rule into FAMILY.PL and then ask for all the pairs of siblings known to the computer, we get a surprise:
?- siblingX,Y. X = cathy Y = X = cathy Y = X = sharon Y = X = sharon Y = cathy sharon cathy sharon
etc.
Cathy is not Cathys sibling. Yet Cathy denitely has the same mother as Cathy. We need to rephrase the rule: X is a sibling of Y if M is the mother of X, and M is the mother of Y, and X is not the same as Y. To express not the same we need an equality test: if X and Y are instantiated to the same value, then
X == Y
etc.
But wait a minute, you say. Thats the same answer twice! We reply: No, it isnt. Remember that, as far as Prolog is concerned, siblingcathy,sharon and siblingsharon,cathy are separate pieces of knowledge. Both of them are true, so its entirely correct to get them both. Heres another example of equality testing. X is an only child if Xs mother doesnt have another child different from X. In Prolog:
only_childX :- motherM,X, + motherM,Y, + X == Y.
Note how the negations are nested. Given X, the rst step is to nd Xs mother, namely M. Then we test whether M has another child Y different from X. There are actually two equal predicates in Prolog. The predicate == tests whether its arguments already have the same value. The other equality predicate, =, attempts to unify its arguments with each other, and succeeds if it can do so. Thus, you can use it not only to test equality, but also to give a variable a value: X = a will unify X with a. With both arguments instantiated, = and == behave exactly alike. Its a waste of time to use an equality test if you can do the same job by simply putting a value in an argument position. Suppose for instance you want to dene a predicate parent_of_cathyX that succeeds if X is a parent of Cathy. Here is one way to express it:
Authors manuscript
24
parent_of_cathyX :- parentX,Y, Y = cathy.
Introducing Prolog
poor style
Chap. 1
That is: rst nd a person Y such that X is a parent of Y, then check whether Y is Cathy. This involves an unnecessary step, since we can get the same answer in a single step with the rule:
parent_of_cathyX :- parentX,cathy. better style
But = and == are often necessary in programs that perform input from the keyboard or a le during the computation. We can have goals such as:
?- readX, writeX, X = cathy.
This means: Instantiate X to a value read in from the keyboard, then write X on the screen, then test whether X equals cathy. It is necessary to use = or == here because we cannot predict what value X will have, and we dont want the computation to fail before printing X out. We will deal with input and output in Chapter 2.
Exercise 1.12.1 Does FAMILY.PL list anyone who satises only_child as dened above? Explain why or why not. Exercise 1.12.2 Can a query such as ?- only_childX. retrieve a value for X? Explain why or why not. If necessary, add an instance of an only child to the knowledge base in order to test this. Exercise 1.12.3 From the information in FAMILY.PL, can you tell for certain who is married to whom? Explain why or why not. Exercise 1.12.4 Add to FAMILY.PL the denitions of brother, sister, uncle, and aunt. Verify that your predicate denitions work correctly. (Hint: Recall that you have two kinds of uncles: the brothers of your parents, and the husbands of your aunts. You will need to add facts to specify who is male, who is female, and who is married to whom.)
Here the underscore mark stands for an ANONYMOUS VARIABLE, a special variable that matches anything, but never takes on a value. The values of anonymous variables are not printed out in response to a query. More importantly, successive anonymous variables in the same clause do not take on the same value; they behave as if they were different variables.
Authors manuscript
Sec. 1.14.
25
You should use an anonymous variable whenever a variable occurs only once in a clause and its value is never put to any use. For example, the rule
is_a_grandmotherX :- motherX,Y, parentY,Z.
is exactly equivalent to
is_a_grandmotherX :- motherX,Y, parentY,_.
but is less work for the computer because no value need be assigned to the anonymous variable. Here X and Y cannot be replaced with anonymous variables because each of them has to occur in two places with the same value.
Exercise 1.13.1 Modify blue_eyed_non_grandparent (above, p. 22) by putting an anonymous variable in the appropriate place. Exercise 1.13.2 Why isnt the following a proper denition of grandparent?
grandparentG,C :- parentG,_, parent_,C. wrong!
[1]
and we want to express the fact that, if X is married to Y, then Y is married to X. We might try the rule:
marriedX,Y :- marriedY,X.
[2]
Don and Jane are not in the knowledge base. Accordingly, this query does not match any of the facts in [1], so rule [2] gets invoked and the new goal becomes:
?- marriedjane,don.
Again, this does not match any of the facts in [1], so rule [2] is invoked and the new goal becomes:
?- marrieddon,jane.
Authors manuscript
26
Introducing Prolog
Chap. 1
And now were back where we started. The loop continues until the computer runs out of stack space or the user interrupts the computation. One way to prevent the loop is to have two married predicates, one for facts and one for rules. Given the facts in [1], we can dene a predicate couple 2 which, unlike married, will take its arguments in either order. The denition is as follows:
coupleX,Y :- marriedX,Y. coupleY,X :- marriedX,Y.
No loop can arise because no rule can call itself directly or indirectly; so now the query ?- coupledon,jane. fails, as it should. (Only because they are not in the knowledge base; we hasten to assure readers who know us personally that they are married!) Sometimes a rule has to be able to call itself in order to express repetition. To keep the loop from being endless, we must ensure that, when the rule calls itself, it does not simply duplicate the previous call. For an example, lets go back to FAMILY.PL and develop a denition for ancestor. One clause is easy, since parents are ancestors of their children:
ancestorX,Y :- parentX,Y.
[3]
But the relation of ancestor to descendant can span an unlimited number of generations. We might try to express this with the clause:
ancestorX,Y :- ancestorX,Z, ancestorZ,Y. wrong!
[4]
Cathy isnt an ancestor of anyone, and the query should fail. Instead, the computer goes into an innite loop. To solve the query, the computer rst tries clause [3], which fails because it cant satisfy parentcathy,Who. Then it tries clause [4], generating the new goal:
?- ancestorcathy,Z, ancestorZ,Who.
In order to solve ancestorcathy,Z the computer will do exactly the same things as for ancestorcathy,Who; in fact, since both Z and Who are uninstantiated, the new goal is in effect the same as the old one. The loop continues over and over until the computer runs out of stack space or the user interrupts the computation. We can x the problem by replacing [4] with the following:
ancestorX,Y :- parentX,Z, ancestorZ,Y.
[5]
This denition will still follow an ancestordescendant chain down an unlimited number of generations, but now it insists on nding a parentchild relation in each step before calling itself again. As a result, it never gets into endless loops. Many, though not all, transitive relations can be expressed in this way in order to prevent looping. Finally, and more obviously, Prolog can get into a loop whenever two rules call each other without imposing any additional conditions. For example:
Authors manuscript
Sec. 1.15.
27
The cure in this case is to recognize that the predicates human_being and person are equivalent, and use only one of them. It is possible to have a computation that never halts but never repeats a query. For instance, with the rules:
positive_integer1. positive_integerX :- Y is X-1, positive_integerY.
and so on.
Exercise 1.14.1 Add to FAMILY.PL the predicate relatedX,Y such that X is related to Y if X and Y have any ancestor in common but are not the same person. (Note that when you ask for all the solutions, it will be normal to get many of them more than once, because if two people have one ancestor in common, they also have earlier ancestors in common, several of whom may be in the knowledge base.) Verify that Michael and Julie are related, Cathy and Danielle are related, but Michael and Melody are not related. Exercise 1.14.2 Describe how to x positive_integer so that queries with non-integer arguments would fail rather than looping. (You havent been given quite enough Prolog to actually implement your solution yet.)
Authors manuscript
28
more complicated:
?- located inWhat,texas. ** 0 CALL: located in 0085,texas ? ** 0 EXIT: located inhouston,texas ? What = houston - ; ** 0 REDO: located inhouston,texas ? ** 0 EXIT: located inaustin,texas ? What = austin - ; ** 0 REDO: located inaustin,texas ? ** 0 FAIL: located in 0085,texas ? no
Introducing Prolog
Chap. 1
That is: to prove located_intoronto,canada, the computer rst had to prove located intoronto,ontario. Heres an example in which the backtracking is
Here _0085 denotes an uninstantiated variable. Notice that each step is marked one of four ways:
CALL marks the beginning of execution of a query; REDO means an alternative solution is being sought for a query that has already
succeeded once;
EXIT means that a query has succeeded; FAIL means that a query has failed.
If you keep hitting Return you will see all the steps of the computation. If you hit
s (for skip), the debugger will skip to the end of the current query (useful if the current query has a lot of subgoals which you dont want to see). And if you hit a
(abort), the computation will stop. To turn off the debugger, type
?- notrace.
To learn more about what the debugger can do, consult your manual.
Exercise 1.15.1 Use the debugger to trace each of the following queries: ?- located_inaustin,What. (using GEO.PL) ?- parentmichael,cathy. (using FAMILY.PL) ?- uncleWho,cathy. (using your solution to Exercise 1.12.4) ?- ancestorWho,cathy. (using FAMILY.PL with [4] and [5] from section 1.14) Describe what happens in each case.
Authors manuscript
Sec. 1.16.
29
This is not all of FAMILY.PL
parentmichael,cathy. parentmelody,cathy. parentcharles_gordon,michael. parenthazel,michael. malemichael. malecharles_gordon. femalecathy. femalemelody. femalehazel. fatherX,Y :- parentX,Y, maleX. motherX,Y :- parentX,Y, femaleX.
Is this an improvement? In one sense, denitely so, because now the information is broken down into simpler concepts. If you say mother youre asserting parenthood and femaleness at once; if you say parent and female separately, youre distinguishing these two concepts. Not only that, but now you can tell without a doubt who is female and who is male. In FAMILY.PL, you could deduce that all the mothers are female and all the fathers are male, but youd still have to state separately that Cathy is female (shes not a mother). Which style is computationally more efcient depends on the kinds of queries to be answered. FAMILY.PL can answer father and mother queries more quickly, since they do not require any inference. But the representation that takes parent as basic can answer parent queries more quickly. Unlike other knowledge representation languages, Prolog does not force the knowledge base builder to state information in a particular logical style. Information can be entered in whatever form is most convenient, and then appropriate rules can be added to retrieve the information in a different form. From the viewpoint of the user or higher- level rule issuing a query, information deduced through rules looks exactly like information entered as facts in the knowledge base. Yet another style is sometimes appropriate. We could use a data-record format to encode the family tree like this:
personcathy,female,michael,melody. personmichael,male,charles_gordon,hazel. personmelody,female,jim,eleanor.
Each record lists a persons name, gender, father, and mother. We then dene predicates to pick out the individual pieces of information:
maleX :- personX,male,_,_. femaleX :- personX,female,_,_. fatherFather,Child :- personChild,_,Father,_. motherMother,Child :- personChild,_,_,Mother.
Authors manuscript
30
Introducing Prolog
Chap. 1
The only advantage of this style is that the multiargument facts are often easy to generate from conventional databases, by simply printing out the data in a format that conforms to Prolog syntax. Human beings nd the datarecord format much less readable than the other formats, and it is, if anything slower to process than a set of one or twoargument facts.
Exercise 1.16.1 Databases often contain names and addresses. Take the names and addresses of two or three people and represent them as a set of Prolog facts. Many different approaches are possible; be prepared to justify the approach you have taken.
Authors manuscript
Chapter 2
31
Authors manuscript 693 ppid September 9, 1995 Prolog Programming in Depth
32
Chap. 2
base, and for controlling the backtracking process. The programs in this chapter will contain both a knowledge base and a set of procedures. For brevity, we will usually use a trivially simple knowledge base. Bear in mind, however, that the powerful knowledge base construction techniques from the previous chapter are equally usable here. The inputoutput predicates introduced in this chapter are those of Edinburgh Prolog. It is expected that commercial implementations will continue to support them even though the inputoutput system of ISO Prolog is not entirely the same. Well look at the ISO Prolog inputoutput system in Chapter 5; it is described in detail in Appendix A.
Recall that yes is printed after every successful query. We often use write to print out a value obtained by instantiating a variable:
?- motherX,cathy, write'The mother of Cathy is ', writeX. The mother of Cathy is melody yes
Notice that melody is written in all lower case, just as in the knowledge base. If its argument is an uninstantiated variable, write displays a symbol such as _0001, uniquely identifying the variable but not giving its name. Try a query such as
?- writeX.
to see what uninstantiated variables look like in your implementation. Notice that write displays quoted atoms, such as 'Hello there', without the quotes. The omission of quotes means that terms written onto a le by write cannot easily be read back in using Prolog syntax. If you write 'hello there' you get hello there, which will be read back in as two atoms, not one. To solve this problem, Prolog offers another predicate, called writeq, that includes quotes if they would be needed for reading the term back in:
Authors manuscript
Sec. 2.2.
33
Another predicate, called display, puts all functors in front of their arguments even if they were originally written in other positions. This makes display useful for investigating the internal representation of Prolog terms. For example:
?- display2+2. +2,2 yes
This shows that + is an inx operator. We will deal with arithmetic operators in Chapter 3. For now, be aware that 2+2 does not represent the number 4; it is a data structure consisting of a 2, a +, and another 2. Still another predicate, write_canonical, combines the effects of writeq and display:
?- write_canonical2+3. +2,3 ?- write_canonical'hello there'. 'hello there'
Not all Prologs have write_canonical; Quintus Prolog and the ISO standard include it.
Exercise 2.2.1 Predict the output of each of the following queries, then try the queries on the computer to conrm your predictions:
??????????????writeaaa, writebbb. writeaaa, nl, writebbb. writeqaaa. displayaaa. write'don''t panic'. writeq'don''t panic'. display'don''t panic'. writeDontpanic. writeqDontpanic. displayDontpanic. write3.14159*2. display3.14159*2. write'an example'. display'an example'.
Also try out write_canonical if your implementation supports it. If youre bursting with curiosity about how to do arithmetic in Prolog, try this query:
?- What is 3.14159*2.
Authors manuscript
Chap. 2
Its important to distinguish queries that perform inputoutput operations from queries that dont. For example, the query
?- motherX,cathy, writeX.
tells the computer to gure out who is the mother of Cathy and print the result. By contrast, the query
?- motherX,cathy.
tells the computer to identify the mother of Cathy, but does not say to print anything. If you type the latter query at the Prolog prompt, the value of X will get printed, because the Prolog system always prints the values of variables instantiated by queries that have not performed any output of their own. But its important to understand that mother 2 isnt doing the printing; the Prolog user interface is. A common mistake is to construct a predicate that prints something when you were assigned to construct a predicate that computes it, or vice versa. Normally, in Prolog, any predicate that does a computation should deliver the result by instantiating an argument, not by writing on the screen directly. That way, the result can be passed to other subgoals in the same program.
Exercise 2.3.1 Add to FAMILY.PL the following two predicates: A predicate cathys_fatherX that instantiates X to the name of Cathys father. A predicate print_cathys_father (with no arguments) that writes the name of Cathys father on the screen.
will display information about the rst state it nds. A few Prolog systems will then invite you to type ; to get alternative solutions, but most Prologs will not do this, because they assume that if you used write, you must have already written out whatever it was that you wanted to see. Thats where fail comes in. To print out all the alternatives, you can phrase the query like this:
Authors manuscript
Sec. 2.4.
35
File CAPITALS.PL or KB.PL Knowledge base for several examples in Chapter 2 :- dynamiccapital_of 2. Omit this line if your Prolog does not accept it.
Figure 2.1
?- capital_ofState,City,writeCity, write' is the capital of ',writeState,nl,fail. atlanta is the capital of georgia sacramento is the capital of california tallahassee is the capital of florida augusta is the capital of maine no
In place of fail you could have used any predicate that fails, because any failure causes Prolog to back up to the most recent untried alternative. The steps in the computation are as follows: 1. Solve the rst subgoal, capital_ofState,City, by instantiating State as georgia and City as atlanta. 2. Solve the second, third, fourth, and fth subgoals (the three writes and nl) by writing atlanta is the capital of georgia and starting a new line. 3. Try to solve the last subgoal, fail. This subgoal cannot be solved, so back up. 4. The most recent subgoal that has an alternative is the rst one, so pick another state and city and try again. Figure 2.2 shows part of this process in diagrammatic form. Notice that the
writes are executed as the computer tries each path that passes through them,
whether or not the whole query is going to succeed. In general, a query does not have to succeed in order to perform actions. We say that write has the SIDE EFFECT that whenever it executes, something gets written to the screen, regardless of whether the whole query is going to succeed. Notice also that, upon hitting fail, the computer has to back up all the way back to capital_ofState,City to get an alternative. It is then free to move forward through the writes again, since it is now on a different path. Inputoutput predicates such as write, writeq, nl, and display do not yield alternative solutions upon backtracking. For instance, the query
Authors manuscript
36
Chap. 2
?- write(atlanta).
?- write(sacramento).
?- write(tallahassee).
?- write(georgia).
?- write(california).
?- write(florida).
?- nl.
?- nl.
?- nl.
?- fail.
?- fail.
?- fail.
Figure 2.2
?- write'hello',fail.
writes hello only once. That is, write, writeq, nl, and display are DETERMINISTIC (or, as some textbooks express it, they CANNOT BE RESATISFIED).
Exercise 2.4.1 Take the rst example of fail given above, and replace fail with some other query that will denitely fail. What happens? Exercise 2.4.2 In your Prolog system, what happens if you try to query a predicate that doesnt exist? Does the query fail, or do you get an error message? Experiment and nd out. Exercise 2.4.3 Recall that CAPITALS.PL does not list Idaho. Assuming that CAPITALS.PL has been consulted, what is output by each of the following two queries? Explain the reason for the difference.
?- capital_ofidaho,C, write'The capital of Idaho is ', writeC. ?- write'The capital of Idaho is ', capital_ofidaho,C, writeC.
Exercise 2.4.4 Using FAMILY.PL and your knowledge from Chapter 1, construct a query that will print out the names of all the ancestors of Cathy, like this:
Authors manuscript
Sec. 2.5.
Predicates as Subroutines
37
will have the same effect as the much longer query that it stands for. In effect, the rule denes a subroutine; it makes it possible to execute all the subgoals of the original query by typing a single name. In this case, there are advantages to dening two subroutines, not just one:
print_a_capital :- capital_ofState,City, writeCity, write' is the capital of ', writeState, nl. print_capitals :print_a_capital, fail.
This makes the program structure clearer by splitting apart two conceptually separate operations printing one state capital in the desired format, and backtracking through all alternatives. Predicate denitions in Prolog correspond to subroutines in Fortran or procedures in Pascal. From here on we will often refer to Prolog predicate denitions as PROCEDURES. Theres one more subtlety to consider. Any query to print_capitals will ultimately fail (although it will print out a lot of useful things along the way). By adding a second clause, we can make print_capitals end with success rather than failure:
print_capitals :- print_a_capital, fail. print_capitals. Clause 1
Clause 2
Authors manuscript
38
Chap. 2
Now any query to print_capitals will backtrack through all the solutions to print_a_capital, just as before. But then, after the rst clause has run out of solutions, execution will backtrack into the second clause, which succeeds without doing anything further.
Exercise 2.5.1 Get print_capitals working on your computer. Try the query
?- print_capitals, write'All done.'.
with and without Clause 2. What difference does Clause 2 make? Exercise 2.5.2 Go back to FAMILY.PL and your solution to Exercise 2.4.4. Dene a predicate called print_ancestors_of that takes one argument (a persons name) and prints out the names of all the known ancestors of that person, in the same format as in Exercise 2.4.4.
(typed by user)
(typed by user)
(typed by user)
Crucially, if the period is left out, the computer will wait for it forever, accepting line after line of input in the hope that the period will eventually be found. If the argument of read is already instantiated, then read will try to unify that argument with whatever the user types, and will succeed if the unication succeeds, and fail if the unication fails:
?- readhello. hello. yes ?- readhello. goodbye. no
(typed by user)
(typed by user)
Authors manuscript
Sec. 2.6.
39
File INTERAC.PL Simple interactive program capital_ofgeorgia,atlanta. capital_offlorida,tallahassee. go :- write'What state do you want to know about?',nl, write'Type its name, all lower case, followed by a period.',nl, readState, capital_ofState,City, write'Its capital is: ',writeCity,nl.
Figure 2.3
An interactive program.
Note in particular that readyes will succeed if the user types yes. and fail if the user types anything else. This can be a handy way to get answers to yesno questions. With read, the user can type any legal Prolog term, no matter how complex:
?- readX. mothermelody,cathy. X = mothermelody,cathy yes
Exactly as in programs, unquoted terms that begin with upper case letters are taken to be variables:
?- readX. A. X = _0091 yes ?- readX. fY :- gY. X = f_0089 :- g_0089 yes
(typed by user)
(typed by user)
Here _0091 and _0089 stand for uninstantiated variables. Like write, writeq, nl, and display, read is deterministic, i.e., it does not yield alternative solutions upon backtracking. Figure 2.3 shows a program, INTERAC.PL, that uses read to interact with the user. A dialogue with INTERAC.PL looks like this:
?- go. What state do you want to know about? Type its name, all lower case, followed by a period: florida. Its capital is: tallahassee
Authors manuscript
40
Chap. 2
The need to follow Prolog syntax can be a real inconvenience for the user. The period is easy to forget, and bizarre errors can result from uppercase entries being taken as variables. In Chapter 5 we will show you how to get around this. In the meantime, note that read makes a good quick-and-dirty substitute for more elaborate input routines that will be added to your program later. Also, consult your manual for more versatile input routines that your implementation may supply.
Exercise 2.6.1 Try out INTERAC.PL. (Consult it and type ?- go. to start it.) What happens if you begin the name of the state with a capital letter? Explain why you get the results that you do. Exercise 2.6.2 If you wanted to mention South Carolina when running INTERAC.PL, how would you have to type it? Exercise 2.6.3 Using FAMILY.PL, write an interactive procedure find_mother (with no arguments) that asks the user to type a persons name, then prints out the name of that persons mother. Exercise 2.6.4 What does readyes do if the user responds to it by typing each of the following? Does it succeed, fail, or crash with an error message? Why?
yes. no. Yes. No. y. n. y e s.
Exercise 2.6.5 Does read ignore comments in its input? Try it and see.
Authors manuscript
Sec. 2.7.
41
inserts capital_ofhawaii,honolulu immediately before the other clauses for capital_of, and
?- assertzcapital_ofwyoming,cheyenne.
adds a fact at the end of the clauses for capital_of. The argument of retract is either a complete clause, or a structure that matches the clause but contains some uninstantiated variables. The predicate must be instantiated and have the correct number of arguments. For example,
?- retractmothermelody,cathy.
nds the rst clause that matches motherX,Y and removes it, instantiating X and Y to the arguments that it found in that clause. If there is no clause matching motherX,Y, then retract fails. Extra parentheses are required when the argument of asserta, assertz, or retract contains a comma or an if operator:
?- assertamaleX :- fatherX. ?- assertacan_flyX :- birdX, ?- retractparentX,Y :- Z. + penguinX.
The parentheses make it clear that the whole clause is just one argument. The effects of assert and retract are not undone upon backtracking. These predicates thus give you a permanent way to store information. By contrast, variable instantiations store information only temporarily, since variables lose their values upon backtracking. (But assert and retract modify only the knowledge base in memory; they dont affect the disk le from which that knowledge base was loaded.) The predicate abolish removes all the clauses for a particular predicate with a particular arity, and succeeds whether or not any such clauses exist:1
?- abolishmother 2.
And to see only a particular predicate, type (for example) ?- listingmother. or ?- listingmother 2. Note that listing is not in the ISO standard, and its exact behavior varies somewhat from one implementation to another.
Exercise 2.7.1 What would be in the knowledge base if you started with it empty, and then performed the following queries in the order shown?
1 In
Authors manuscript
42
?????assertagreenkermit. assertzgraygonzo. assertagreenbroccoli. assertzgreenasparagus. retractgreenX.
Chap. 2
Predict the result, then try the queries and use listing to see if your prediction was right. Exercise 2.7.2 What does the following Prolog code do?
:- dynamicf 0. Omit this line if your Prolog does not accept it
test :- f, write'Not the first time'. test :+ f, assertaf, write'The first time'.
Try the query ?- test. several times and explain why it does not give the same result each time.
to tell the Prolog system that the predicate capital_of 2 (or whatever) should be stored in a way that allows you to assert and retract its clauses. Thats the reason for the dynamic declaration in CAPITALS.PL (page 35). As you might guess, were going to be asserting some additional clauses into capital_of at run time. Dynamic declarations have another effect, too: they tell the Prolog system not to worry if you try to query a predicate that doesnt exist yet. In many Prologs, a query like
?- fa,b.
Authors manuscript
Sec. 2.9.
43
will raise an error condition if there are no clauses for f 2 in the knowledge base. The computer has, of course, no way of knowing that you are going to assert some clauses later and you just havent gotten around to it. But if you have declared :- dynamicf 2. then the query above will simply fail, without raising an error condition. Finally, note that abolish wipes out not only a predicate, but also its dynamic declaration, if there is one. To retract all the clauses for a predicate without wiping out its dynamic declaration, you could do something like this:
clear_away_my_predicate :- retractf_,_, fail. clear_away_my_predicate :- retractf_,_ :- _, fail. clear_away_my_predicate.
That is: Retract all the facts that match f_,_, then retract all the rules that begin with f_,_, and nally succeed with no further action.
Exercise 2.8.1 Does your Prolog allow you to use dynamic declarations? If so, do they affect whether or not you can assert and retract clauses? Try consulting CAPITALS.PL and then performing the queries:
?- retractcapital_ofX,Y. ?- assertzcapital_ofkentucky,frankfort.
Exercise 2.8.2 In your Prolog, does listing show all the predicates, or only the dynamic ones? State how you found out. Exercise 2.8.3 Does your Prolog let you use compile as an alternative to consult or reconsult? If so, does it affect whether predicates are static or dynamic?
Authors manuscript
44
Chap. 2
the messages Starting... and Finished will appear at the beginning and the end of the consulting process, respectively. (A few Prologs use ?- instead of :-, and some Prologs take either one.) Can you use an embedded query to make your program start executing the moment it is loaded? Possibly. We often did this in the previous edition of this book, but we no longer recommend it because it is not compatible with all Prologs. The question of how to start execution really arises only when you are compiling your Prolog program into a standalone executable (an .EXE le or the like), and the manual for your compiler will tell you how to specify a starting query. For portable programs that are to be run from the query prompt, you could embed a query that gives instructions to the user, such as
:- write'Type ''go.'' to start.'.
at the end of the program le. In this book, we will often use the names go or start for the main procedure of a program, but this is just our choice; those names have no special signicance in Prolog. The difference between consult and reconsult, as we noted in Chapter 1, is that upon encountering the rst clause for each predicate in the le, reconsult throws away any preexisting denitions of that predicate that may already be in the knowledge base. Thus, you can reconsult the same le over and over again without getting multiple copies of it in memory. In fact, some Prologs no longer maintain this distinction; in Quintus Prolog, for example, consult is simply another name for reconsult. And in SWIProlog, consult acts like the old reconsult, and reconsult doesnt exist. One very good use of embedded queries is to include one Prolog le into another. Suppose FILE1.PL contains a predicate that you want to use as part of FILE2.PL. You can simply insert the line
:- reconsult'file1.pl'.
near the top of FILE2.PL. Then, whenever you consult or reconsult FILE2.PL, FILE1.PL will get reconsulted as well (provided, of course, it is in your current directory!). Better yet, if your Prolog permits it, use the embedded query
:- ensure_loaded'file1.pl'.
which will reconsult FILE1.PL only if it is not already in memory at the time. Quintus Prolog and the ISO standard support ensure_loaded, but in order to accommodate other Prologs, we will generally use reconsult in this book. Finally, what if the clauses for a single predicate are spread across more than one le? Recall that reconsult will discard one set of clauses as soon as it starts reading the other one. To keep it from doing so, you can use a declaration like this:
:- multifilecapital_of 2.
That is: Allow clauses for capital_of 2 to come from more than one le. This declaration must appear in every le that contains any of those clauses. At least, thats how its done in Quintus Prolog and in the ISO standard; consult your manual to nd out whether this applies to the Prolog that you are using.
Authors manuscript
Sec. 2.10.
Exercise 2.9.1
45
Does your Prolog support embedded queries beginning with :-? With ?-? Experiment and see. Exercise 2.9.2 By experiment, nd out whether your Prolog supports ensure_loaded and whether it supports multifile.
As long as a le is open, the computer keeps track of the position at which the next term will be read. By calling see repeatedly, you can switch around among several les that are open at once. To switch to the keyboard without closing the other input les, use seeuser. Thus:
?- see'aaa', readX1, see'bbb', readX2, seeuser, readX3, see'aaa', readX4, seen. read first term from AAA read first term from BBB read a term from the keyboard read second term from AAA close all input files
On attempting to read past the end of a le, read returns the special atom end_of_file ('!EOF' in Cogent Prolog). If the attempt is repeated, some implementations return end_of_file over and over, and some raise an error condition. The predicate tell opens a le for output and switches output to that le; told closes output les and switches output back to the console. Here is how to create a le called YOURDATA and write Hello there on it:
Authors manuscript
46
?- tell'yourdata', write'Hello there', nl, told.
Chap. 2
The biggest disadvantage of tell is that if something goes wrong, the error messages appear on the le, not the screen. Likewise, if something goes wrong while see is in effect, you may not be able to make the computer accept any input from the keyboard. In general, see, seen, tell, and told are barely adequate as a le handling system; we will use them often in this book because of their great portability, but you should jump at every chance to use a better le inputoutput system (implementation specic or ISO standard as the case may be).
Exercise 2.10.1 Use the following query to create a text le:
?- tellmyfile, writegreenkermit, write'.', nl, writegreenasparagus, write'.', nl, told.
What gets written on the le? Exercise 2.10.2 Construct a query that will read both of the terms from the le you have just created.
Authors manuscript
Sec. 2.11.
47
the user to name the capital and stores the information in its knowledge base. The knowledge base is stored on a separate le called KB.PL, which is initially a copy of CAPITALS.PL but gets rewritten every time the user terminates the program. A dialogue with LEARNER.PL looks like this:
?- start. Type names all in lower case, followed by period. Type "stop." to quit. State? georgia. The capital of georgia is atlanta State? hawaii. I do not know the capital of that state. Please tell me. Capital? honolulu. Thank you. State? maine. The capital of maine is augusta State? hawaii. The capital of hawaii is honolulu State? stop. Saving the knowledge base... Done.
Notice that the program has learned what the capital of Hawaii is. The learning is permanent if you run the program again and ask for the capital of Hawaii, you will henceforth get the correct answer. LEARNER.PL uses three predicates, start, process_a_query, and answer. Its structure is a recursive loop, since process_a_query calls answer and, under most conditions, answer then calls process_a_query. In Pascal or a similar language, this kind of loop would be very bad form, but in Prolog, it is one of the normal ways of expressing repetition. Further, as we will see in Chapter 4, the program can be modied so that the recursive calls do not consume stack space. The predicate start simply loads the knowledge base (using reconsult so that the program can be run again and again with impunity), prints the introductory message, and calls process_a_query for the rst time. Then process_a_query asks the user to name a state, accepts a term as input, and passes it to answer. The predicate answer does one of three things, depending on its argument. If the argument is stop, it saves a new copy of the knowledge base that contains any information added during the run, then prints Done and terminates successfully. Otherwise, if the argument is a state that can be found in the knowledge base, answer looks up the capital and writes it on the screen. If the argument is a state that is not in the knowledge base, answer asks the user for the requisite information,
Authors manuscript
48
Chap. 2
constructs the appropriate fact, and adds it using assertz. In either of these latter cases answer then calls process_a_query to begin the cycle anew.
Exercise 2.11.1 Get LEARNER.PL working on your computer and conrm that it performs as described. In particular, conrm that LEARNER.PL remembers what it has learned even after you exit Prolog completely and start everything afresh. What does KB.PL look like after several states and capitals have been added? Exercise 2.11.2 In LEARNER.PL, what is the effect of the following line?
write':- dynamiccapital_of 2.',nl,
Why is it needed?
Here 42 is the ASCII code for the asterisk. You can use put to output not only printable characters, but also special effects such as code 7 (beep), code 8 (backspace), code 12 (start new page on printer), or code 13 (return without new line). ASCII stands for American Standard Code for Information Interchange. Table 2.1 lists the 128 ASCII characters; some computers, including the IBM PC, use codes 128 to 255 for additional special characters. IBM mainframe computers use a different set of codes known as EBCDIC. The opposite of put is get. That is, get accepts one character and instantiates its argument to that characers ASCII code, like this:
?- getX. * X = 42
(typed by user)
And here you will encounter a distinction between buffered and unbuffered keyboard input. In the example just given, some Prologs will execute getX the moment you type the asterisk. But most Prologs wont see the asterisk until you have also hit Return. We describe the keyboard as BUFFERED if the program does not receive any input until you hit Return, or UNBUFFERED (RAW) if all incoming keystrokes are available to the program immediately. Note that get skips any blanks, returns, or other nonprinting characters that may precede the character it is going to read. If you want to read every keystroke that comes in, or every byte in a le, use get0 instead. For example, if you type
?- get0X, get0Y.
Authors manuscript
Sec. 2.12.
49
File LEARNER.PL Program that modifies its own knowledge base This program requires file KB.PL, which should be a copy of CAPITALS.PL. reconsult'kb.pl', nl, write'Type names entirely in lower case, followed by period.', nl, write'Type "stop." to quit.', nl, nl, process_a_query.
start :-
process_a_query :- write'State? ', readState, answerState. If user typed "stop." then save the knowledge base and quit. answerstop :write'Saving the knowledge base...',nl, tell'kb.pl', write':- dynamiccapital_of 2.',nl, omit if not needed listingcapital_of, told, write'Done.',nl.
If the state is in the knowledge base, display it, then loop back to process_a_query answerState :capital_ofState,City, write'The capital of ', writeState, write' is ', writeCity,nl, nl, process_a_query.
If the state is not in the knowledge base, ask the user for information, add it to the knowledge base, and loop back to process_a_query answerState :+ capital_ofState,_, write'I do not know the capital of that state.',nl, write'Please tell me.',nl, write'Capital? ', readCity, write'Thank you.',nl,nl, assertzcapital_ofState,City, process_a_query.
Figure 2.4
Authors manuscript
50
Chap. 2
TABLE 2.1
ASCII CHARACTER SET, WITH DECIMAL NUMERIC CODES Ctrl-@ Ctrl-A Ctrl-B Ctrl-C Ctrl-D Ctrl-E Ctrl-F Ctrl-G Backspace Tab Ctrl-J Ctrl-K Ctrl-L Return Ctrl-N Ctrl-O Ctrl-P Ctrl-Q Ctrl-R Ctrl-S Ctrl-T Ctrl-U Ctrl-V Ctrl-W Ctrl-X Ctrl-Y Ctrl-Z Escape CtrlCtrlCtrl-^ Ctrl-_
@ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z ` a b c d e f g h i j k l m n o p q r s t u v w x y z | ~
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63
Space
! " $ & ' * + , . 0 1 2 3 4 5 6 7 8 9 : ; = ?
64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95
^ _
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
Delete
Authors manuscript
Sec. 2.13.
Constructing Menus
51
and type * and Return, youll see the code for * (42) followed by the code for Return (13 or 10 depending on your implementation). In the ISO standard, put and get0 are called put_code and get_code respectively; get is not provided, but you can dene it as:
getCode :- repeat, get_codeCode, Code 32, !.
The use of repeat and ! (pronounced cut) will be discussed in Chapter 4. As you may surmise, get0 and put are used mainly to read arbitrary bytes from les, send arbitrary control codes to printers, and the like. Well explore byte bybyte le handling in Chapter 5. On trying to read past end of le, both get and get0 return -1 (except in Arity Prolog, in which they simply fail, and Cogent Prolog, in which they return the atom '!EOF').
Exercise 2.12.1 What does the following query do? Explain, step by step, what happens.
?- writehello, put13, writebye.
Exercise 2.12.2 Is Prolog keyboard input on your computer buffered or unbuffered? Explain how you found out. Exercise 2.12.3 When you hit Return, does get0 see code 10, code 13, or both? Explain how you found out.
Similar menus can be used in other types of programs. Note that MENUDEMO.PL reads each response by executing both get and get0, like this:
get_from_menuState :getCode, read a character get0_, consume the Return keystroke interpretCode,State.
Authors manuscript
52
Chap. 2
File MENUDEMO.PL Illustrates accepting input from a menu Knowledge base capital_ofgeorgia,atlanta. capital_ofcalifornia,sacramento. capital_offlorida,tallahassee. capital_ofmaine,augusta. Procedures to interact with user start :display_menu, get_from_menuState, capital_ofState,City, nl, write'The capital of ', writeState, write' is ', writeCity, nl.
display_menu :- write'Which state do you want to know about?',nl, write' 1 Georgia',nl, write' 2 California',nl, write' 3 Florida',nl, write' 4 Maine',nl, write'Type a number, 1 to 4 -- '. get_from_menuState :getCode, read a character get0_, consume the Return keystroke interpretCode,State. * * * * ASCII ASCII ASCII ASCII 49 50 51 52 = = = = '1' '2' '3' '4' * * * *
Figure 2.5
Authors manuscript
Sec. 2.13.
Constructing Menus
53
File GETYESNO.PL Menu that obtains 'yes' or 'no' answer get_yes_or_noResult :- getChar, read a character get0_, consume the Return after it interpretChar,Result, !. cut -- see text get_yes_or_noResult :- nl, put7, beep write'Type Y or N:', get_yes_or_noResult. interpret89,yes. interpret121,yes. interpret78,no. interpret110,no. ASCII ASCII ASCII ASCII 89 121 78 110 = = = = 'Y' 'y' 'N' 'n'
Figure 2.6
Here getCode skips any preceding nonprinting codes, then reads the digit 1, 2, 3, or 4 typed by the user. Then get0_ reads the Return keystroke that follows the letter. If your Prolog accesses the keyboard without buffering, you can remove get0_ and the user will get instant response upon typing the digit. The kind of menu that well use most often is one that gets a yes or no answer to a question, and wont accept any other answers (Fig. 2.6, le GETYESNO.PL). The idea is that from within a program, you can execute a query such as
?- get_yes_or_noResponse.
and Response will come back instantiated to yes if the user typed y or Y, or no if the user typed n or N. And if the user types anything else, he or she gets prompted to type Y or N. The rst clause of get_yes_or_no reads a character, then calls interpret to translate it to yes or no. If the user typed y, Y, n, or N, the call to interpret succeeds, and get_yes_or_no then executes a cut (written !). Well introduce cuts in Chapter 4; for now, all you need to know is that cut prevents execution from backtracking into the other clause. But if the user doesnt type y, Y, n, or N, then interpret wont succeed and the cut wont get executed. In that case get_yes_or_no will backtrack into the other clause, beep, print Type Y or N, and call itself recursively to begin the whole process again.
Exercise 2.13.1 Adapt MENUDEMO.PL to use the rst letter of the name of each state, rather than the digits 14, to indicate choices.
Authors manuscript
54
Exercise 2.13.2
Chap. 2
Using get_yes_or_no, dene another predicate succeed_if_yes that asks the user to type Y or N (upper or lower case), then succeeds if the answer was Y and fails if the answer was N. Exercise 2.13.3 What would go wrong with get_yes_or_no if the cut were omitted?
Check whether there is fuel in the tank. If so, check for a clogged fuel line or filter or a defective fuel pump.
CAR.PL has two features that would be difcult to implement in a conventional programming language: it lists all possible diagnoses, not just one, and it does not ask questions unless the information is actually needed. Both of these features are exemplied in the following dialogue.
?- start. This program diagnoses why a car won't start. Answer all questions with Y for yes or N for no. When you first started trying to start the car,
Authors manuscript
Sec. 2.14.
55
did the starter crank the engine normally? n Check that the gearshift is set to Park or Neutral. Try jiggling the gearshift lever. Check for a defective battery, voltage regulator, or alternator; if any of these is the problem, charging the battery or jumpstarting may get the car going temporarily. Or the starter itself may be defective.
If the starter is obviously inoperative, the other diagnoses do not come into consideration and there is no point collecting the information needed to try them. CAR.PL has two knowledge bases. The diagnostic knowledge base species what diagnoses can be made under what conditions, and the case knowledge base describes the particular car under consideration. The diagnostic knowledge base resides in defect_may_be 1. The case knowledge base resides in stored_answer 2, whose clauses get asserted as the program runs. For convenience, we have assigned names both to the diagnoses (e.g., drained_battery) and the conditions that the user observes and reports (e.g., fuel_is_ok). Separate predicates (explain 1 and ask_question 1) display the text associated with each diagnosis or observation. The diagnoses themselves are straightforward. The battery may be drained if the starter worked originally and does not work now; the gearshift may be set incorrectly if the starter never worked; and so on. Notice that the diagnoses are not mutually exclusive in particular, wrong_gear and starting_system have the same conditions and are not arranged into any kind of logic tree or owchart. One of the strengths of Prolog is that the contents of a knowledge base need not be organized into a rigorous form in order to be usable. The case knowledge base is more interesting, since the information has to be obtained from the user, but we do not want to ask for information that is not needed, nor repeat requests for information that was already obtained when trying another diagnosis. To take care of this, the program does not call stored_answer directly, but rather calls user_says, which either retrieves a stored answer or asks a question, as appropriate. Consider what happens upon a call to user_saysfuel_is_ok,no. The rst clause of user_says immediately looks for stored_answerfuel_is_ok,no; if that stored answer is found, the query succeeds. Otherwise, there are two other possibilities. Maybe there is no stored_answerfuel_is_ok,: : : at all; in that case, user_says will ask the question, store the answer, and nally compare the answer that was received to the answer that was expected (no). But if there is already a stored_answerfuel_is_ok,: : : whose second argument is not no, the query fails and the question is not asked. The toplevel procedure try_all_possibilities manages the whole process:
Authors manuscript
56
Chap. 2
The rst clause nds a possible diagnosis that is, a clause for defect_may_be that succeeds, instantating D to some value. Then it prints the explanation for D. Next it hits fail and backs up. Since explain has only one clause for each value of D, the computation has to backtrack to defect_may_be, try another clause, and instantiate D to a new value. In this manner all possible diagnoses are found. The second clause succeeds with no further action after the rst clause has failed. This enables the program to terminate with success rather than with failure. Although small and simple, CAR.PL can be expanded to perform a wide variety of kinds of diagnosis. It is much more versatile than the owcharts or logic trees that would be required to implement a diagnostic program easily in a conventional programming language.
Exercise 2.14.1 Get CAR.PL working on your computer and demonstrate that it works as described. Exercise 2.14.2 Modify CAR.PL to diagnose defects in some other kind of machine that you are familiar with.
Authors manuscript
Sec. 2.14.
57
File CAR.PL Simple automotive expert system :- reconsult'getyesno.pl'. Main control procedures start :write'This program diagnoses why a car won''t start.',nl, write'Answer all questions with Y for yes or N for no.',nl, clear_stored_answers, try_all_possibilities. try_all_possibilities :defect_may_beD, explainD, fail. try_all_possibilities. Backtrack through all possibilities... Use ensure_loaded if available.
Diagnostic knowledge base conditions under which to give each diagnosis defect_may_bedrained_battery :user_saysstarter_was_ok,yes, user_saysstarter_is_ok,no. defect_may_bewrong_gear :user_saysstarter_was_ok,no. defect_may_bestarting_system :user_saysstarter_was_ok,no. defect_may_befuel_system :user_saysstarter_was_ok,yes, user_saysfuel_is_ok,no. defect_may_beignition_system :user_saysstarter_was_ok,yes, user_saysfuel_is_ok,yes.
Figure 2.7
Authors manuscript
58
Chap. 2
Case knowledge base information supplied by the user during the consultation :- dynamicstored_answer 2. Clauses get added as user answers questions. Procedure to get rid of the stored answers without abolishing the dynamic declaration clear_stored_answers :- retractstored_answer_,_,fail. clear_stored_answers. Procedure to retrieve the user's answer to each question when needed, or ask the question if it has not already been asked user_saysQ,A :- stored_answerQ,A. user_saysQ,A :+ stored_answerQ,_, nl,nl, ask_questionQ, get_yes_or_noResponse, assertastored_answerQ,Response, Response = A.
Texts of the questions ask_questionstarter_was_ok :write'When you first started trying to start the car,',nl, write'did the starter crank the engine normally? ',nl. ask_questionstarter_is_ok :write'Does the starter crank the engine normally now? ',nl. ask_questionfuel_is_ok :write'Look in the carburetor.
Authors manuscript
Sec. 2.14.
59
explainwrong_gear :nl, write'Check that the gearshift is set to Park or Neutral.',nl, write'Try jiggling the gearshift lever.',nl. explainstarting_system :nl, write'Check for a defective battery, voltage',nl, write'regulator, or alternator; if any of these is',nl, write'the problem, charging the battery or jump-',nl, write'starting may get the car going temporarily.',nl, write'Or the starter itself may be defective.',nl. explaindrained_battery :nl, write'Your attempts to start the car have run down the battery.',nl, write'Recharging or jump-starting will be necessary.',nl, write'But there is probably nothing wrong with the battery itself.',nl. explainfuel_system :nl, write'Check whether there is fuel in the tank.',nl, write'If so, check for a clogged fuel line or filter',nl, write'or a defective fuel pump.',nl. explainignition_system :nl, write'Check the spark plugs, cables, distributor,',nl, write'coil, and other parts of the ignition system.',nl, write'If any of these are visibly defective or long',nl, write'overdue for replacement, replace them; if this',nl, write'does not solve the problem, consult a mechanic.',nl.
End of CAR.PL
Authors manuscript
60
Chap. 2
Authors manuscript
Chapter 3
3.1. ARITHMETIC
Here are some examples of how to do arithmetic in Prolog:
?- Y is 2+2. Y = 4 yes ?- 5 is 3+3. no ?- Z is 4.5 + 3.9 Z = 6.3571428 yes 2.1.
The builtin predicate is takes an arithmetic expression on its right, evaluates it, and unies the result with its argument on the left. Expressions in Prolog look very much like those in any other programming language; consult your manual and Table 3.1 (p. 62) for details.1 The simplest expression consists of just a number; you can say
?- What is 2.
1 Older versions of Arity Prolog, and possibly some other Prologs, do not let you write an inx operator immediately before a left parenthesis. You have to write 4.5 + 3.9 2.1 (with spaces), not 4.5+3.9 2.1.
61
Authors manuscript 693 ppid September 9, 1995 Prolog Programming in Depth
62 TABLE 3.1
(Many implementations include others.) Inx operators
+ *
Chap. 3
mod
Addition Subtraction Multiplication Floatingpoint division Integer division Modulo Absolute value Square root Logarithm, base e Antilogarithm, base e Largest integer argument Nearest integer
Functions
if you want to, but its a needlessly roundabout way to do a simple thing. The precedence of operators is about the same as in other programming languages: ^ is performed rst, then * and , and nally + and -. Where precedences are equal, operations are performed from left to right. Thus 4+3*2+5 is equivalent to 4+3*2+5. Prolog supports both integers and oatingpoint numbers, and interconverts them as needed. Floatingpoint numbers can be written in E format (e.g., 3.45E-6 for 3:45 10,6 ). Notice that Prolog is not an equation solver. That is, Prolog does not solve for unknowns on the right side of is:
?- 5 is 2 + What. instantiation error wrong!
Beginners are sometimes surprised to nd that Prolog can solve for the unknown in fathermichael,Who but not in 5 is 2 + What. But think a moment about the difference between the two cases. The query fathermichael,Who can be solved by trying all the clauses that match it. The query 5 is 2 + What cant be solved this way because there are no clauses for is, and anyhow, if you wanted to do arithmetic by trying all the possible numbers, the search space would be innite in several dimensions. The only way to solve 5 is 2 + What is to manipulate the equation in some way, either algebraically (5 , 2 =What) or numerically (by doing a guided search for the right value of What). This is particularly easy to do in Prolog because is can accept an expression created at run time. We will explore numerical equation solving in Chapter 7. The point to remember, for now, is that the ordinary Prolog search strategy doesnt work for arithmetic, because there would be an innite number of numbers to try.
Authors manuscript
Sec. 3.2.
Constructing Expressions
63
Exercise 3.1.1 Try out expressions containing each of the functors in Table 3.1. Do they all work in your Prolog? Exercise 3.1.2 Use Prolog to evaluate each of the following expressions. Indicate how you did so. 234 + 567:8 3 , 0:0001 j53 , 6j 9 mod 12 Exercise 3.1.3 In your Prolog, what happens if you try to do arithmetic on an expression that contains an uninstantiated variable? Does the query simply fail, or is there an error message? Try it and see.
The other comparisons, , , = , and =, work just like =:= except that they perform different tests. Notice that we write = and =, not = and =. This is because the latter two symbols look like arrows, and the designers of standard Prolog chose to keep them undened so that you could redene them for other purposes.
Authors manuscript
64 TABLE 3.2
Chap. 3
R is Expr Expr1 =:= Expr2 Expr1 = = Expr2 Expr1 Expr2 Expr1 Expr2 Expr1 = Expr2 Expr1 = Expr2
Notice also that the arithmetic comparison predicates require their arguments to be fully instantiated. You cannot say Give me a number less than 20 because such a request would have an innite number of possible answers. Speaking of comparisions, another trap for the unwary, present in all programming languages, but easier to fall into in Prolog than in most, is the following: A oating-point number obtained by computation is almost never truly equal to any other oating-point number, even if the two look the same when printed out. This is because computers do arithmetic in binary, but we write numbers in decimal notation. Many decimal numbers, such as 0:1, have no binary equivalent with a nite number of digits. (Expressing 1=10 in binary is like expressing 1=3 or 1=7 in decimal the digits to the right of the point repeat endlessly.) As a result, oating-point calculations are subject to rounding error, and 0:1 + 0:1 does not evaluate to precisely 0:2. Some Prologs work around this problem by treating numbers as equal if they are sufciently close, even though their internal representations are different.
Exercise 3.2.1 Explain which of the following queries succeed, fail, or raise error conditions, and why:
?????????5 is 2+3. 5 =:= 2+3. 5 = 2+3. 4+1 is 2+3. 4+1 =:= 5. What is 2+3. What =:= 2+3. What is 5. What = 5.
Exercise 3.2.2 Try each of the following queries and explain the results you get:
?????4 is sqrt16. 2.0E-1 is sqrt4.0E-2. 11.0 is sqrt121.0. 0.5 is 0.1 + 0.1 + 0.1 + 0.1 + 0.1. 0.2 * 100 =:= 2 * 10.
Authors manuscript
Sec. 3.3.
Practical Calculations
65
If you have the time and the inclination, try similar tests in other programming languages.
we should write
?- product3,4,P, sum2,P,S, sumS,5,What. Not standard Prolog!
and in fact thats how some early Prologs did it. But the older approach has two problems: its unwieldy, and it gives the impression that Prolog has a search strategy for numbers, which it doesnt. Thus we use expressions instead. If you want to implement numerical algorithms, you do have to dene Prolog predicates, because theres usually no way to dene additional functions that can appear within expressions. Thus, you have to revert to a purely logical style when dealing with things youve dened for yourself.2 For example, lets dene a predicate close_enough 2 that succeeds if two numbers are equal to within 0.0001. That will let us compare the results of oating point computations without being thrown off by rounding errors. Heres how its done:
close_enoughX,X :- !. close_enoughX,Y :- X Y, Y-X 0.0001. close_enoughX,Y :- X Y, close_enoughY,X.
The rst clause takes care of the case where the two arguments, by some miracle, really are equal. It also handles the case where one argument is uninstantiated, by unifying it with the other argument. This enables us to use close_enough as a complete substitute for = when working with oating-point numbers. The cut (!) ensures that if clause 1 succeeds in a particular case, the other two clauses will never be tried. The second clause is the heart of the computation: compare X and Y, subtract the smaller from the larger, and check whether the difference is less than 0.0001.
2 Unless, of course, you want to write your own replacement for is, which can be well worth doing; see Chapter 7.
Authors manuscript
66
Chap. 3
The third clause deals with arguments in the opposite order; it simply swaps them and calls close_enough again, causing the second clause to take effect the second time. Notice that no loop is possible here. Now lets do some computation. The following predicate instantiates Y to the real square root of X if it exists, or to the atom nonexistent if not:3
real_square_rootX,nonexistent :- X real_square_rootX,Y :- X = 0.0, Y is sqrtX. 0.0.
Notice however that the query real_square_root121.0,11.0 will probably fail, because 11.0 p does not exactly match the oatingpoint result computed by sqrt, even though 121 = 11 exactly. We can remedy this by doing the comparison with close_enough rather than letting the unier do it directly. This requires redening real_square_root as follows:
real_square_rootX,nonexistent :- X 0.0. Clause 1 Clause 2
Finally, lets exploit Prologs ability to return alternative answers to the same question. Every positive real number has two square roots, one positive and the other negative. For example, the square roots of 1:21 are 1:1 and ,1:1. Wed like real_square_root to get both of them.
3 Versions
of Quintus Prolog and Cogent Prolog that predate the ISO standard do not let you write
sqrt... in expressions. In Cogent Prolog, for sqrtX simply write explnX 2. In Quintus, sqrt 2 is a Prolog predicate found in the math library, and to make real square root work, youll have
to change it as follows: (1) Add :- ensure loadedlibrarymath. at the beginning of your program. (2) Replace R is sqrtX with the goal sqrtX,R. (3) In clause 3, replace R is -sqrtX with the two goals sqrtX,S, R is -S.
Authors manuscript
Sec. 3.4.
67
Thats easy to do, but we need separate clauses for the alternatives, because the arithmetic itself, in Prolog, is completely deterministic. All we have to add is the following clause:
real_square_rootX,Y :- X 0.0, R is -sqrtX, close_enoughR,Y. Clause 3
This gives an alternative way of nding a real square root. Now every call to real_square_root with a positive rst argument will return two answers on successive tries:
?- real_square_root9.0,Root. Root = 3.0 Root = -3.0 yes
Nondeterminism is a useful mathematical tool because many mathematical problems have multiple solutions that can be generated algorithmically. Even if the mathematical computation is deterministic, Prolog lets you package the results so that they come out as alternative solutions to the same query.
Exercise 3.3.1 Get close_enough and real_square_root working and verify that they work as described. Exercise 3.3.2 What guarantees that the recursion in close_enough will not continue endlessly? Exercise 3.3.3 Modify close_enough so that it tests whether two numbers are equal to within 0.1% (i.e., tests whether the difference between them is less than 0.1% of the larger number). Exercise 3.3.4 What does real_square_root do if you ask for the square root of a negative number? Why? Explain which clauses are tried and what happens in each.
X or
Authors manuscript
68
Chap. 3
To do this we need a way to test whether X and Y are instantiated. Prolog provides two predicates to do this: var, which succeeds if its argument is an uninstantiated variable, and nonvar, which succeeds if its argument has a value. We can thus rewrite real_square_root as follows:4
real_square_rootX,nonexistent :nonvarX, X 0.0. real_square_rootX,Y :- nonvarX, X = 0.0, R is sqrtX, close_enoughR,Y. real_square_rootX,Y :- nonvarX, X 0.0, R is -sqrtX, close_enoughR,Y. real_square_rootX,Y :- nonvarY, Ysquared is Y*Y, close_enoughYsquared,X. Clause 1
Clause 2
Clause 3
Clause 4
Here clause 4 provides a way to compute X from Y, and the use of nonvar throughout ensures that the correct clause will be chosen and that we will not try to do computations or comparisons on uninstantiated variables. Now, however, there is some spurious nondeterminism. If both X and Y are instantiated, then either clause 2 or clause 3 will succeed, and so will clause 4. This may produce unwanted multiple results when a call to real_square_root is embedded in a larger program. The spurious nondeterminism can be removed by adding still more tests to ensure that only one clause succeeds in such a case.
Exercise 3.4.1 Demonstrate that the latest version of real_square_root works as described (i.e., that it can solve for either argument given the other). Exercise 3.4.2 Remove the spurious nondeterminism in real_square_root. That is, ensure that a query such as real_square_root1.21,1.1 succeeds only once and does not have an alternative way of succeeding. Exercise 3.4.3 Dene the predicate sumX,Y,Z such that X + Y = Z. Give it the ability to solve for any of its three arguments given the other two. You can assume that at least two arguments will be instantiated.
4 Quintus
Authors manuscript
Sec. 3.5.
Lists
69
Exercise 3.4.4 Implement a solver for Ohms Law in Prolog with full interchangeability of unknowns. That is, dene ohmE,I,R such that E = I R and such that any of the three arguments will be found if the other two are given. You can assume that all arguments will be nonzero.
3.5. LISTS
One of the most important Prolog data structures is the LIST. A list is an ordered sequence of zero or more terms written between square brackets, separated by commas, thus:
alpha,beta,gamma,delta 1,2,3,go 2+2,inaustin,texas,-4.356,X a,list,within ,a,list
The elements of a list can be Prolog terms of any kind, including other lists. The empty list is written . Note especially that the one-element list a is not equivalent to the atom a. Lists can be constructed or decomposed through unication. An entire list can, of course, match a single variable: Unify
a,b,c
With
X
Result
X= a,b,c
Also, not surprisingly, corresponding elements of two lists can be unied one by one: Unify
X,Y,Z X,b,Z
With
a,b,c a,Y,c
Result
X=a, Y=b, Z=c X=a, Y=b, Z=c
With
X,Y Z,ca
Result
X= a,b , Y=c X=a, Z=ab
More importantly, any list can be divided into head and tail by the symbol |. (On your keyboard, the character | may have a gap in the middle.) The head of a list is the rst element; the tail is a list of the remaining elements (and can be empty). The tail of a list is always a list; the head of a list is an element. Every non-empty list has a head and a tail. Thus,
a| b,c,d a| = a = a,b,c,d
Authors manuscript
70
Chap. 3
(The empty list, , cannot be divided into head and tail.) The term X|Y unies with any non-empty list, instantiating X to the head and Y to the tail, thus: Unify
X|Y X|Y
With
a,b,c,d a
Result
X=a, Y= b,c,d X=a, Y=
So far, | is like the CARCDR distinction in Lisp. But, unlike CAR and CDR, | can pick off more than one initial element in a single step. Thus
a,b,c| d,e,f = a,b,c,d,e,f
and this feature really proves its worth in unication, as follows: Unify
X,Y|Z X,Y|Z X,Y,Z|A X,Y,Z|A X,Y,a X,Y|Z
With
a,b,c a,b,c,d a,b,c a,b Z,b,Z a|W
Result
X=a, Y=b, Z= c X=a, Y=b, Z= c,d X=a, Y=b, Z=c, A=
fails
X=Z=a, Y=b X=a, W= Y|Z
The work of constructing and decomposing lists is done mostly by unication, not by procedures. This means that the heart of a list processing procedure is often in the notation that describes the structure of the arguments. To accustom ourselves to this notation, lets dene a simple list processing predicate:
third_element A,B,C|Rest ,C.
This one succeeds if the rst argument is a list and the second argument is the third element of that list. It has complete interchangeability of unknowns, thus:
?- third_element a,b,c,d,e,f ,X. X = c yes ?- third_element a,b,Y,d,e,f ,c. Y = c yes ?- third_elementX,a. X = _0001,_0002,a|_0003 yes
In the last of these, the computer knows nothing about X except that it is a list whose third element is a. So it constructs a list with uninstantiated rst and second elements, followed by a and then an uninstantiated tail.
Authors manuscript
Sec. 3.6.
71
Exercise 3.5.1 Dene a predicate first_two_same that succeeds if its argument is a list whose rst two elements match (are uniable), like this:
?- first_two_same a,a,b,c . yes ?- first_two_same a,X,b,c . X=a yes ?- first_two_same a,b,c,d . no
Exercise 3.5.2 Dene a predicate swap_first_two which, given a list of any length another list like the rst one but with the rst two elements swapped:
?- swap_first_two a,b,c,d ,What. What = b,a,c,d
2, constructs
is a reasonable way to represent an address, with elds for name, street, city, state, and Zip code. Procedures like third_element above can extract or insert data into such a list. One important difference between a list and a data record is that the number of elements in a list need not be declared in advance. At any point in a program, a list can be created with as many elements as available memory can accommodate. (If the number of elements that you want to accommodate is xed, you should consider using not a list but a STRUCTURE, discussed in section 3.14 below.) Another difference is that the elements of a list need not be of any particular type. Atoms, structures, and numbers can be used freely in any combination. Moreover, a list can contain another list as one of its elements:
'Michael Covington', 'B.A',1977 , 'M.Phil.',1978 , 'Ph.D.',1982 , 'Associate Research Scientist', 'University of Georgia'
Authors manuscript
72
Chap. 3
Here the main list has four elements: name, list of college degrees, current job title, and current employer. The list of college degrees has three elements, each of which is a twoelement list of degree and date. Note that the number of college degrees per person is not xed; the same structure can accommodate a person with no degrees or a person with a dozen. This, of course, raises a wide range of issues in data representation. Recall the contrast between data-record style and other uses of predicates that we pointed out at the end of Chapter 1. The best representation of a database cannot be determined without knowing what kind of queries it will most often be used to answer. Lists in Prolog can do the work of arrays in other languages. For instance, a matrix of numbers can be represented as a list of lists:
1,2,3 , 4,5,6 , 7,8,9
There is, however, an important difference. In an array, any element can be accessed as quickly as any other. In a list, the computer must always start at the beginning and work its way along the list element by element. This is necessary because of the way lists are stored in memory. Whereas an array occupies a sequence of contiguous locations, a list can be discontinuous. Each element of a list is accompanied by a pointer to the location of the next element, and the entire list can be located only by following the whole chain of pointers. We will return to this point in Chapter 7.
Exercise 3.6.1 Dene a predicate display_degrees that will take a list such as
'Michael Covington', 'B.A',1977 , 'M.Phil.',1978 , 'Ph.D.',1982 , 'Associate Research Scientist', 'University of Georgia'
and will write out only the list of degrees (i.e., the second element of the main list).
3.7. RECURSION
To fully exploit the power of lists, we need a way to work with list elements without specifying their positions in advance. To do this, we need repetitive procedures that will work their way along a list, searching for a particular element or performing some operation on every element encountered. Repetition is expressed in Prolog by using RECURSION, a program structure in which a procedure calls itself. The idea is that, in order to solve a problem, we will perform some action and then solve a smaller problem of the same type using the same procedure. The process terminates when the problem becomes so small that the procedure can solve it in one step without calling itself again. Lets dene a predicate memberX,Y that succeeds if X is an element of the list Y. We do not know in advance how long Y is, so we cant try a nite set of
Authors manuscript
Sec. 3.7.
Recursion
73
predetermined positions. We need to keep going until we either nd X or run out of elements to examine. Before thinking about how to perform the repetition, lets identify two special cases that arent repetitive. If Y is empty, fail with no further action, because nothing is a member of the empty list. If X is the rst element of Y, succeed with no further action (because weve found it). We will deal with the rst special case by making sure that, in all of our clauses, the second argument is something that will not unify with an empty list. An empty list has no tail, so we can rule out empty lists by letting the second argument be a list that has both a head and a tail. We can express the second special case as a simple clause:5
memberX, X|_ . Clause 1
Now for the recursive part. Think about this carefully to see why it works:
X is a member of Y if X is a member of the tail of Y.
This does not match clause 1, so proceed to clause 2. This clause generates the new query
?- memberc, b,c .
Were making progress we have transformed our original problem into a smaller problem of the same kind. Again clause 1 does not match, but clause 2 does, and we get a new query:
?- memberc, c .
Now were very close indeed. Remember that c is equivalent to c| . So this time, clause 1 works, and the query succeeds. If we had asked for an element that wasnt there, clause 2 would have applied one more time, generating a query with an empty list as the second argument. Since an empty list has no tail, that query would match neither clause 1 nor clause 2, so it would fail exactly the desired result. This process of trimming away list elements from the beginning is often called CDRing down the list. (CDR, pronounced could-er, is the name of the Lisp function that retrieves the tail of a list; it originally stood for contents of the decrement register.)
is a builtin predicate in the implementation of Prolog that you are using, give your version of it a different name, such as mem.
5 If member
Authors manuscript
74
Exercise 3.7.1
Chap. 3
Describe exactly what happens, step by step, when the computer solves each of these queries:
?- memberc, a,b,c,d,e . ?- memberq, a,b,c,d,e .
How many solutions does each of them have? Exercise 3.7.3 What does each of the following predicates do? Try them on the computer (with various lists as arguments) before jumping to conclusions. Explain the results.
test1List :- memberX,List, writeX, nl, fail. test1_. test2 First|Rest :- writeFirst, nl, test2Rest. test2 .
The recursion terminates because the list eventually becomes empty as elements are removed one by one. The order in which the computations are done is shown below. (Variable names are marked with subscripts to show that variables in different invocations of the clause are not identical.)
6 We call it list length because there is already a builtin predicate called length that does the same thing.
Authors manuscript
Sec. 3.9.
75
?- list length a,b,c ,K0 . ?- list length b,c ,K1 . ?- list length c ,K2 . ?- list length ,0. ?- K2 is 0+1. ?- K1 is 1+1. ?- K0 is 2+1.
This recursive procedure calls itself in the middle: shorten the list, nd the length of the shorter list, and then add 1. Work similar examples by hand until you are at ease with this kind of program execution.
Exercise 3.8.1 Dene a predicate count_occurrencesX,L,N that instantiates N to the number of times that element X occurs in list L:
?- count_occurrencesa, a,b,r,a,c,a,d,a,b,r,a ,What. What = 5 ?- count_occurrencesa, n,o,t,h,e,r,e ,What. What = 0
Start by describing the recursive algorithm in English. Consider three cases: the list is empty, the rst element matches what you are looking for, or the rst element does not match what you are looking for. Exercise 3.8.2 Dene a predicate last_elementL,E that instantiates E to the last element of list L, like this:
?- last_element a,b,c,d ,What. What = d
Authors manuscript
76
Chap. 3
Describing clause 2 declaratively: The rst element of the result is the same as the rst element of the rst list. The tail of the result is obtained by concatenating the tail of the rst list with the whole second list.7 Lets express this more procedurally. To concatenate two lists: 1. Pick off the head of the rst list (call it X1). 2. Recursively concatenate the tail of the rst list with the whole second list. Call the result Z. 3. Add X1 to the beginning of Z. Note that the value of X1: from step 1 is held somewhere while the recursive computation (step 2) is going on, and then retrieved in step 3. The place where it is held is called the RECURSION STACK. Note also that the Prolog syntax matches the declarative rather than the procedural English description just given. From the procedural point of view, the term X1|X2 in the argument list represents the rst step of the computation decomposing an already instantiated list while the term X1|Z in the same argument list represents the last step in the whole procedure putting a list together after Z has been instantiated. Because of its essentially declarative nature, append enjoys complete interchangeability of unknowns:
?- append a,b,c , d,e,f ,X. X = a,b,c,d,e,f yes ?- append a,b,c ,X, a,b,c,d,e,f . X = d,e,f yes ?- appendX, d,e,f , a,b,c,d,e,f . X = a,b,c yes
Each of these is deterministic there is only one possible solution. But if we leave the rst two arguments uninstantiated, we get, as alternative solutions, all of the ways of splitting the last argument into two sublists:
?X= X= X= X= X= appendX,Y, a,b,c,d . Y= a,b,c,d a Y= b,c,d a,b Y= c,d a,b,c Y= d a,b,c,d Y=
7 Like member, append is a builtin predicate in some implementations. If you are using such an implementation, use a different name for your predicate, such as app.
Authors manuscript
Sec. 3.10.
77
This can be useful for solving problems that involve dividing groups of objects into two sets.
Exercise 3.9.1 What is the result of this query?
?- append J,b,K , d,L,f , a,M,c,N,e,P .
Exercise 3.9.2 Dene a predicate append3 that concatenates three lists, and has complete interchangeability of unknowns. You can refer to append in its denition. Exercise 3.9.3 Write a procedure called flatten that takes a list whose elements may be either atoms or lists (with any degree of embedding) and returns a list of all the atoms contained in the original list, thus:
?- flatten a,b,c , d, e,f ,g ,h ,X. X = a,b,c,d,e,f,g,h
Make sure your procedure does not generate spurious alternatives upon backtracking. (What you do with empty lists in the input is up to you; you can assume that there will be none.)
This is a translation of the classic Lisp list-reversal algorithm, known as naive reversal or NREV and frequently used to test the speed of Lisp and Prolog implementations. Its naivet consists in its great inefciency. You might think that an e
8 Again, reverse
rev.
Authors manuscript
78
Chap. 3
eightelement list could be reversed in eight or nine steps. With this algorithm, however, reversal of an eight-element list takes 45 steps 9 calls to reverse followed by 36 calls to append. One thing to be said in favor of this algorithm is that it enjoys interchangeability of unknowns at least on the rst solution to each query. But if the rst argument is uninstantiated, the second argument is a list, and we ask for more than one solution, a strange thing happens. Recall that in order to solve
?- reverseX, a,b,c .
where Head|Tail =X but neither Tail nor ReversedTail is instantiated. The computer rst tries the rst clause, instantiating both Tail and ReversedTail to . This cant be used in further computation, so the computer backtracks, tries the next clause, and eventually generates a list of uninstantiated variables of the proper length. So far so good; computation can then continue, and the correct answer is produced. But when the user asks for an alternative solution, Prolog tries a yet longer list of uninstantiated variables, and then a longer one, ad innitum. So the computation backtracks endlessly until it generates a list so long that it uses up all available memory.
Exercise 3.10.1 By inserting some writes and nls, get reverse to display the arguments of each call to itself and each call to append. Then try the query reverseWhat, a,b,c , ask for alternative solutions, and watch what happens. Show your modied version of reverse and its output. Exercise 3.10.2 (for students with mathematical background)
Devise a formula that predicts how many procedure calls are made by reverse, as a function of the length of the list. Exercise 3.10.3 Why is NREV not a good algorithm for testing Prolog implementations? (Hint: Consider what Prolog is designed for.)
Authors manuscript
Sec. 3.12.
Character Strings
79
The rst clause checks that the original list is indeed instantiated, then calls a threeargument procedure named fast_reverse_aux. The idea is to move elements one by one, picking them off the beginning of the original list and adding them to a new list that serves as a stack. The new list of course becomes a copy of the original list, backward. Through all of the recursive calls, Result is uninstantiated; at the end, we instantiate it and pass it back to the calling procedure. Thus:
?- fast_reverse_aux a,b,c , ,Result. ?- fast_reverse_aux b,c , a ,Result. ?- fast_reverse_aux c , b,a ,Result. ?- fast_reverse_aux , c,b,a , c,b,a .
This algorithm reverses an n-element list in n + 1 steps. We included nonvar in the rst clause to make fast_reverse fail if its rst argument is uninstantiated. Without this, an uninstantiated rst argument would send the computer into an endless computation, constructing longer and longer lists of uninstantiated variables none of which leads to a solution.
Exercise 3.11.1 Demonstrate that fast_reverse works as described. Modify it to print out the arguments of each recursive call so that you can see what it is doing. Exercise 3.11.2 Compare the speed of reverse and fast_reverse reversing a long list. (Hint: On a microcomputer, you will have to do this with stopwatch in hand. On UNIX systems, the Prolog builtin predicate statistics will tell you how much CPU time and memory the Prolog system has used.)
Authors manuscript
80
Chap. 3
An immediate problem is that there is no standard way to output a character string, since write and display both print the list of numbers:
?- write"abc". 97,98,99 yes
We will dene a string input routine presently and rene it in Chapter 5 but here is a simple string output procedure:
write_str Head|Tail :- putHead, write_strTail. write_str .
The recursion is easy to follow. If the string is non-empty (and thus will match Head|Tail ), print the rst item and repeat the procedure for the remaining items. When the string becomes empty, succeed with no further action. Strings are lists, in every sense of the word, and all list processing techniques can be used on them. Thus reverse will reverse a string, append will concatenate or split strings, and so forth.
Exercise 3.12.1 Dene a Prolog predicate print_splits which, when given a string, will print out all possible ways of dividing the string in two, like this:
?- print_splits"university". university u niversity un iversity uni versity univ ersity unive rsity univer sity univers ity universi ty universit y university yes
Feel free to dene and call other predicates as needed. Exercise 3.12.2 Dene a predicate ends_in_s that succeeds if its argument is a string whose last element is the character s (or, more generally, a list whose last element is the ASCII code for s), like this:
?- ends_in_s"Xerxes". yes ?- ends_in_s"Xenophon". no ?- ends_in_s an,odd,example,115 . yes
Hint: This can be done two ways: using append, or using the algorithm of Exercise 3.8.2.
Authors manuscript
Sec. 3.13.
81
Notice that this predicate begins with a brief comment describing it. From now on such comments will be our standard practice. The lookahead is achieved by reading one character, then passing that character to read_str_aux, which makes a decision and then nishes inputting the line. Specically: If Char is 10 or 13 (end of line) or ,1 (end of le), dont input anything else; the rest of the string is empty. Otherwise, put Char at the beginning of the string, and recursively input the rest of it the same way. The cuts in read_str_aux ensure that if any of the rst three clauses succeeds, the last clause will never be tried. Well explain cuts more fully in Chapter 4. Their purpose here is to keep the last clause from matching unsuitable values of Char. Note that read_str assumes that keyboard input is buffered. If the keyboard is unbuffered, read_str will still work, but if the user hits Backspace while typing, the Backspace key will not untype the previous key instead, the Backspace character will appear in the string.11 We often want to read a whole line of input, not as a string, but as an atom. Thats easy, too, because the builtin predicate name 2 interconverts strings and atoms:
10 Recall 11 In
that in ISO Prolog, get0 is called get code. Arity Prolog, which uses unbuffered input, you can dene read str this way:
read strString :- read line0,Text, list textString,Text.
This relies on two builtin Arity Prolog predicates. There is also a builtin predicate read string which reads a xed number of characters.
Authors manuscript
82
?- nameabc,What. What = 97,98,99 ?- nameWhat,"abc". What = abc ?- nameWhat,"Hello there". What = 'Hello there' yes ?- nameWhat, 97,98 . What = ab
Chap. 3
equivalent to "abc"
(Remember that a string is a list of numbers nothing more, nothing less. The Prolog system neither knows nor cares whether you have typed "abc" or 97,98,99 .) So an easy way to read lines as atoms is this:
read_atomAtom Accepts a whole line of input as a single atom. read_atomAtom :- read_strString, nameAtom,String.
Implementations differ as to what name does when the string can be interpreted as a number (such as "3.1416"). In some implementations, name would give you the number 3.1416, and in others, the atom '3.1416'. Thats one reason name isnt in the ISO standard. In its place are two predicates, atom_codes and number_codes, which produce atoms and numbers respectively. Alongside them are two more predicates, atom_chars and number_chars, which use lists of onecharacter atoms instead of strings.12 We will deal with input of numbers in Chapter 5.
Exercise 3.13.1 Get read_str and read_atom working on your computer and verify that they function as described. Exercise 3.13.2 In your Prolog, does ?- nameWhat,"3.1416". produce a number or an atom? State how you found out. Exercise 3.13.3 Based on read_str, dene a predicate read_charlist that produces a list of one character atoms l,i,k,e,' ',t,h,i,s instead of a string. Exercise 3.13.4 Modify read_str to skip blanks in its input. Call the new version read_str_no_blanks. It should work like this:
12 PreISO versions of Quintus Prolog have atom chars and number chars, but they produce strings, not character lists; that is, they have the behavior prescribed for atom codes and number codes respectively.
Authors manuscript
Sec. 3.14.
Structures
83
Do not use get; instead, read each character with get0 and skip it if it is a blank.
3.14. STRUCTURES
Many Prolog terms consist of a functor followed by zero or more terms as arguments:
ab,c alpha beta,gamma ,X 'this and'that fg,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v i_have_no_arguments
Terms of this form are called STRUCTURES. The functor is always an atom, but the arguments can be terms of any type whatever. A structure with no arguments is simply an atom. So far we have used structures in facts, rules, queries, and arithmetic expressions. Structures are also data items in their own right; alongside lists, they are useful for representing complex data. For example:
personname'Michael Covington', gendermale, birthplacecity'Valdosta', state'Georgia' sentencenoun_phrasedeterminerthe, nouncat, verb_phraseverbchased, noun_phrasedeterminerthe, noundog
Structures work much like lists, although they are stored differently (and more compactly) in memory. The structure ab,c contains the same information as the list a,b,c . In fact, the two are interconvertible by the predicate =.. (pronounced univ after its name in Marseilles Prolog):
?- ab,c,d =.. X. X = a,b,c,d yes ?- X =.. w,x,y,z . X = wx,y,z yes
Authors manuscript
84
?- alpha =.. X. X = alpha yes
Chap. 3
Notice that the left-hand argument is always a structure, while the right-hand argument is always a list whose rst element is an atom. One important difference is that a list is decomposable into head and tail, while a structure is not. A structure will unify with another structure that has the same functor and the same number of arguments. Of course the whole structure will also unify with a single variable: Unify
ab,c ab,c ab,c ab,c
With
X aX,Y aX aX,Y,Z
Result
X=ab,c X=b, Y=c
fails fails
In addition to =.. Prolog provides two other built-in predicates for decomposing structures:
functorS,F,A unies F and A with the functor and arity, respectively, of structure S. Recall that the arity of a structure is its number of arguments. argN,S,X unies X with the Nth argument of structure S.
For example:
?- functorab,c,X,Y. X = a Y = 2 ?- arg2,ab,c,d,e,What. What = c
These are considerably faster than =.. because they dont have to construct a list. Do not confuse Prolog functors with functions in other programming languages. A Pascal or Fortran function always stands for an operation to be performed on its arguments. A Prolog functor is not an operation, but merely the head of a data structure.
Exercise 3.14.1 Using what you know about list processing, construct a predicate reverse_args that takes any structure and reverses the order of its arguments:
?- reverse_argsab,c,d,e,What. What = ae,d,c,b
Exercise 3.14.2 Which arguments of functor have to be instantiated in order for it to work? Try various combinations and see.
Authors manuscript
Sec. 3.15.
Exercise 3.14.3
85
Construct a predicate last_argS,A that unies A with the last argument of structure S, like this:
?- last_argab,c,d,e,f,What. What = f
The ISO standard includes a predicate, unify_with_occurs_check, that checks whether one term contains the other before attempting unication, and fails if so:
?- unify_with_occurs_checkX,fX. no. ?- unify_with_occurs_checkX,fa. X = fa
Our experience has been that the occurscheck is rarely needed in practical Prolog programs, but it is something you should be aware of.
Exercise 3.15.1 Which of the following queries creates a loopy structure?
????X=Y, Y=X. X=fY, Y=X. X=fY, Y=fX. X=fY, Y=fZ, Z=a.
Authors manuscript
86
Chap. 3
The power of call comes from the fact that a goal can be created by computation and then executed. For example:
answer_question :write'Mother or father? ', read_atomX, write'Of whom? ', read_atomY, Q =.. X,Who,Y , callQ, writeWho, nl.
If the user types mother and cathy, then Q becomes motherWho,cathy. This is then executed as a query and the value of Who is printed out. Thus (assuming the knowledge base from FAMILY.PL):
?- answer_question. Mother or father? father Of whom? michael charles_gordon yes ?- answer_question. Mother or father? mother Of whom? melody eleanor yes
We can make this slightly more convenient by dening a predicate apply (similar to APPLY in Lisp) that takes an atom and a list, and constructs a query using the atom as the functor and the list as the arguments, then executes the query.
applyFunctor,Arglist Constructs and executes a query. applyFunctor,Arglist :Query =.. Functor|Arglist , callQuery.
The goal applymother, Who,melody has the same effect as motherWho,melody. The arguments are given in a list because the number of them is unpredictable; the list, containing an unspecied number of elements, is then a single argument of apply. Prolog provides no way to dene a predicate with an arbitrarily variable number of arguments. Many Prologs, including the ISO standard, let you omit the word call and simply write a variable in place of a subgoal:
applyFunctor,Arglist :Query =.. Functor|Arglist , Query.
Authors manuscript
Sec. 3.17.
87
Exercise 3.16.1 Does your Prolog let you write a variable as a goal, instead of using call? Exercise 3.16.2 Get answer_question working (in combination with FAMILY.PL) and then modify answer_question to use apply. Exercise 3.16.3 (small project)
Dene mapFunctor,List,Result (similar to MAPCAR in Lisp) as follows: Functor is a 2argument predicate, List is a list of values to be used as the rst argument of that predicate, and Result is the list of corresponding values of the second argument. For example, using the knowledge base of CAPITALS.PL, the following query should succeed:
?- mapcapital_of, georgia,california,florida ,What. What = atlanta,sacramento,tallahassee
Authors manuscript
88
Chap. 3
countX Unifies X with the number of times count 1 has been called. countX :- retractcount_auxN, X is N+1, assertacount_auxX. :- dynamiccount_aux 1. count_aux0.
Figure 3.1
A predicate that tells you how many times it has been called.
?- countX. X = 3 yes
Because count has to remember information from one call to the next, regardless of backtracking or failure, it must store this information in the knowledge base using assert and retract. There is no way the information could be passed from one procedure to another through arguments, because there is no way to predict what the path of execution will be. In almost all Prologs, including the ISO standard, count is deterministic. But in LPA Prolog, it is nondeterministic because LPA Prolog considers that performing the assert creates a new alternative solution. There are several reasons to use assert only as a last resort. One is that assert usually takes more computer time than the ordinary passing of an argument. The other is that programs that use assert are much harder to debug and prove correct than programs that do not do so. The problem is that the ow of control in a procedure can be altered when the program modies itself. Thus, it is no longer possible to determine how a predicate behaves by looking just at the denition of that predicate; some other part of the program may contain an assert that modies it. There are, however, legitimate uses for assert. One of them is to record the results of computations that would take a lot of time and space to recompute. For instance, a graphsearching algorithm might take a large number of steps to nd each path through the graph. As it does so, it can use assert to add the paths to the knowledge base so that if they are needed again, the computation need not be repeated. Thus:
find_path... :- ...computation..., assertafind_path....
Each time find_path computes a solution, it inserts into the knowledge base, ahead of itself, a fact giving the solution that it found. Subsequent attempts to nd the same path will use the stored fact rather than performing the computation. Procedures
Authors manuscript
Sec. 3.18.
Bibliographical Notes
89
that remember their own earlier results in this way are sometimes called MEMO PROCEDURES, and are much easier to create in Prolog than in other languages (compare Abelson and Sussman 1985:218-219). Another legitimate use of assert is to set up the controlling parameters of a large and complex program, such as an expert system, which the user can use in several modes. By performing appropriate asserts, the program can set itself up to perform the function that the user wants in a particular session. For example, asserting test_modeyes might cause a wide range of testing actions to be performed as the program runs.
Exercise 3.17.1 Dene a procedure gensymX (like GENSYM in Lisp) which generates a new atom every time it is called. One possibility would be to have it work like this:
?- gensymWhat. What = a ?- gensymWhat. What = b ... ?- gensymWhat. What = z ?- gensymWhat. What = za
However, you are free to generate any series of Prolog atoms whatsoever, so long as each atom is generated only once. Exercise 3.17.2 (small project)
Use a memo procedure to test whether integers are prime numbers. Show that this procedure gets more efcient the more it is used.
Authors manuscript
90
Chap. 3
Authors manuscript
Chapter 4
91
Authors manuscript 693 ppid September 9, 1995 Prolog Programming in Depth
92
Exercise 4.1.1
Chap. 4
How does Prolog arithmetic (Chapter 3) differ from what you would expect in a programming language based purely on logic? Explain the practical reason(s) for the difference(s).
Each denition matches in exactly one of the three cases. A common mistake is to write the clauses as follows:
writenameX :- X=1, write'One'. writenameX :- X=2, write'Two'. writenameX :- X=3, write'Three'. Inefficient!
This gives correct results but wastes time. It is wasteful to start executing each clause, perform a test that fails, and backtrack out, if the inapplicable clauses could have been prevented from matching the goal in the rst place. A key to effective programming in Prolog is making each logical unit of the program into a separate procedure. Each if or case statement should, in general, become a procedure call, so that decisions are made by the procedurecalling process. For example, the Pascal procedure
Authors manuscript
Sec. 4.2.
Conditional Execution
Pascal, not Prolog
93
Crucially, Every time there is a decision to be made, Prolog calls a procedure and makes the decision by choosing the right clause. In this respect Prolog goes further than ordinary structured programming. A major goal of structured programming is to make it easy for the programmer to visualize the conditions under which any given statement will be executed. Thus, structured languages such as Pascal restrict the use of goto statements and encourage the programmer to use block structures such as ifthenelse, while, and repeat, in which the conditions of execution are stated explicitly. Still, these structures are merely branches or exceptions embedded in a linear stream of instructions. In Prolog, by contrast, the conditions of execution are the most visible part of the program.
Exercise 4.2.1 Dene a predicate absval which, given a number, computes its absolute value:
?- absval0,What. What = 0 ?- absval2.34,What. What = 2.34 ?- absval-34.5,What. What = 34.5
Do not use the builtin abs function. Instead, test whether the number is negative, and if so, multiply it by ,1; otherwise return it unchanged. Make sure absval is deterministic, i.e., does not have unwanted alternative solutions. Do not use cuts. Exercise 4.2.2 Dene a predicate classify that takes one argument and prints odd, even, not an integer, or not a number at all, like this:
?- classify3. odd
Authors manuscript
94
?- classify4. even ?- classify2.5. not an integer ?- classifythisand,that. not a number at all
Chap. 4
(Hint: You can nd out whether a number is even by taking it modulo 2. For example, 13 mod 2 = 1 but 12 mod 2 = 0 and ,15 mod 2 = ,1.) Make sure that classify is deterministic. Do not use cuts.
This gives correct results but lacks conciseness. In order to make sure that only one clause can be executed with each number, we have had to test the value of X in both of the last two clauses. We would like to tell the program to print Out of range for any number that did not match any of the rst three clauses, without performing further tests. We could try to express this as follows, with some lack of success:
writename1 writename2 writename3 writename_ ::::write'One'. Wrong! write'Two'. write'Three'. write'Out of range'.
Authors manuscript
Sec. 4.3.
95
matches both the rst clause and the last clause. Thus it has two alternative solutions, one that prints One and one that prints Out of range. Unwanted alternatives are a common error in Prolog programs. Make sure your procedures do the right thing, not only on the rst try, but also upon backtracking for an alternative. We want writename to be DETERMINISTIC, that is, to give exactly one solution for any given set of arguments, and not give alternative solutions upon backtracking. We therefore need to specify that if any of the rst three clauses succeeds, the computer should not try the last clause. This can be done with the cut operator (written !). The cut operator makes the computer discard some alternatives (backtrack points) that it would otherwise remember. Consider for example this knowledge base:
b :- c, d, !, e, f. b :- g, h.
and suppose that the current goal is b. We will start by taking the rst clause. Suppose further that c and d succeed and the cut is executed. When this happens, it becomes impossible to look for alternative solutions to c and d (the goals that precede the cut in the same clause) or to try the other clause for b (the goal that invoked the clause containing the cut). We are committed to stick with the path that led to the cut. It remains possible to try alternatives for e and f in the normal manner. More precisely, at the moment the cut is executed, the computer forgets about any alternatives that were discovered upon, or after, entering the current clause. Thus the cut burns your bridges behind you and commits you to the choice of a particular solution. The effect of a cut lasts only as long as the clause containing it is being executed. To see how this works, add to the knowledge base the following clauses:
a :- p, b, q. a :- r, b.
Leave b dened as shown above, and let the current goal be a. There are two clauses for a. Take the rst one, and assume that p succeeds, and then b succeeds using the rst of its clauses (with the cut), and then q fails. What can the computer do? It cant try an alternative for b because the cut has ensured that there are none. It can, however, backtrack all the way past b outside the scope of the cut and look for alternatives for p and for a, which the cut didnt affect. When this is done, the effect of the cut is forgotten (because that particular call to b is over), and if execution reenters b, the search for solutions for b will start afresh. We can make writename deterministic by putting a cut in each of the rst three clauses. This changes their meaning slightly, so that the rst clause (for example) says, If the argument is 1, then write One and do not try any other clauses.
writename1 writename2 writename3 writename_ ::::!, write'One'. !, write'Two'. !, write'Three'. write'Out of range'.
Authors manuscript
96
Chap. 4
Since write is deterministic, it does not matter whether the cut is written before or after the call to write. The alternatives that get cut off are exactly the same. However, programs are usually more readable if cuts are made as early as possible. That is: make the cut as soon as you have determined that the alternatives wont be needed.
Exercise 4.3.1 Make absval (from the previous section) more efcient by using one or more cuts. State exactly what unwanted computation the cut(s) prevent(s). Exercise 4.3.2 Make classify (from the previous section) more efcient by using one or more cuts. Exercise 4.3.3 Consider a predicate my_cut dened as follows:
my_cut :- !.
Authors manuscript
Sec. 4.5.
97
These cuts have no effect if only one solution is being sought. However, they ensure that, if a later goal fails, no time will be wasted backtracking into this predicate to look for another solution. The programmer knows that only one of these clauses will succeed with any given value of X; the cuts enable him or her to communicate this knowledge to the computer. (No cut is needed in the last clause because there are no more alternatives after it.) Red cuts can save time even when looking for the rst solution:
writename1 writename2 writename3 writename_ ::::!, write'One'. !, write'Two'. !, write'Three'. write'Out of range'.
Here, we need never test explicitly whether X is out of range. If X = 1, 2, or 3, one of the rst three clauses will execute a cut and execution will never get to the last clause. Thus, we can assume that if the last clause executes, X must have been out of range. These cuts are considered red because the same clauses without the cuts would not be logically correct. Use cuts cautiously. Bear in mind that the usual use of cuts is to make a specic predicate deterministic. Resist the temptation to write an imprecise predicate and then throw in cuts until it no longer gives solutions that you dont want. Instead, get the logic right, then add cuts if you must. Make it correct before you make it efcient is a maxim that applies to Prolog at least as much as to any other computer language.
Exercise 4.4.1 Classify as red or green the cuts that you added to absval and classify in the previous section.
Authors manuscript
98
Chap. 4
Then the query ?- onceGoal. means Find the rst solution to Goal, but not any alternatives. For example (using FAMILY.PL from Chapter 2):
?- parentWho,cathy. Who = michael ; Who = melody ?- onceparentWho,cathy. Who = michael
No matter how many possible solutions there are to a goal such as fX, the goal oncefX will return only the rst solution. If fX has no solutions, oncefX fails. The argument of once can be a series of goals joined by commas. In such a case, extra parentheses are necessary:
?- once parentX,cathy, parentG,X .
And, of course, you can use once in predicate denitions. Heres a highly hypothetical example:
fX :- gX, once hX, iX , jX.
Here once serves as a limitedscope cut (like Arity Prologs snips) it ensures that, each time through, only the rst solution of hX, iX will be taken, although backtracking is still permitted on everything else. Use once sparingly. It is usually better to make your predicates deterministic, where possible, than to make a deterministic call to a nondeterministic predicate.
Exercise 4.6.1 Rewrite absval and classify (from several previous sections) to use once instead of cuts. Is this an improvement? (Not necessarily. Compare the old and new versions carefully and say which you prefer. Substantial reorganization of the Prolog code may be necessary.)
Authors manuscript
Sec. 4.7.
99
This means if Goal1 then Goal2 else Goal3, or more precisely, Test whether Goal1 succeeds, and if so, execute Goal2; otherwise execute Goal3. For example:
writenameX :X = 1 writeone ; write'not one'.
That is: Try X = 1, then X = 2, then X = 3, until one of them succeeds; then execute the goal after the arrow, and stop. You can leave out the semicolon and the else goal. The ifthenelse structure gives Prolog a way to make decisions without calling procedures; this gives an obvious gain in efciency. (Some compilers generate more efcient code for ifthenelse structures than for the same decisions expressed any other way.) But we have mixed feelings about ifthenelse. To us, it looks like an intrusion of ordinary structured programming into Prolog. Its handy and convenient, but it collides headon with the idea that Prolog clauses are supposed to be logical formulas. But we have more substantial reasons for not using ifthenelse in this book. First, ifthenelse is unnecessary; anything that can be expressed with it can be expressed without it. Second, one of the major Prologs (Arity) still lacks the usual ifthenelse structure (although it has a different ifthenelse of its own). Third, and most seriously, Prolog implementors do not agree on what ifthenelse structures should do in all situations; see Appendix B for the details.
Exercise 4.7.1 Rewrite absval and classify (again!), this time using ifthenelse structures.
Authors manuscript
100
Chap. 4
A call to f succeeds with any arguments; it may or may not print its message, but it will certainly not fail and hence will not cause backtracking in the procedure that invoked it. Moreover, because of the cut, f is deterministic. The cut prevents the second clause from being used to generate a second solution with arguments that have already succeeded with the rst clause. Another way to make a query succeed is to put it in disjunction with true, which always succeeds:
?- fA,B ; true.
(Recall that the semicolon means or.) If the original query doesnt succeed, the call to true certainly will. Better yet, wrap the whole thing in once so that it doesnt give two solutions when youre expecting only one:
?- once fA,B ; true .
You can guarantee that any procedure will fail by adding !, fail at the end of each of its denitions, thus:
gX,Y :- X Y, write'X less than Y', !, fail. gX,Y :- Y X, write'Y less than X', !, fail.
Any call to g ultimately returns failure for either of two reasons: either it doesnt match any of the clauses, or it matches one of the clauses that ends with cut and fail. The cut is written next to last so that it wont be executed unless all the other steps of the clause have succeeded; as a result, it is still possible to backtrack from one clause of g to the other as long as the cut has not yet been reached. You can dene predicates make_succeed and make_fail that make any goal succeed or fail, thus:
make_succeedGoal :- Goal, !. make_succeed_. make_failGoal :- callGoal, !, fail.
Related to make_fail is the technique of making a rule fail conclusively. Think back to GEO.PL, the geographical knowledge base in Chapter 1. Suppose we found that, in practice, we were constantly getting queries from people wanting to know whether Toronto is in the United States. Rather than letting the computer calculate the answer each time, we might introduce, at the beginning of the knowledge base, the rule:
located_intoronto,usa :- !, fail.
Now the query ?- located_intoronto,usa. will hit this rule and fail immediately with no alternatives, thus saving computer time. Finally, note that cut can be used to dene not, thus:
notGoal :- callGoal, !, fail. not_.
Authors manuscript
Sec. 4.9.
101
That is: If a call to Goal succeeds, then reject alternatives and fail. Otherwise, succeed regardless of what Goal is.
Exercise 4.8.1 Rewrite writename so that instead of printing out of range it succeeds without printing anything if its argument is not 1, 2, or 3. Make sure it is still deterministic. Exercise 4.8.2 Using only two clauses, and not using +, dene a deterministic predicate non_integer that fails if its argument is an integer, but succeeds if its argument is anything else whatsoever. (Hint: Use !, fail.)
And heres a procedure that turns the computer into a typewriter, accepting characters from the keyboard ad innitum, until the user hits the break key to abort the program:1
typewriter :- repeat, get0C, fail. unreliable!
The loop can be made to terminate by allowing it to succeed eventually, so that backtracking stops. The following version of typewriter stops when the user presses Return (ASCII code 13):
typewriter :- repeat, get0C, C = 13. unreliable!
If C is equal to 13, execution terminates; otherwise, execution backtracks to repeat and proceeds forward again through get0C. (Note that in some Prologs you should test for code 10, not 13.)
1 We assume that the computer displays each character as it is typed. If your Prolog doesnt do this, add putC after get0C. Remember that in ISO Prolog, get0 and put are called get code and put code respectively.
Authors manuscript
102
Chap. 4
Even with this change, the looping in typewriter can be restarted by the failure of a subsequent goal, as in the compound query
?- typewriter, write'Got it', fail.
(Try it.) To prevent the loop from restarting unexpectedly, we need to add a cut as follows:
typewriter :- repeat, get0C, C = 13, !.
10 under UNIX
In effect, this forbids looking for alternative solutions to anything in typewriter once one solution has succeeded. To sum up: Every repeat loop begins with a repeat goal and ends with a test that fails, followed by a cut. Note that repeat loops in Prolog are quite different from repeat loops in Pascal. The biggest difference is that in Pascal, the loop always starts over and over from the beginning, whereas in Prolog, backtracking can take you to any subgoal that has an untried alternative which need not be repeat. Moreover, if any goal in a Prolog loop fails, backtracking takes place immediately; subsequent goals are not attempted. A serious limitation of repeat loops is that there is no convenient way to pass information from one iteration to the next. Prolog variables lose their values upon backtracking. Thus, there is no easy way to make a repeat loop accumulate a count or total. (Information can be preserved by storing it in the knowledge base, using assert and retract, but this process is usually inefcient and always logically inelegant.) With recursion, information can be transmitted from one pass to the next through the argument list. This is the main reason for preferring recursion as a looping mechanism.
Exercise 4.9.1 Modify typewriter so that it will stop whenever it gets code 10 or code 13. Exercise 4.9.2 Using repeat, dene a predicate skip_until_blank that reads characters from standard input, one by one, until it gets either a blank or an endofline mark. Demonstrate that it works correctly. If you are using the keyboard for input, note the effect of buffering. Exercise 4.9.3 Using repeat, see, read, and assertz, write your own version of consult. That is, dene a predicate which, when given a le name, will read terms from that le and assert them into the knowledge base. (This can be extremely handy if you want to preprocess the terms in some way before asserting them.)
Authors manuscript
Sec. 4.10.
Recursion
103
4.10. RECURSION
Most programmers are familiar with recursion as a way to implement taskwithin atask algorithms such as tree searching and Quicksort. Indeed, Prolog lends itself well to expressing recursive algorithms developed in Lisp. What is not widely appreciated is that any iterative algorithm can be expressed recursively. Suppose for example we want to print a table of the integers 1 to 5 and their squares, like this:
1 2 3 4 5 1 4 9 16 25
This is obviously a job for a loop. We could describe the computation in Pascal as:
for i:=1 to 5 do writelni,' ',i*i Pascal, not Prolog
But the same computation can also be described recursively. Lets rst describe the recursive algorithm in English: To print squares beginning with I : If I 5, do nothing.
Otherwise, print I and I 2 , then print squares beginning with I + 1. In Pascal this works out to the following:
procedure PrintSquaresi:integer; begin if noti 5 then begin writelni,' ',i*i; PrintSquaresi+1 end end; Pascal, not Prolog
The procedure prints one line of the table, then invokes itself to print the next. Here is how it looks in Prolog:
Authors manuscript
104
print_squaresI :- I 5, !.
Chap. 4
print_squaresI :S is I*I, writeI, write' ', writeS, nl, NewI is I+1, print_squaresNewI.
Notice that there is no loop variable. In fact, in Prolog, it is impossible to change the value of a variable once it is instantiated, so there is no way to make a single variable I take on the values 1, 2, 3, 4, and 5 in succession. Instead, the information is passed from one recursive invocation of the procedure to the next in the argument list.
Exercise 4.10.1 Dene a predicate print_stars which, given an integer as an argument, prints that number of asterisks:
?- print_stars40. *************************************** yes
, 1.
Authors manuscript
Sec. 4.11.
105
This is straightforward; the procedure factorial calls itself to compute the factorial of the next smaller integer, then uses the result to compute the factorial of the integer that you originally asked for. The recursion stops when the number whose factorial is to be computed is 0. This denition is logically elegant. Mathematicians like it because it captures a potentially innite number of multiplications by distinguishing just two cases, N = 0 and N 0. In this respect the recursive denition matches the logic of an inductive proof: the rst step establishes the starting point, and the second step applies repeatedly to get from one instance to the next. But that is not the usual way to calculate factorials. Most programmers would quite rightly use iteration rather than recursion: Start with 1 and multiply it by each integer in succession up to N . Here, then, is an iterative algorithm to compute factorials (in Pascal):
function factorialN:integer:integer; var I,J:integer; begin I:=0; Initialize J:=1; while I N do begin Loop I:=I+1; J:=J*I end; factorial:=J Return result end; Pascal, not Prolog
In Pascal, this procedure does not call itself. Its Prolog counterpart is a procedure that calls itself as its very last step a procedure that is said to be TAIL RECURSIVE:
factorialN,FactN :- fact_iterN,FactN,0,1. fact_iterN,FactN,N,FactN :- !. fact_iterN,FactN,I,J :I N, NewI is I+1, NewJ is J*NewI, fact_iterN,FactN,NewI,NewJ.
Authors manuscript
106
Chap. 4
Here the third and fourth arguments of fact_iter are state variables or accumulators that pass values from one iteration to the next. State variables in Prolog correspond to variables that change their values repeatedly in Pascal. Lets start by examining the recursive clause of fact_iter. This clause checks that I is still less than N, computes new values for I and J, and nally calls itself with the new arguments. Because Prolog variables cannot change their values, the additional variables NewI and NewJ have to be introduced. In Prolog (as in arithmetic, but not in most programming languages), the statement
X is X+1 wrong!
is never true. So NewI and NewJ contain the values that will replace I and J in the next iteration. The rst clause of fact_iter serves to end the iteration when the state variables reach their nal values. A more Pascallike but less efcient way of writing this clause is:
fact_iterN,FactN,I,J :- I = N, FactN = J.
That is, if I is equal to N, then FactN (uninstantiated until now) should be given the value of J. By writing this same clause more concisely as we did above, we make Prologs unication mechanism perform work that would require explicit computational steps in other languages. Most iterative algorithms can be expressed in Prolog by following this general pattern. First transform other types of loops (e.g., for and repeatuntil) into Pascal like while loops. Then break the computation into three stages: the initialization, the loop itself, and any nal computations needed to return a result. Then express the loop as a tail recursive clause (like the second clause of fact_iter) with the whilecondition at the beginning. Place the nal computations in another, nonrecursive, clause of the same procedure, which is set up so that it executes only after the loop is nished. Finally, hide the whole thing behind a frontend procedure (factorial in this example) which is what the rest of the program actually calls. The frontend procedure passes its arguments into the tail recursive procedure along with initial values of the state variables.
Exercise 4.11.1 Dene a recursive predicate sumJ,K,N that instantiates N to the sum of the integers from J to K inclusive:
?- sum-1,1,What. What = 0 ?- sum1,3,What. What = 6 ?- sum6,7,What. What = 13
Exercise 4.11.2 Is your version of sum tail recursive? If not, modify it to make it so.
Authors manuscript
Sec. 4.12.
Exercise 4.11.3
107
The Fibonacci series is the series of numbers obtained by starting with h1; 1i and forming each subsequent member by adding the previous two: h1; 1; 2; 3; 5; 8; 13; 21 : : :i. The following procedure computes the N th Fibonacci number in an elegant but inefcient way:
fib1,1 :- !. fib2,1 :- !. fibN,F :- N 2, N1 is N-1, fibN1,F1, N2 is N-2, fibN2,F2, F is F1+F2.
Explain what is inefcient about this. Then write a more efcient procedure (named fib2) that uses state variables to remember the previous two Fibonacci numbers when computing each one. Exercise 4.11.4 Are the cuts in fib (previous exercise) red or green?
Authors manuscript
108
f81 :- !. fX :- X =
Chap. 4
The idea is to start with a number and keep squaring it until either 81 or a number greater than 100 is reached. If 81 is encountered, the query succeeds; if not, the query fails, but in either case, the loop terminates, and the conditions under which it terminates are obvious from the way the program is written. Finally, make sure that each of the clauses that youve written will actually execute in some situation. A common error is to have, say, three or four clauses, the last of which is never executed.
Exercise 4.12.1 Dene a predicate even_length that takes one argument, a list, and succeeds if that list has an even number of elements. Do not use arithmetic; do the computation entirely by picking off elements of the list, two at a time. Exercise 4.12.2 Dene a predicate remove_duplicatesX,Y that removes duplicated members from list X giving list Y thus:
?- remove_duplicates a,b,r,a,c,a,d,a,b,r,a ,X. X = c,d,b,r,a
That is, only one occurrence of each element is to be preserved. (Whether to preserve the rst or the last occurrence is up to you.) You can assume that the rst argument is instantiated. Check that your procedure does not generate spurious alternatives. Exercise 4.12.3 Write a predicate that produces a list of the members that two other lists have in common, thus:
?- members_in_common a,b,c , c,d,b ,X. X= b,c
Assume that the rst two arguments are instantiated. The order of elements in the computed list is not important. Exercise 4.12.4 Dene a predicate my_square_rootX,Y that unies Y with the square root of X. Find the square root by successive approximations, based on the principle that if G is a reasonably good guess for the square root of X , then X=G + G=2 is a better guess. Start with G = 1, and stop when G no longer changes very much from one iteration to the next.
Authors manuscript
Sec. 4.13.
109
of two things: the CONTINUATION, which tells how to continue with the current clause after the called procedure succeeds, and the BACKTRACK POINTS, which indicate where to look for alternatives if the current clause fails. Heres an example:
a :- b, c. a :- d. ?- a.
When b is called, the computer must save information telling it to continue with c if b succeeds and to try the second clause of a if b fails. In this case there is only one backtrack point, but if b had been preceded by a subgoal that had more than one solution, there would have been a backtrack point for that subgoal as well. The stack space is released when the procedure terminates. Since a recursive call places more information onto the stack without releasing what was already there, it would seem that repeated recursion would lead inevitably to stack overow. However, almost all Prolog implementations recognize a special case. If the continuation is empty and there are no backtrack points, nothing need be placed on the stack; execution can simply jump to the called procedure, without storing any record of how to come back. This is called LASTCALL OPTIMIZATION. If the procedure is recursive, then instead of calling the same procedure again, the computer simply places new values into its arguments and jumps back to the beginning. In effect, this transforms recursion into iteration. We have now come full circle: to make the logic clearer, we transformed iteration into recursion, and to make the execution more efcient, the interpreter or compiler transforms it back into iteration. A procedure that calls itself with an empty continuation and no backtrack points is described as TAIL RECURSIVE, and lastcall optimization is sometimes called TAILRECURSION OPTIMIZATION. For example, this procedure is tail recursive:
test1N :- writeN, nl, NewN is N+1, test1NewN.
Its continuation is empty because the recursive call is the last subgoal in the clause. There is no backtrack point because there are no other clauses for test1 and no alternative solutions to writeN, nl, or NewN is N+1. By contrast, the following procedure is not tail recursive:
test2N :- writeN, nl, NewN is N+1, test2NewN, nl.
Here the continuation is not empty; the second nl remains to be executed after the recursive call succeeds. Here is a clause that has an empty continuation but still has a backtrack point:
test3N :- writeN, nl, NewN is N+1, test3NewN. test3N :- N 0.
Every time the rst clause calls itself, the computer has to remember that (on that call) the second clause has not been tried yet. Accordingly, test3 is not tail recursive. The fact that test3 has two clauses is not the point here. A procedure with many clauses can be tail recursive as long as there are no clauses remaining to be
Authors manuscript
110
Chap. 4
tried after the recursive call. If we rearrange the clauses of test3, we get a perfectly good tail recursive procedure:
test3aN :- N 0. test3aN :- writeN, nl, NewN is N+1, test3aNewN.
Nor does a oneclause procedure always lack backtrack points. The following procedure is not tail recursive because of untried alternatives within the clause:
test4N :- writeN, nl, mN,NewN, test4NewN. mN,NewN :- N =0, NewN is N+1. mN,NewN :- N 0, NewN is -1*N.
There is only one clause for test4, but m has two clauses, and only the rst of them has been tried when test4 rst calls itself recursively. The computer must therefore record, on the stack, the fact that another path of computation is possible. Never mind that both clauses cannot succeed with any given number; when the recursive call takes place, the second one has not even been tried. A quick way to conrm that your Prolog system has tail recursion optimization is to type in the clauses above and try the queries:
????test10. test20. test30. test40.
If tail recursion optimization is taking place, test1 will run indenitely, printing everincreasing integers until stopped by some external cause such as a numeric overow or a nger on the Break key. But test2, test3, and test4 will run out of stack space after a few thousand iterations. Use them to gauge your machines limits. In almost all implementations of Prolog, a procedure can become tail recursive by executing a cut. Recall that a tail recursive procedure must have an empty continuation and no backtrack points. The purpose of the cut is to discard backtrack points. Accordingly, the following procedures test5 and test6 ought to be tail recursive:
test5N :- writeN, nl, NewN is N+1, !, test5NewN. test5N :- N 0. test6N :- writeN, nl, mN,NewN, !, test6NewN.
Except for the cuts, these predicates are identical to test3 and test4. (Recall that m was dened earlier.) Finally, notice that tail recursion can be indirect. Unneeded stack space is freed whenever one procedure calls another with an empty continuation and no backtrack points whether or not the call is recursive. Thats why tailrecursion optimization is also called lastcall optimization, although naturally it is of little practical importance except in tail recursive procedures. Thus, the following procedure is tail recursive:
Authors manuscript
Sec. 4.14.
Indexing
111
Each procedure calls the other without placing anything on the stack. The result is a recursive loop spread across more than one procedure.
Exercise 4.13.1 (Not recommended for multiuser computers.) Test tail recursion on your machine. How many iterations of test2 can you execute without running out of stack space? How about test3? Exercise 4.13.2 Rework fib2 (from Exercise 4.11.3, p. 107) to make it tail recursive. (If your version of fib2 is already tail recursive, say so.)
4.14. INDEXING
When a Prolog system executes a query, it doesnt have to search the entire knowledge base for a matching clause. All Prologs use INDEXING (a hashing function or lookup table) to go directly to the right predicate. For example, with FAMILY.PL, a query to mother 2 does not search the clauses for father 2. Most modern Prologs use indexing to go further than that. They index, not only on the predicate and arity, but also on the principal functor of the rst argument. For example, if you have the knowledge base
ab. ac. de. df.
then the query ?- df. not only wont search the clauses for a 1, it also wont look at de. It goes straight to df, which is the only clause whose predicate, arity, and rst argument match those in the query. Indexing can speed up queries tremendously. It can also make predicates tail recursive when they otherwise wouldnt be. Heres an example:
test80 :- write'Still going', nl, test80. test8-1.
The query ?- test80. executes tail recursively because indexing always takes it right to the rst clause, and the second clause is never even considered (and hence does not generate a backtrack point). Notice that: Indexing looks at the principal functor of the rst argument (or the whole rst argument if the rst argument is a constant). Firstargument indexing can distinguish from a nonempty list, but cannot distinguish one nonempty list from another.
Authors manuscript
112
Chap. 4
Firstargument indexing works only when the rst arguments of all the clauses, and also of the query, are instantiated (or at least have instantiated principal functors). Indexing does not predict whether clauses will succeed. Nor does it necessarily predict whether the entire unication process, including the inner parts of lists and structures, can be carried out. Its only purpose is to rule out some obvious mismatches without wasting too much time. To take advantage of indexing, you should design your predicates so that the rst argument is the one that is most often used to select the right clause. That means that, commonly, you will organize Prolog predicates so that the rst argument contains the known information, and the subsequent arguments contain information to be looked up or computed. Of course, this is not a hard and fast rule; if it were, Prolog wouldnt be nearly as versatile as it is. Consider now the familiar list concatenation procedure:
append Head|Tail ,X, Head|Y :- appendTail,X,Y. append ,X,X.
This procedure is tail recursive in Prolog even though its Lisp counterpart is not. It is easy to make the mistake of thinking that the Prolog procedure works in the following non-tail-recursive way: (Misinterpreted algorithm:) 1. Split the rst list into Head and Tail. 2. Recursively append Tail to X giving Y. 3. Join Head to Y giving the answer. This is, after all, how the corresponding procedure would work in Lisp:
DEFUN APPEND LIST1 LIST2 IF NULL LIST1 LIST2 CONS CAR LIST1 APPEND CDR LIST1 LIST2
That is: if LIST1 is empty, then return LIST2; otherwise join the rst element of LIST1 with the result of appending the tail of LIST1 to LIST2. The joining operation (the CONS) is performed after returning from the recursive call; hence the recursive call is not the last step, and the procedure is not tail recursive in Lisp. The catch is that in Prolog, the joining of Head to Y does not have to wait until the list Y has been constructed. Rather, the procedure operates as follows: Split the rst list into Head and Tail; unify the second list with X; and create a third list whose head is Head and whose tail is Y, an uninstantiated variable. Recursively append Tail to X, instantiating Y to the result.
Authors manuscript
Sec. 4.15.
113
Internally, the third list is created with its tail pointing to a memory location where the rest of the list will be constructed; the recursive call then builds the rest of the list there. The ability to use uninstantiated variables in this way distinguishes Prolog from other list processing languages.
Exercise 4.14.1 In FAMILY.PL, which of the following queries executes faster?
?- fatherWho,michael. ?- fathercharles_gordon,Who.
You will probably not be able to measure the difference; give your answer based on what you know about indexing. Exercise 4.14.2 Would append (as shown in this section) be tail recursive in a Prolog system that did not have rstargument indexing? Explain. Exercise 4.14.3 Based on what you now know, take flatten (from exercise 3.9.3, p. 77) and make it tail recursive. (This is not easy; if youre stuck, make it as close to tail recursive as possible.)
Authors manuscript
114
Chap. 4
f_aux, f_start, and the like for any other predicates that are needed to make f complete. In this way, the programmer using f need not look at the names of all the predicates dened in the f package; he or she need only refrain from using predicate names that begin with f_.
Prolog also facilitates top-down design. A program can be designed by writing the main procedure rst, then lling in the other procedures that it will call. The interchangeability of facts and rules makes it easy to write STUBS, or substitutes, for procedures that are to be written later. Suppose for example that the main body of the program requires a predicate permuteList1,List2 that succeeds if List2 is a permutation of List1. The following quickanddirty stub works with lists of three or fewer elements:
permuteX,X. permute A,B , B,A . permute A,B,C , A,C,B . permute A,B,C , B,A,C . permute A,B,C , B,C,A . permute A,B,C , C,A,B . permute A,B,C , C,B,A . permute A,B,C,D|Rest , A,B,C,D|Rest :- write'List too long!'.
A more general denition of permute can be substituted later without any other change to the program. In a similar way, Prolog facts with canned data can substitute for procedures that will be written later to obtain or compute the real data.
Exercise 4.15.1 Suppose you needed append but didnt have it. Write a stub for append that works for lists of up to 3 elements (giving a concatenated list up to 6 elements long). Does writing the stub help you recognize the recursive pattern that would have to be incorporated into the real append predicate? Exercise 4.15.2 Does your Prolog have a module system? Check manuals and Appendix A. If so, describe it briey.
icate is called;
- denotes an argument that is normally not instantiated until this predicate
instantiates it;
Authors manuscript
Sec. 4.16.
115
writename+Number Writes "One", "Two", or "Three" to describe Number. Fails if Number is not 1, 2, or 3. writename1 :- write'One'. writename2 :- write'Two'. writename3 :- write'Three'.
writeq_string+String Given a list of ASCII codes, writes the corresponding characters in quotes. writeq_stringString :write'"', write_string_auxString, write'"'. writeq_string_aux First|Rest :putFirst, write_string_auxRest. writeq_string_aux .
append?List1,?List2,?List3 Succeeds if List1 and List2, concatenated, make List3. append Head|Tail ,X, Head|Y :- appendTail,X,Y. append ,X,X.
Figure 4.1
Authors manuscript
116
Chap. 4
Some programmers use @ to denote arguments that contain variables which must not become instantiated. The next two lines describe, in English, what the predicate does. This description is as concise as possible. Then comes the predicate denition itself, followed by any auxiliary predicates that it uses internally. Note that +, -, and ? (and @, if you use it) describe how the predicate is normally meant to be called; we dont guarantee that it will go wrong if called with the wrong set of instantiations (but we dont guarantee that it will go right, either).
Exercise 4.16.1 Add comments, in the prescribed format, to mother and father in FAMILY.PL. Exercise 4.16.2 Add comments, in the prescribed format, to your latest versions of absval and flatten (from previous exercises). Exercise 4.16.3 Add a comment, in the prescribed format, to the following predicate (which should be familiar from Chapter 3):
fast_reverseOriginal,Result :nonvarOriginal, fast_reverse_auxOriginal, ,Result. fast_reverse_aux Head|Tail ,Stack,Result :fast_reverse_auxTail, Head|Stack ,Result. fast_reverse_aux ,Result,Result.
4.17.1. Recursion
First lets compute by hand the Prolog query
Authors manuscript
Sec. 4.17.
117
?- memberc, a,b,c,X .
This does not match clause 1 , but it matches the head of clause 2 . But we must remember that the variable X in clause 2 and the variable X in our goal 1 are really different variables. So we will begin by rewriting clause 2 so the variables not only are different but also look different. Since we are matching goal 1 with clause 2 , we will attach the digit 1 to each of the variables in clause 2 to distinguish them. This will also make it easier for us to remember at which step in our computation this invocation of clause 2 occurred. Rewritten, clause 2 looks like this:
2.1 memberX1, _|Y1 :- memberX1,Y1.
(By 2.1 we mean the instance of clause 2 used in satisfying goal 1.) Now the goal matches the head of 2.1 with the following unications:
X1 = c _ = a Y1 = b,c,X .
In what follows we will usually omit anonymous variables because their values are not saved.
Recall that the unications we just made apply to the body of 2.1 as well as to its head. The body of 2.1 , with these unications in effect, becomes our new goal, designated 2:
1 ?- memberc, a,b,c,X . 2 ?- memberc, b,c,X .
Goal 2 will not match clause 1 . But it matches the head of clause 2 , which we rewrite as
pointers are one step behind those that would actually be used in an implementation of Prolog. Practical Prolog systems store pointers to the next clause that should be tried, rather than the clause that has just been used. And if the last clause that has just been used is the last clause of the predicate (as in this case), no pointer is stored at all. Thats the key to tail recursion optimization.
2 Our
Authors manuscript
118
2.2
Chap. 4
(This is another invocation of 2 , distinct from 2.1 .) To get the match, we must unify some variables. We add these new instantiations to our previous set:
X1 = c X2 = c Y1 = Y2 = b,c,X c,X
Next, we set the pointer for goal 2 at clause 2 so we can remember which clause we used to satisfy 2.
1 2 memberX, X|_ . memberX, _|Y :- memberX,Y. -1 -2
and we replace goal 3 with the instantiated body of 1.3 . But 1.3 has no body, so we have no goals left to satisfy. Success! The original query (goal 1) has been satised. Since the variable X in the original query was never instantiated, Prolog prints something like X = _0021 as the solution to the query.
4.17.3. Backtracking
Suppose we ask Prolog to look for another solution. Then it will try to resatisfy 3. But before trying to resatisfy goal 3, it must undo all the variable instantiations that were established when it last satised 3. In particular, X3 loses its value and the list of instantiations reverts to:
X1 = c X2 = c Y1 = Y2 = b,c,X c,X
We see that the pointer for 3 is pointing to clause 1 , so we must now try a clause subsequent to 1 . Goal 3 will match clause 2 , rewritten
Authors manuscript
Sec. 4.17.
2.3
119
New instantiations, moving the pointer for 3, and replacing the current goal with the instantiated body of the rule just invoked gives us this situation:
X1 = c X2 = c X3 = c 1 2 Y1 = Y2 = Y3 = b,c,X c,X X
-1
-2
-3
1 ?- memberc, a,b,c,X . 2 ?- memberc, b,c,X . 3 ?- memberc, c,X . 4 ?- memberc, X .
Now that we have the general idea, lets move a little faster. Matching 4 with
1 rewritten 1.4 memberX4, X4|_ .
produces
X1 X2 X3 X4 1 2 = = = = c c c X = c Y1 = Y2 = Y3 = b,c,c c,X c
-1
-2
-3
We have no new goal since 1 is a fact. Prolog prints the solution X = c. Again we ask for additional solutions. Prolog tries to resatisfy 4, rst de instantiating X4 and X. Now 4 will match 2 , rewritten as
2.4 memberX4, _|Y4 :- memberX4,Y4.
-1
-2
-3
-4
Authors manuscript
120
Chap. 4
1 ?- memberc, a,b,c,X . 2 ?- memberc, b,c,X . 3 ?- memberc, c,X . 4 ?- memberc, X . 5 ?- memberc,
.
But 5 fails since it will match neither 1 nor the head of 2 . There are no more rules for 4 to match, so Prolog backs up and tries to resatisfy 3. After undoing the appropriate instantiations, the situation is this:
X1 = c X2 = c 1 2 Y1 = Y2 = b,c,X c,X
-1
-2
-3
There are no more rules for member, so there is nothing else that 3 can match, so its time to redo goal 2. Prolog deinstantiates X2 and Y2, producing:
X1 = c 1 2 Y1 = b,c,X
-1
-2
and tries to resatisfy 2. But there are no more rules for 2 to try, so Prolog backs up and tries to resatisfy 1 with the following situation:
No instantiations all undone due to backtracking. 1 2 memberX, X|_ . memberX, _|Y :- memberX,Y.
-1
Now there are no more clauses for 1 to try, so there are no more alternatives. Prolog has failed to nd another solution for our query, so it prints no.
4.17.4. Cuts
Lets compute the same query ?- memberc, a,b,c,X . again, but with a slightly different denition of member:
1 2 memberX, X|_ :- !. memberX, _|Y :- memberX,Y.
This example will show us how to do a hand computation where a cut is involved. This computation will look exactly like our previous computation until the following situation is reached:
X1 = c X2 = c Y1 = Y2 = b,c,X c,X
Authors manuscript
Sec. 4.17.
1 2
121
-1
-2
-1
-2
1 ?- memberc, a,b,c,X . 2 ?- memberc, b,c,X . 3 ?- memberc, c,X . 4 ?- !.
Now the current goal is a cut. Recall that the cut is supposed to prevent backtracking. When we execute it, we promise that we will not try to resatisfy any previous goals until we backtrack above the goal that introduced the cut into the computation. This means that we must now mark some of the lines in our goal stack to indicate that we cannot try to resatisfy them. The lines that we must mark comprise the current line and all lines above it until we get to the line that introduced the cut into the computation in this case, line 3. We will use exclamation marks for markers.3
1 ?- memberc, a,b,c,X . 2 ?- memberc, b,c,X . ! 3 ?- memberc, c,X . ! 4 ?- !.
We have no more goals to satisfy, so our query has succeeded. The X in the topmost goal was never instantiated, so Prolog prints out something like X = _0021 as a solution to the query. Suppose we ask for an alternative solution. Then we must backtrack. Lines 3 and 4 are marked as not resatisable, so we try to resatisfy 2. The situation is then:
X1 = c X2 = c 1 2 Y1 = Y2 = b,c,X c,X
-1
-2
3 Note that we could get the same effect by moving -3 to the end of the last clause which would mean, in effect, that -3 would no longer be a backtrack point. Thats how a real Prolog interpreter does it: all backtrack points introduced after entering goal 3 get discarded by the cut.
Authors manuscript
122
1 ?- memberc, a,b,c,X . 2 ?- memberc, b,c,X .
Chap. 4
There are no more clauses for 2 to match, so it fails and we backtrack to 1. The pointer for 1 also drops off the bottom of the database, and our original query fails. The difference between this computation and the earlier one is that the cut in clause 1 prevents us from nding the second solution. Instead of the two solutions X = _0021 and X = c that the rst computation produced, this second computation produces only the second of these results.
will produce the result X = b,a . If we ask for alternative solutions, a curious thing happens: computation continues until we interrupt it or until a message tells us that Prolog has run out of memory. What has gone wrong? We can determine the answer by doing a hand computation of our query. The relevant clauses in our knowledge base are
1 2 3 4 reverse , . reverse H|T ,L :- reverseT,X, appendX, H ,L. append ,X,X. append H|T ,L, H|TT :- appendT,L,TT.
-1
(For brevity, we are no longer listing the whole goal stack or the bodies of the clauses, although if you are following along with pencil and paper, you may want to write them in.) Goal 2 consists of two goals joined by a comma. Our rst task is therefore to satisfy the rst part of 2. This matches 2 producing:
Authors manuscript
Sec. 4.17.
123
T1 =
H2|T2
X1 = L2
-1
-2
Notice that 3 is the result of replacing the rst goal in 2 with the instantiated body of 2 . The remainder of 2 is just recopied, becoming the nal goal in 3. Our current goal is now the rst goal in 3, which matches 1 producing:
X = H1| H2|T2 L1 = a,b T2 = X2 = . 1 2 3 4 -3 -1 = H1,H2 T1 = H2| = H2 X1 = L2
-2
4 ?- append
We have simplied the value of X in our list of instantiations. We will do this without further comment in subsequent steps. Since 1 is a fact, the rst goal in 3 disappears, and 4 consists of the two remaining goals. Now for the rst goal in 4. This matches 3 producing the situation:
X = H1,H2 L1 = a,b L2 = H3 = 1 2 3 4 -3 -1 -4 T1 = T2 = H2 X1 = H2 H2 = H3
H2
-2
We lost a goal going from 4 to 5 because we matched the rst goal in 4 with a fact. The new current goal will not match 3 since H2 will only match a list with at least one member. So our current goal matches 4 to produce:
X = H1,H2 = H1,a L1 = a,b L2 = H2 = a L5 = H1 T1 = T2 = T5 = H2 = a X1 = H2 = a H2 = H3 = H5 = a TT5 = b
Authors manuscript
124
1 2 3 4 -3 -1 -4 -5 -2
Chap. 4
6 ?- append
, H1 , b .
Goal 6 matches 3 with H1 = H6 = b. Since 3 is a fact, we have no goals left and our query succeeds. The nal situation is:
X = H1,a = b,a L1 = a,b L2 = a L5 = H1 = b H6 = H1 = b 1 2 3 4 -3 -1 -4 -5 T1 = T2 = T5 = a X1 = a H2 = H3 = H5 = a TT5 = b
-2 -6
Notice that every variable has been instantiated to some constant or list of constants. Prolog prints the solution, which comprises the instantiations for all variables occurring in the original query ?- reverseX, a,b . namely X = b,a . What happens if we ask for another solution? We retry 6, rst deinstantiating H6. Goal 6 will not match 4 since has no rst member. So the pointer for 6 disappears and we retry 5 after deinstantiating H5, T5, L5, and TT5. The pointer is at the bottom of the database, so there are no more clauses to try. Backtrack to 4, moving the pointer for 4 down to 4 . The situation is then:
X = H1| H2 L1 = a,b T2 = X2 1 2 3 4 -3 -1 -4 = H1,H2 T1 = H2| = H2 X1 = L2
-2
But 4 doesnt match 4 , so 4 fails and we backtrack to 3. Goal 3 matches 2 producing:
X = H1| H2|T2 L1 = a,b T1 = H2|T2 = X1 = L2 T2 = H3|T3 X2 = L3 = H1,H2|T2 = = H1,H2| H3|T3 H2,H3|T3 = H1,H2,H3|T3
H2| H3|T3
Authors manuscript
Sec. 4.17.
125
1 2 3 4
-1
-2
-3
-2
-3
5 ?- append
-2
-3
X2 = X3 =
H6
Authors manuscript
126
1 2 3 4 -4 -1 -5 -6 -2 -3
Chap. 4
7 ?- append
H6| H2 T3 = T6 =
H6,H2 X3 = TT6 = H3 = H6 H2
-2 -7
-3
Without tracing further, we can see that 8 must fail. We cannot append a list with two members and a list with one member to produce a list with only two members. Also, we cannot resatisfy 7 since one of the arguments is and therefore will not unify with 4 . The marker for 6 drops off the database. We cannot resatisfy 5 because of . So we backtrack all the way to 4, deinstantiate all the variables with subscripts higher than 3, move the pointer for 4 down to 2 , and the situation is:
X = H1,H2,H3|T3 L1 = a,b X2 = L3 1 2 3 4 4 T1 = T2 = H2,H3|T3 H3|T3 X1 = L2
-1
-2
-3
-4
Notice that the current goal is now the same as when we were at 3. If we continue, we will reach a situation where X = H1,H2,H3,H4|T4 and the current goal will be reverseT4,X4. But of course the reverse of a,b cant have four members any more than it could have three. So a new variable will be added to the list X and the process will repeat until we ll the stack and the machine stops.
Authors manuscript
Sec. 4.18.
Bibliographical Notes
127
This last example shows how we can use hand computations to discover why Prolog behaves the way it does when it does something unexpected. Sometimes using the trace facility built into a Prolog interpreter is confusing since we only see the current goal and cannot at the same time view either the preceding goals or the knowledge base with the pointers to the clauses used to satisfy the preceding goals. A good method to better understand what the trace is telling you is to carry on a hand computation on paper as you run the trace.
Exercise 4.17.1 Try some of the hand computations in this section, and trace the same computations using the Prolog debugger. Compare the results. Does your debugger display numbers that indicate which clauses are involved in each query?
Authors manuscript
128
Chap. 4
Authors manuscript
Chapter 5
129
Authors manuscript 693 ppid September 9, 1995 Prolog Programming in Depth
130
?- read_atomWhat. this is an atom (typed by user) What = 'this is an atom' ?- read_numWhat. 3.1416 (typed by user) What = 3.1416
Chap. 5
To make this work, well rely on read_str, dened in Chapter 3 and shown, along with some other predicates, in Figure 5.1. Many programs later in this book will assume that the predicates in this le (READSTR.PL) are available and have been debugged to run properly on your computer. Usually, what you want from the input is not a list of character codes, but an atom or number. As we will see in Chapter 7, strings (lists) are bulky, and character string data should be stored as atoms where feasible. And conversion of strings to numbers is obviously essential if you want to read numeric data from the keyboard or from text les. There are three ways to approach the conversion process. First, the ISO Prolog standard denes two builtin predicates, atom_codes and number_codes, which interconvert, respectively, atoms with strings and numbers with strings, like this:
?- atom_codesabc,What. What = 97,98,99 ?- atom_codesWhat,"abc". What = abc ?- number_codes3.14,What. What = 51,46,49,52 ?- number_codesWhat,"3.14". What = 3.14
If number_codes is given a string that doesnt make a valid number, or if either of its arguments is of the wrong type, it raises a runtime error condition. We will explore how to deal with these errors in the next section.1 Many older Prologs use name 2 to perform both kinds of conversions:
?- nameabc,What. What = 97,98,99 ?- nameWhat,"abc". What = abc ?- name3.14,What. What = 51,46,49,52 ?- nameWhat,"3.14". What = 3.14
But there are a few Prologs in which name has the behavior prescribed for atom codes, and the conversion of numbers is done some completely different way. Moreover,
1 PreISO versions of Quintus Prolog have these same predicates, but they are called atom chars and number chars. The ISO standard has predicates called atom chars and number chars, but they produce lists of characters l,i,k,e,' ',t,h,i,s rather than strings.
Authors manuscript
Sec. 5.2.
131
File READSTR.PL Reading and writing lines of text Uses get0, and works in almost all Prologs not Arity.
read_str-String Accepts a whole line of input as a string list of ASCII codes. Assumes that the keyboard is buffered. read_strString :- get0Char, read_str_auxChar,String. read_str_aux-1, read_str_aux10, read_str_aux13, :- !. :- !. :- !. end of file end of line UNIX end of line DOS
read_atom-Atom Reads a line of input and converts it to an atom. See text concerning name 2 vs. atom_codes 2. read_atomAtom :read_strString, nameAtom,String.
or preferably atom_codesAtom,String.
read_num-Number Reads a line of input and converts it to a number. See text concerning name 2 vs. number_codes 2. read_numAtom :read_strString, nameAtom,String.
or preferably number_codesAtom,String.
write_str+String Outputs the characters corresponding to a list of ASCII codes. write_str Code|Rest :- putCode, write_strRest. write_str .
Figure 5.1
Authors manuscript
132
Chap. 5
name generally deals with errors by simply failing, but check your implementation
to be sure. In what follows, well assume that youve successfully implemented read_atom and read_num, dened roughly as follows:
read_atomAtom :- read_strString, atom_codesAtom,String. read_numNum :- read_strString, number_codesNumber,String.
and that these are in the le READSTR.PL (Figure 5.1). Be sure to check that they work correctly in your Prolog.2 There is a third way to convert strings into numbers: pick off the digits one by one, convert them to their numeric values, and do the arithmetic. Specically, what you do is maintain a running total, which starts as 0. Each time you get another digit, multiply the total by 10, then add to it the value of that digit. For example, converting the string "249", youd do the following computations: Digit 2 Digit 4 Digit 9
You may never have to do this, but the algorithm is worth remembering in case you have to deal with digits in an unusual base or format (e.g., hexadecimal, or separated by commas).
Exercise 5.2.1 On your computer, when you hit Return on the keyboard, does get0 (ISO get_code) read it as code 10, code 13, or both? What if you read from a text le using see? Explain how you found out. Does read_str need any modication in order to work reliably when reading from les on your computer? If so, indicate what changes should be made. Exercise 5.2.2 Are read_str and write_str tail recursive? Explain. Exercise 5.2.3 Get read_atom and read_num working on your computer. Store the working versions in le READSTR.PL. What does read_num do when its input is not a validly written number? Exercise 5.2.4 Put read_atom to practical use by modifying LEARNER.PL (Chapter 2) to accept input without requiring the user to use Prolog syntax.
2 In
ALS Prolog, use name to convert atoms, and deal with numbers thus:
Authors manuscript
Sec. 5.3.
133
Exercise 5.2.5 Modify read_atom to convert all letters to lower case as they are read in. Call the modied procedure read_lc_atom. Exercise 5.2.6 Dene a procedure, read_hex_num, that is like read_num except that it reads hexadecimal numbers:
?- read_hex_numWhat. 20 What = 32 ?- read_hex_numWhat. 10fe What = 4350
Use a digitbydigit conversion algorithm similar to the one described in this section, but adapted for hexadecimal.
means, If READSTR.PL has not already been consulted, consult it now. Thats exactly what you need. (Of course it works only if READSTR.PL is in the current directory.) Other Prologs dont have ensure_loaded. A reasonable substitute is to use reconsult, like this:
:- reconsult'readstr.pl'.
On encountering this line in your program, the Prolog system will reconsult READSTR.PL, whether or not READSTR.PL has been consulted before. This wastes a bit of time, but nothing serious goes wrong. Early versions of Arity Prolog (before 5.0) have trouble with nested reconsults. If you nd that you cannot include a reconsult directive in a le that is being reconsulted, your best option is to copy one program, whole, into the other, thus bypassing the problem of how to reconsult it.
Exercise 5.3.1 Does your Prolog support ensure_loaded? If not, do embedded reconsult directives work correctly? Experiment and see.
Authors manuscript
Chap. 5
Any time a program accepts input from the user at the keyboard, two problems can arise: The user may type something that is not an acceptable answer (e.g., 4 when the menu choices are 1, 2, and 3). The user may type something that is not even interpretable (e.g., xyz when a number is expected). In either case the program should do something sensible. Its convenient to use a
repeat loop to validate user input, like this: get_numberN :- repeat, write'Type a number between 1 and 3: ', read_numN, N = 3, N = 1, !.
If the user types a number that is out of range, execution backtracks to the repeat goal, the computer prints the prompt again, and the user gets another chance. If there are several discrete choices, its convenient to use member with a list of alternatives:
get_choiceC :- repeat, write'Type a, b, or c: ', read_atomC, memberC, a,b,c , !.
The last example shows that you can use all the power of Prolog inference to decide what answers are acceptable. A greater challenge arises when the user types uninterpretable input, such as letters where a number is expected. In some Prologs, read_num will simply fail in such a case:
?- read_numN. (typed by user) asdf no.
Authors manuscript
Sec. 5.5.
Constructing Menus
135
Thats simple enough: you can use repeat loops just as shown above, and execution will backtrack from uninterpretable answers the same way as from answers that have been examined and found unacceptable. In other Prologs, uninterpretable numbers cause runtime errors, stopping the program. Fortunately, the ISO standard includes a builtin predicate, catch, which can catch these errors and keep them from interrupting the program. If you have ISO Prolog available, try this:
read_num_or_failN :catchread_numN,_,fail.
Naturally, menu 2 would normally be called from another procedure that needs to display a menu. Its rst argument consists of a list of the form
itemMessage1,Value1,itemMessage2,Value2,itemMessage3,Value3
with up to 9 items, each consisting of a message to be displayed and a result to be returned (in the second argument position) if the user chooses that item. The message is normally an atom; the value can be a term of any kind.
Authors manuscript
136
Chap. 5
The menus that you get this way are very unsophisticated. In fact, they ask the user to choose an arbitrary number, which is a relatively errorprone kind of choice. Menus that use initial letters or mouse pointers are easier to use and less subject to errors. Note, however, that you could easily replace menu 2 with any other kind of menuing routine that you care to implement. The important thing is that you specify what the menu choices are to be, and menu 2 takes care of the rest of the work. Thus, menu 2 establishes an important conceptual boundary between deciding what is to be on the menu, and deciding how to implement the menu. Few other languages let you make such a clean distinction.
Exercise 5.5.1 Get menu 2 working on your computer and verify that it works correctly. Exercise 5.5.2 Note that menu 2 uses a recursive loop to deal with invalid choices. Rewrite it to use a repeat loop instead. Exercise 5.5.3 Rewrite menu 2 so that the user makes a choice by typing, not a number, but the rst letter of the word to be chosen.
because get0 and get_byte are the same thing. In Arity Prolog, use this denition instead:
Authors manuscript
Sec. 5.6.
137
File MENU.PL A menu generator in Prolog menu+Menu,-Result Displays a menu of up to 9 items and returns the user's choice. Menu is a list of the form itemMessage,Value,itemMessage,Value... where each Message is to be displayed and Value is to be returned as Result if the user chooses that item. menuMenu,Result :- menu_displayMenu,49,Last, menu_chooseMenu,49,Last,Result, nl. Display all the messages and simultaneously count them. The count starts at 49 ASCII code for '1'. menu_display ,SoFar,Last :!, not an item, so don't use this number Last is SoFar - 1. menu_display itemMessage,_|Tail ,SoFar,Last :put32, put32, putSoFar, appropriate digit put32, blank writeMessage, nl, Next is SoFar + 1, menu_displayTail,Next,Last. Get the user's choice. If invalid, make him her try again. menu_chooseMenu,First,Last,Result :write'Your choice ', putFirst, write' to ', putLast, write': ', getChar, menu_choose_auxMenu,First,Last,Result,Char.
Figure 5.2
Authors manuscript
138
Chap. 5
menu_choose_auxMenu,First,Last,Result,Char :Char = First, Char = Last, !, menu_selectMenu,First,Char,Result. menu_choose_auxMenu,First,Last,Result,_ :put7, beep put13, return to beginning of line menu_chooseMenu,First,Last,Result. Find the appropriate item to return for Char menu_select item_,Result|_ ,First,First,Result :- !. menu_select _|Tail ,First,Char,Result :NewFirst is First+1, menu_selectTail,NewFirst,Char,Result.
Demonstrate the whole thing demo :- menu item'Georgia',ga, item'Florida',fl, item'Hawaii',hi ,Which, write'You chose: ', writeWhich, nl.
End of MENU.PL
Authors manuscript
Sec. 5.7.
139
Experimentation may be needed to determine how to best dene get_byte in your Prolog.3
Exercise 5.6.1 Get get_byte working on your computer. Ensure that it works correctly not only when reading from the keyboard, but also when reading a le using see. Exercise 5.6.2 On your computer, does get_byte (as you have dened it) let you repeatedly attempt to read past the same end of le, or does it give a runtime error if you bump into the same endofle mark more than once?
usually means Read a term into X from the le whose handle is H. The handle is a value that is given to you when you open the le. Unfortunately, the syntax for accessing les in this way is widely variable. The proposed ISO system is described in Appendix A; the actual syntax used in Quintus Prolog and SWI Prolog looks like this:4
test :- open'myfile1.txt',read,File1, readFile1,Term, closeFile1, open'myfile2.txt',write,File2, writeFile2,Term, closeFile2.
The idea is that the open predicate opens a le for either reading or writing, and instantiates File1 to its handle. You then give File1 as an argument of all subsequent predicates that use the le.
3 In Cogent Prolog 2.0, to read all the bytes of a le transparently, you will have to open it as binary and access it by le handle (see the next section and your manual). As far as we can determine, users of ALS Prolog 1.2 are simply out of luck, unless they want to link in a subroutine written in C. 4 For an Arity Prolog example see Appendix B.
Authors manuscript
140
Exercise 5.7.1
Chap. 5
Adapt the example just given so that it works on your computer (or demonstrate that it already works asis). Exercise 5.7.2 Adapt the predicates in READSTR.PL to take an extra argument for the le handle, and get them working on your computer. Noticethat you can add the new predicates to READSTR.PL without conict, because they have different arities than the old ones.
Notice that in Prolog, we often count down when an ordinary programming language would count up (for i:=1 to N or the like). That lets us compare the loop variable to 0 (a constant) rather than comparing to N (which would have to be another parameter supplied at runtime). The version of read_bytes that well actually use is more complicated and is shown in Figure 5.4. It uses one character of lookahead to check for an unexpected
z
12
z
10
z
12
| z | z
4 4
Each line may be followed by a 1 or 2byte endofline mark, depending on the operating system and the le format.
Figure 5.3
Authors manuscript
Sec. 5.8.
FixedLength Fields
141
endofle mark. In Figure 5.4 we also dene skip_bytes, which skips a specied number of bytes without storing them in a list. Most Prologs also include a seek command to jump to a particular position in a randomaccess le, but we do not use it here because of lack of standardization. Even the ISO standard makes the details of this operation up to the implementor.
Exercise 5.8.1 Get read_bytes and skip_bytes working on your computer. Demonstrate that they work by using them to read FIXEDLEN.DAT. Exercise 5.8.2 On your computer, what does read_bytes do if you repeatedly try to read past the end of the le? Exercise 5.8.3 Why is ?- skip_bytes80. faster than ?- read_bytes80,_.? Exercise 5.8.4 Dene a predicate read_record 1 that will read FIXEDLEN.DAT one record at a time, returning each record as a Prolog term, like this:
?- see'fixedlen.dat', read_recordA, read_recordB, seen. A = record'Covington ','Michael ','Athens ','Ga. ',4633 B = record'Nute ','Donald ','Athens ','Ga. ',5462
You will have to use read_bytes to read the individual elds as strings, and then use name (or atom_codes and number_codes) to convert the strings to atoms or numbers as appropriate. Be sure you dont forget the endofline mark, which will be either 1 or 2 bytes long depending on the operating system. Exercise 5.8.5 Is 'Athens Exercise 5.8.6 Modify read_record to discard blanks at the beginning and end of each eld, so that you get 'Athens' (etc.) without unnecessary blanks. Call your new procedure read_record_1. Exercise 5.8.7 Modify read_bytes so that it if hits the end of the le, it returns the atom end_of_file instead of returning a string. Call your new procedure read_bytes_1. Exercise 5.8.8 Using read_bytes_1, modify read_record so that it, too, returns end_of_file if it hits the end of the le. Call your new procedure read_record_2.
' the same term as 'Athens'? Explain.
Authors manuscript
142
Chap. 5
File READBYTE.PL Reads fixed-length fields from files. Insert appropriate definition of get_byte here: get_byteC :- get0C. read_bytes+N,-String Reads the next N bytes from current input, as a list of ASCII codes. Stops if end of file is encountered prematurely. read_bytesN,String :get_byteC, read_bytes_auxC,N,String. read_bytes_aux-1,_, :- !. end of file, so stop no more bytes to read, so stop
read_bytes_auxC,1, C :- !.
read_bytes_auxC,N, C|Rest : keep going get_byteNextC, NextN is N-1, read_bytes_auxNextC,NextN,Rest. skip_bytes+N Skips the next N bytes on standard input skip_bytes0 :- !. skip_bytesN :N 0, get_byteC, skip_bytes_auxC,N. skip_bytes_aux-1,_ :- !. skip_bytes_aux_,N :NextN is N-1, skip_bytesNextN. special case ordinary case
Demonstration, should print demo :- see'fixedlen.dat', skip_bytes22, read_bytes12,String, seen, writeString, nl.
65,116,104,101,110,115,32,32,32,32,32,32
Figure 5.4
Authors manuscript
Sec. 5.9.
143
the computer should open FIXEDLEN.DAT, start reading, and eventually yield the answer:
Name='Covington' FirstName='Michael' State='Ga.' Num=4633
To simplify matters, do not look for alternative solutions; use only the rst record that matches the query.
Authors manuscript
144
Chap. 5
Figure 5.5
a specic code (or end of le or end of line, whichever comes rst). Then, to read a commadelimited eld, simply read_until a comma. The quotes complicate the problem slightly. The actual algorithm is to read the rst character of the eld, and then, if it is not a quote, read_until a comma. But if the eld begins with a quote, the computer must read_until the closing quote (thereby obtaining the data), then read_until the comma (to discard it). Figure 5.6 shows the complete implementation.
Exercise 5.10.1 Get read_cdf working on your computer. Demonstrate that it can read COMMADEL.DAT eldbyeld. Exercise 5.10.2 Reimplement read_record from two sections back, but this time, make it use read_cdf to read COMMADEL.DAT. Call it read_cdf_record.
Authors manuscript
Sec. 5.11.
Binary Numbers
145
File READCDF.PL Reading comma-delimited fields from a file Insert suitable definition of get_byte here get_byteC :- get0C.
read_until+Target,-String Reads characters until a specific character is found, or end of line, or end of file. read_untilTarget,String :get_byteC, read_until_auxC,Target,String. read_until_aux-1,_, read_until_aux13,_, read_until_aux10,_, read_until_auxT,T, :- !. :- !. :- !. :- !. end of file, so stop end of line DOS end of line UNIX found the target, so stop keep going
read_cdf-String Reads a comma-delimited field. read_cdfString :get_byteFirstChar, look at first character read_cdf_auxFirstChar,String. read_cdf_aux34,String :!, read_until34,String, read_until44,_. read_cdf_auxC,String :read_until_auxC,44,String. field begins with " read until next " consume the following comma field does not begin with " just in case C is -1 or 10...
Figure 5.6
Authors manuscript
146
Chap. 5
File READI16.PL Routines to read 16-bit binary integers from a file Assumes less significant byte comes first as on PC's, not Sparcstations. If this is not the case, swap Lo and Hi in read_u16 1.
:- ensure_loaded'readbyte.pl'.
read_u16-Integer Reads 2 bytes as a 16-bit unsigned integer, LSB first. read_u16I :read_bytes2, Lo,Hi , I is Hi*256 + Lo. 16-bit unsigned integer
read_i16-Integer Reads 2 bytes as a 16-bit signed integer, LSB first. read_i16I :read_u16U, u16_to_i16U,I. 16-bit signed integer
u16_to_i16+U,-I Converts 16-bit unsigned to signed integer. u16_to_i16U,I :- U u16_to_i16U,U. 32767, !, I is U - 65536.
Figure 5.7
Authors manuscript
Sec. 5.11.
Binary Numbers
147
Even oatingpoint numbers can be stored in binary, and a huge variety of formats is used. We will look at IEEE 64bit format, a representation recommended by the Institute of Electrical and Electronic Engineers. In IEEE 64bit format, a oatingpoint number consists of: One bit to denote sign (1 for negative, 0 for positive); 11 bits for the exponent, biased by adding 1023; 56 bits for the mantissa, without its rst digit (which is always 1). The MANTISSA and EXPONENT are the parts of a number written in scientic notation. For example, in the number 3:14 1023 , 3.14 is the mantissa and 23 is the exponent. Naturally, IEEE 64bit format is binary, not decimal. The rst digit of the mantissa is always 1 because oatingpoint numbers are normalized. To understand normalization, think about how numbers are written in scientic notation. We never write 0:023 103 instead, we write 2:3 101 . That is, we shift the mantissa so that its rst digit is nonzero, and adjust the exponent accordingly. Thats called NORMALIZATION. Now in binary, if the rst digit is not 0, then the rst digit is necessarily 1. Thats why the rst digit of the mantissa of a normalized binary oatingpoint number can be omitted. Finally, the exponent is normalized by adding 1023 so that the available range will be ,1023 to +1024, rather than 0 to 2047. So if we can evaluate the sign, the mantissa, and the exponent, the value of the oatingpoint number is given by the formula: Value = ,1Sign 1 + Mantissa 2Exponent,1023 Recall that Sign is either 1 or 0. The evaluation is clumsy because the boundaries between sign and exponent, and between exponent and mantissa, fall within rather than between the bytes. Fortunately, we can pick the bytes apart using simple arithmetic: if B is the value of an 8bit byte, then B 128 is its rst bit and B mod 128 is the value of its remaining bits. In the same way, we can split a byte in half by dividing by 16. There are two special cases: If the mantissa and exponent are all zeroes, then the number is taken to be exactly 0.0 (not 1 2,1023 , which is what the formula gives, and which would otherwise be the smallest representable number). This gets around the problem that the mantissa of 0 never begins with 1, even after normalization. If all 11 bits of the exponent are 1, the number is interpreted as not a number (NaN), i.e., a number with an unknown or uncomputable value. (There is a further distinction between NaN and innity, which we will ignore here.) One last subtlety: the bytes are stored in reverse order (least signicant rst); that is, they are read in the opposite of the order in which you would write a number in binary. This affects only the order of the bytes themselves, not the order of the bits within the bytes.
Authors manuscript
148
Chap. 5
Figure 5.8 contains the actual code to read IEEE 64bit oatingpoint numbers. It is not elegant, but it shows that Prolog has the power to handle unfamiliar data formats, even very exotic ones. You do not have to have 64bit arithmetic to run this code; it works in any oatingpoint arithmetic system. IEEE and other oatingpoint number formats are described concisely by Kain (1989:456460). Arity Prolog has a large library of builtin predicates for reading binary numbers; other Prologs may also have libraries to handle similar tasks.
Exercise 5.11.1 Get read_i16 and read_u16 working on your computer. Test them by reading the byte codes of the characters !!, which should decode as 8224 (the same whether signed or unsigned). Exercise 5.11.2 (small project)
Using a C or Pascal program, write some 16bit binary integers to a le, then read them back in using a Prolog program. (To write binary integers in C, use fwrite with an integer argument; in Pascal, create a file of integer.) Exercise 5.11.3 Get read_f64 working on your computer. Test it by reading the byte codes of the characters !!!ABCDE, which should decode as approximately 4:899 1025 . Exercise 5.11.4 (small project)
If you have access to a C or Pascal compiler that uses IEEE oatingpoint representation, then do the same thing as in Exercise 5.11.2, but with oatingpoint numbers. Exercise 5.11.5 Modify read_u16, read_i16, and read_f64 so that each of them returns the atom end_of_file if the end of the le is encountered where data is expected.
5.12.
GRAND FINALE:
Figure 5.9 shows a Prolog program that reads spreadsheets in Lotus .WKS format. Because so much information in the business world is stored in spreadsheets, or is easily imported into them, a program like this can greatly extend the usefulness of Prolog. Walden (1986) gives a full description of .WKS format, which is the le format used by early versions of Lotus 123. More recent spreadsheet programs can still use .WKS format if you tell them to, although it is no longer the default. The spreadsheet le consists of a series of FIELDS (RECORDS), each of which comprises: A 16bit OPCODE (operation code) indicating the type of eld; A 16bit number giving the length of the eld; The contents of the eld, which depend on its type.
Authors manuscript
Sec. 5.12.
149
File READF64.PL Routine to read IEEE 64-bit binary numbers from a file :- ensure_loaded'readbyte.pl'. or use reconsult if necessary
read_f64-Float Reads 8 bytes as an IEEE 64-bit floating--point number. read_f64Result :read_bytes8, B0,B1,B2,B3,B4,B5,B6,B7 , Sign is B7 128, 1 bit for sign B7L is B7 mod 128, first 7 bits of exponent B6H is B6 16, last 4 bits of exponent B6L is B6 mod 16, first 4 bits of mantissa read_f64_auxB0,B1,B2,B3,B4,B5,B6L,B6H,B7L,Sign,Result. read_f64_aux0,0,0,0,0,0,0,0,0,_, 0.0 :- !. read_f64_aux_,_,_,_,_,_,_,15,127,_, not_a_number :- !. read_f64_auxB0,B1,B2,B3,B4,B5,B6L,B6H,B7L,Sign, Result :Exponent is B7L*16 + B6H - 1023, Mantissa is B0 256+B1 256+B2 256+B3 256+B4 256+B5 256+B6L 16 + 1, power-1,Sign,S, power2,Exponent,E, Result is S * Mantissa * E.
powerX,N,Result Finds the Nth power of X for integer N. Needed because some Prologs still don't have exponentiation in the 'is' predicate. power_,0,1 :- !. powerX,E,Result :E 0, !, EE is E-1, powerX,EE,R, Result is R*X. powerX,E,Result : E 0, EE is E+1, powerX,EE,R, Result is R X.
Figure 5.8
Authors manuscript
150
Chap. 5
The bulk of the elds in an ordinary spreadsheet are NONDATA FIELDS that is, they contain information about how to print or display the spreadsheet. In order to preserve all defaults, a full set of these is saved with even the smallest spreadsheet. Accordingly, we can ignore nearly all the opcodes. The opcodes that are signicant are: 0 Beginning of le 1 End of le 13 Integer 14 Floatingpoint constant 15 Text constant (label) 16 Formula with stored oatingpoint value Although our program does not try to decode formulas, it would be quite possible to do so, producing expressions that could be evaluated using is. We content ourselves with retrieving the oatingpoint value that is stored along with each formula. Like all the examples in this chapter, LOTUS.PL reads from standard input. Naturally, in any practical application, it should be adapted to read from a le identied by a handle.
Exercise 5.12.1 Get LOTUS.PL working on your computer. Write a procedure, dump spreadsheet, which reads an entire spreadsheet le and writes out the contents of each eld. (The program disk accompanying this book contains a spreadsheet le, SAMPLE.WKS, which you can use for testing. Be sure that if you transfer SAMPLE.WKS from one computer to another, it is transferred in binary form.) Exercise 5.12.2 Modify LOTUS.PL to complain if the rst eld of the spreadsheet is not a beginningof le code. (That situation would indicate that the le being read is probably not really a spreadsheet, at least not one in .WKS format.) Exercise 5.12.3 Modify LOTUS.PL to return end of file if it encounters an unexpected end of le. (This will depend on having previously made similar modications to read_bytes, read_u16, etc.) Exercise 5.12.4 (term project)
Modify LOTUS.PL to decode the formulas stored in the spreadsheet. For a description of the data format see Walden (1986).
Authors manuscript
Sec. 5.12.
151
File LOTUS.PL Reads a Lotus .WKS spreadsheet, field by field. :- ensure_loaded'readbyte.pl'. :- ensure_loaded'readi16.pl'. :- ensure_loaded'readf64.pl'. or use reconsult if necessary or use reconsult if necessary or use reconsult if necessary
Insert definition of atom_codes here if not built in atom_codesAtom,Codes :- nameAtom,Codes. read_significant_field-Field Reads a field from the spreadsheet like read_field below, but skips non--data fields. read_significant_fieldResult :repeat, read_fieldR, + R == non_data_field, !, Result = R. like read_field, skips non-data
read_field-Field Reads a field from the spreadsheet, returning one of the following: beginning_of_file -- Lotus beginning-of-file code end_of_file -- Lotus end-of-file code cellCol,Row,integer,Value -- An integer. Col and Row numbered from 0. cellCol,Row,float,Value -- A floating-point number. cellCol,Row,formula,Value -- The numerical value of a formula. cellCol,Row,text,Value -- A text field as a Prolog atom. non_data_field -- Anything else print formats, etc.. read_fieldResult :read_u16Opcode, read_field_auxOpcode,Result. read_field_aux0,beginning_of_file :!, read_u16Length, skip_bytesLength. read_field_aux1,end_of_file :!. no need to read the trivial bytes that follow
Figure 5.9
Authors manuscript
152
Chap. 5
read_field_aux13,cellCol,Row,integer,Value :!, skip_bytes3, length and format information read_u16Col, read_u16Row, read_i16Value. read_field_aux14,cellCol,Row,float,Value :!, skip_bytes3, length and format information read_u16Col, read_u16Row, read_f64Value. read_field_aux15,cellCol,Row,text,Value :!, read_u16Length, skip_bytes1, format code read_u16Col, read_u16Row, Rest is Length - 7, skip_bytes1, alignment code at beg. of string read_bytesRest,String, atom_codesValue,String, skip_bytes1. final zero byte at end of string read_field_aux16,cellCol,Row,formula,Value :!, skip_bytes3, length and format information read_u16Col, read_u16Row, read_f64Value, numeric value of formula read_u16LengthOfRest, skip_bytesLengthOfRest. don't try to decode formula itself read_field_aux_,non_data_field :read_u16Length, skip_bytesLength. Demonstration wksdemo :- see'sample.wks', repeat, read_significant_fieldF, writeqF, nl, F == end_of_file, !, seen.
Authors manuscript
Sec. 5.13.
153
Authors manuscript
154
fathermichael,cathy. fathercharles_gordon,michael. fatherjim,melody.
Chap. 5
We can ask Prolog to display the names of all the fathers by issuing a query such as:
?- fatherX,_, writeX, nl, fail.
That is: Find an X for which fatherX,_ succeeds, print it, and backtrack to nd another one. But what if, instead of displaying the names, we want to process them further as a list? We are in a dilemma. In order to get all the names, the program must backtrack. But in order to construct the list, it must use recursion, passing the partially constructed list as an argument from one iteration to the next which a backtracking program cannot do. One possibility would be to use assert and retract to implement roughly the following algorithm: 1. Backtrack through all solutions of fatherX,_, storing each value of X in a separate fact in the knowledge base; 2. After all solutions have been tried, execute a recursive loop that retracts all the stored clauses and gathers the information into a list. Fortunately, we dont have to go through all this. The builtin predicate
findall will gather the solutions to a query into a list without needing to perform
will instantiate List to the list of all instantiations of Variable that correspond to solutions of Goal. You can then process this list any way you want to. The rst argument of findall need not be a variable; it can be any term with variables in it. The third argument will then be a list of instantiations of the rst argument, each one corresponding to a solution of the goal. For example:
?- findallParent+Child,fatherParent,Child,L. L = michael+cathy,charles_gordon+michael,jim+melody
Here the plus sign (+) is, of course, simply a functor written between its arguments.
Exercise 5.14.1 Given the knowledge base
5 If
Authors manuscript
Sec. 5.15.
155
employee'John Finnegan','secretary',9500.00. employee'Robert Marks','administrative assistant',12000.00. employee'Bill Knudsen','clerk-typist',8250.00. employee'Mary Jones','section manager',32000.00. employee'Alice Brewster','c.e.o.',1250000.00.
what query (using findall) gives a list of all the employees names (and nothing else)? Exercise 5.14.2 Using the same knowledge base as in the previous exercise, dene a procedure average_salaryX which will unify X with the average salary. To do this, rst use findall to collect all the salaries into a list; then work through the list recursively, counting and summing the elements. Exercise 5.14.3 Using the same knowledge base as in Exercise 5.14.1, show how to use findall to construct the following list:
'John Finnegan',9500.00 , 'Robert Marks',12000.00 , 'Bill Knudsen',8250.00 , 'Mary Jones',32000.00 , 'Alice Brewster',1250000.00
Exercise 5.14.4 Do the same thing as in the previous exercise, but this time collect into the list only the employees whose salaries are above $10,000. (Hint: Use a compound goal as the second argument of findall.)
means Find everyone who is the parent of anybody Y need not have the same value for each parent. But
?- bagofX,parentX,Y,L.
Authors manuscript
156
Chap. 5
means Find all the values of X that go with some particular value of Y. Other values of Y will produce alternative solutions to bagof, not additional entries in the same list. Heres an example:
parentmichael,cathy. parentmelody,cathy. parentgreg,stephanie. parentcrystal,stephanie. ?- findallX,parentX,Y,L. X = _0001, Y = _0002, L= michael,melody,greg,crystal ?- bagofX,parentX,Y,L. X = _0001, Y = cathy, L = michael,melody ; X = _0001, Y = stephanie, L = greg,crystal ?- setofX,parentX,Y,L. X = _0001, Y = cathy, L = melody,michael ; X = _0001, Y = stephanie, L = crystal,greg
Of course setof is just like bagof except that it sorts the list and removes duplicates (if any). In the terminology of formal logic, findall treats Y as EXISTENTIALLY QUANTIFIED that is, it looks for solutions that need not all involve the same value of Y while setof and bagof treat Y as something for which you want to nd a specic value. But theres another option. You can do this:
?- bagofX,Y^parentX,Y,L. X = _0001, Y = _0002, L = michael,melody,greg,crystal
Prexing Y^ to the goal indicates that Y is to be treated as existentially quantied within it. More generally, if the second argument of setof or bagof is not Goal but rather Term^Goal, then all the variables that occur in Term are treated as existentially quantied in Goal.6 As an extreme case, if you write bagofV,Goal^Goal,L then all the variables in Goal will be existentially quantied and bagof will work exactly like findall. Finally, a word of warning: findall, bagof, and setof are relatively costly, timeconsuming operations. Dont use them unless you actually need a list of solutions; rst, think about whether your problem could be solved by backtracking through alternative solutions in the conventional manner.
Exercise 5.15.1 Go back to FAMILY.PL (Chapter 1), add a denition of ancestor, and show how to use setof or bagof (not findall)to construct:
some versions of Cogent Prolog and LPA Prolog, Term can only be a variable; all the other Prologs that we have tried allow it to be any term containing variables.
6 In
Authors manuscript
Sec. 5.16.
157
1. A list of all the ancestors of Cathy, in the order in which they are found; 2. A list of all the ancestors of Cathy, in alphabetical order;
3. A list of terms of the form ancestorX,Y where X and Y are instantiated to an ancestor and a descendant, and have all possible values. That is: ancestorcharles,charles_gordon,ancestor (not necessarily in that order). 4. A list of people who are ancestors (without specifying who they are ancestors of).
Which child is the youngest? Well try the rst and third strategies, leaving the second one as an exercise. Its easy to make setof give us the age of the youngest child. Consider these queries:
?- setofA,N^ageN,A,L. L = 3,4,7,8 ?- setofA,N^ageN,A, Youngest|_ . Youngest = 3
The rst query retrieves a sorted list of the childrens ages; the second query retrieves only the rst element of that list. So far so good. Getting the name of the youngest child involves some subtlety. Recall that the rst argument of setof, bagof, or findall need not be a variable; it can be a term containing variables. In particular, we can get a list of childrens names together with their ages in any of the following ways (among others):
Authors manuscript
158
Chap. 5
?- setof A,N ,N^ageN,A,L. L = 3,aaron , 4,sharon , 4,danielle , 7,sharon , 8,cathy ?- setoffA,N,N^ageN,A,L. L = f3,aaron,f4,sharon,f4,danielle,f7,sharon,f8,cathy ?- setofA+N,N^ageN,A,L. L = 3+aaron,4+sharon,4+danielle,7+sharon,8+cathy
In the rst query we ask for each solution to be expressed as a 2element list containing A and N; in the second query we ask for a term fA,N; and in the third query we ask for a term A+N. Notice why 3+aaron comes out as the rst element. When setof compares two terms to decide which one should come rst, it looks rst at the principal functor, and then at the arguments beginning with the rst. Numbers are compared by numeric value, and atoms are compared by alphabetical order. Accordingly, since all the terms in the list have the same functor (+), and all have numbers as the rst argument, the one with the smallest number comes out rst. But you dont have to use setof. Here is a purely logical query that solves the same problem:
?- ageYoungest,Age1, Youngest = aaron + age_,Age2, Age2 Age1.
Think for a moment about how the backtracking works. Prolog will try every possible solution for ageYoungest,Age1 until it gets one for which it cant nd a younger child. That means that every entry in the knowledge base is compared with all of the others; if there are N children, there are N 2 comparisons. Is this inefcient? Maybe; setof can perform its sorting with only N log2 N comparisons. But if N is fairly small, its probably faster to do more comparisons and avoid constructing lists, because list construction is a fairly slow and expensive operation.
Exercise 5.16.1 Show how to use setof to nd out which childs name comes rst in alphabetical order. Exercise 5.16.2 How would you nd out which childs name comes last in alphabetical order? Exercise 5.16.3 What would be the result of this query? Predict the result before trying it.
?- setofN+A,N^ageN,A,L.
Exercise 5.16.4 Dene a predicate find_sixX that will instantiate X to the name of the child whose age is closest to 6. (Hint: Let the second argument of setof be a compound goal such as ageN,A, Diff is abs6-A, then sort on Diff.)
Authors manuscript
Sec. 5.17.
159
Exercise 5.16.5 Rewrite find_six so that instead of using setof, it uses a puerly logical query. Call it logical_find_six. Exercise 5.16.6 (small project)
On your Prolog system, compare the time taken to nd the youngest child by using
setof to the time taken by using a purely logical query. Try larger knowledge bases. How large does the knowledge base need to be in order for setof to be the faster
technique?
Is Fido a dog?
But how can we ask a question such as Are dogs animals? There are two things this question might mean: (1) Is there a rule or set of rules by means of which all dogs can be proven to be animals? (2) Regardless of what the rules say, is it the case that all of the dogs actually listed in the knowledge base are in fact animals? We can call (1) and (2) the INTENSIONAL and EXTENSIONAL interpretations, respectively, of the question Are dogs animals? Of these, (1) is primarily a question about the contents of the rule set, whereas (2) asks Prolog to make a generalization about a set of known individuals. Of course, if (1) is true, (2) will always be true also, though the converse is not the case. We will pursue (2) here. Intuitively, we want to say that All dogs are animals is true if (a) there is at least one dog in the knowledge base, and (b) there is no dog in the knowledge base which is not an animal. We insist that there must be at least one dog in the knowledge base so that, for instance, Snails are monkeys does not come out true merely because there are no snails in the knowledge base. We want to dene a predicate for_allGoalA,GoalB that succeeds if all of the instantiations that make GoalA true also make GoalB true. For the results to be meaningful, GoalA and GoalB must of course share at least one variable. We could then ask Are dogs animals? with the query:
?- for_alldogX,animalX.
Authors manuscript
160
Chap. 5
for_allGoalA,GoalB Succeeds if all solutions for GoalA also satisfy GoalB, and there is at least one solution for both goals. for_allGoalA,GoalB :+ callGoalA, + callGoalB, callGoalA, !.
1 2 3
The nested negations in line 1 may be confusing. Line 1 fails if the compound goal
callGoalA, + callGoalB
succeeds, and vice versa. This compound goal, in turn, succeeds if there is a way to make GoalA succeed (instantiating some variables shared with GoalB in the process) such that GoalB then fails. So if we nd a dog that is not an animal, the compound goal succeeds and line 1 fails. Otherwise, line 1 succeeds. If line 1 succeeds, line 2 then checks that there was indeed at least one dog in the knowledge base. We cannot reverse the order of lines 1 and 2 because line 2 instantiates some variables that must be uninstantiated in line 1. The cut in line 3 ensures that we do not generate spurious alternatives by making line 2 succeed in different ways.
Exercise 5.17.1 Given the knowledge base
dogfido. dogrover. dogX :- bulldogX. bulldogbucephalus. animalX :- dogX. animalfelix.
is it true that all dogs are animals? That all animals are dogs? That all bulldogs are animals? That all dogs are bulldogs? Give the query (using for_all) that you would use to make each of these tests.
Authors manuscript
Sec. 5.18.
Operator Denitions
COMMONLY PREDEFINED PROLOG OPERATORS.
161
TABLE 5.1
Priority 1200 1200 1100 1050 1000 900 700 500 400 200 200
Operators
:- -:- ?; , + (or, in some Prologs, not) = = == == @ @= @ @ = is =:= = = + * mod ^ -
= =..
To create an operator, you must specify the operators POSITION, PRECEDENCE, and ASSOCIATIVITY. Lets deal with position rst. An INFIX operator comes between its two arguments, like + in 2+3; a PREFIX operator comes before its one argument, without parentheses, like + in + dogfelix; and a POSTFIX operator comes after its one argument. Precedence determines how expressions are interpreted when there no parentheses to show how to group them. For example, 2+3*4 is interpreted as 2+3*4, not 2+3*4, because + has higher precedence than *. The operator with lower precedence applies to a smaller part of the expression. Precedences are expressed as numbers between 1 and 1200 (between 1 and 256 in some older implementations). Table 6.1 shows the usual set of builtin operators. Associativity determines what happens when several operators of the same precedence occur in succession without parentheses: should 2+3+4 be interpreted as 2+3+4 (left associative), or as 2+3+4 (right associative), or simply disallowed? Position and associativity are specied by the symbols fx, fy, xf, yf, xfx, xfy, and yfx (Table 6.2). Here f stands for the position of the operator itself; x stands for an argument that does not allow associativity; and y stands for an associative argument. Thus, fx designates a prex operator with one argument and no associativity; yfx designates an inx operator that is associative on the left but not the right. A complete operator denition looks like this:
?- op100,xfx,is_father_of.
This query tells Prolog to allow the functor is_father_of to be written between its arguments, with a precedence of 100 and no associativity. After executing this query, Prolog will accept facts and rules such as:
michael is_father_of cathy. X is_father_of Y :- maleX, parentX,Y.
Authors manuscript
Chap. 5
Meaning Prex, not associative Prex, right-associative (like +) Postx, not associative Postx, left-associative Inx, not associative (like =) Inx, right-associative (like the comma in compound goals) Inx, left-associative (like +)
Notice that the op declaration is a query, not a fact. If you include it as a line in your program, it must begin with :-, like this:
:- op100,xfx,is_father_of.
Then it will affect the reading of all subsequent facts, rules, and embedded queries as the Prolog system consults your program. In fact, it will affect all input and output performed by read, write, consult, and any other procedures that recognize Prolog syntax. In virtually all Prologs, the op declaration will stay in effect until the end of your Prolog session, although the ISO standard does not require this. Naturally, you can also execute an op declaration as a subgoal within one of your procedures, if you wish. Finally, note that operators composed entirely of the special characters
$ & * + - . : = ? @ ^ ~
have some special properties. First, unlike most other atoms that contain nonalphanumeric characters, they need not be written in quotes. Second, they need not be separated from their arguments by spaces (unless, of course, their arguments also consist of those special characters). For example, X =Y is equivalent to X = Y, because and = are special characters, but Xmod3 is not equivalent to X mod 3. Like any other functor, an operator can have different meanings in different contexts. For example, denotes division in arithmetic expressions, but in the argument of abolish elsewhere it joins the functor and arity of a predicate that is being referred to (e.g., abolishfff 2); and you could use it for still other purposes anywhere you wish.
Exercise 5.18.1 Construct an appropriate op declaration so that
likeskermit,piggy.
can be written
kermit likes piggy.
Authors manuscript
Sec. 5.19.
163
Demonstrate that your op declaration affects the behavior of write. Exercise 5.18.2 In op declarations, why isnt yfy a possible specier of precedence and position? Exercise 5.18.3 By redening builtin operators, make Prolog arithmetic expressions obey the rightto-left rule of the programming language APL. That is, change the syntax of +, -, *, and so that all of them have the same precedence and expressions are evaluated from right to left; for example, 2+3*4+5 6-7 should be interpreted as 2+3*4+5 6-7. To verify that your redenitions have had the desired effect, try the following queries: ?- X is 2*3+4. (should give 14, not 10) ?- X is 1.0 2.5-6.5. (should give -0.25, not -6.1)
clearly invokes call with only one argument. In ordinary Prolog, we have to use extra parentheses to get the same effect, like this:
?- call pa, qa, ra .
because without the extra parentheses, call would be taken as having three arguments. We can dene the ampersand to work this way even in ordinary Prolog. First, lets dene its syntax. We want & to be an inx operator with slightly lower precedence than the comma, so that fa&b,c will mean fa&b,c, not fa&b,c. Further, as we will see shortly, & should be rightassociative. In most Prologs, then, the appropriate operator denition is:
:- op950,xfy,&.
Next we need to tell Prolog how to solve a goal that contains ampersands. Obviously, GoalA & GoalB should succeed if GoalA succeeds and then GoalB succeeds with the same instantiations. That is:
GoalA & GoalB :callGoalA, callGoalB.
This is just an ordinary Prolog rule. It could equally well be written as:
'&'GoalA,GoalB :- callGoalA, callGoalB.
Authors manuscript
164
Chap. 5
Demonstration knowledge base parentmichael,cathy. parentmelody,cathy. parentcharles_gordon,michael. parenthazel,michael. parentjim,melody. parenteleanor,melody. grandparentX,Y :- parentZ,Y & parentX,Z. only_childX :- parentP,X & + parentP,Z & Z ==X.
Figure 5.10
Because & is right-associative, this rule can in fact handle an unlimited number of goals joined by ampersands. Suppose we issue the query:
?- writeone & writetwo & writethree.
This unies with the head of the rule dening &, with the instantiations:
GoalA = writeone GoalB = writetwo & writethree
The new goals are callGoalA, which is satised by writing one on the screen, and callGoalB, which invokes the rule for & recursively. File AMPERS.PL (Figure 5.10) illustrates the whole thing.
Authors manuscript
Sec. 5.20.
Exercise 5.19.1
Prolog in Prolog
165
Show how to dene the operators and and or to mean and and or respectively, so that you will be able to write clauses like this:
greenX :- plantX and photosyntheticX or frogX.
Demonstrate that your solution works correctly. Exercise 5.19.2 What happens if there is a cut in one of the subgoals joined by ampersands in AMPERS.PL? Why?
the query
?- clausefabc,Body.
To execute this goal we work through it in the same manner as with the ampersands above. We can dene interpret (which takes a goal as an argument and executes it) as shown in Figure 5.11. Then, to use interpret, we simply type, for example,
?- interpretgrandparentX,Y. instead of ?- grandparentX,Y.
This algorithm is from Clocksin and Mellish (1984:177). The cuts are green: they save steps but do not affect the logic of the program. Notice that call is not used; every successful goal ends up as an invocation of interprettrue.
7 Some
Authors manuscript
166
Chap. 5
File INTERP.PL Meta-interpreter for Prolog interpret+Goal Executes Goal. interprettrue :- !. interpretGoalA,GoalB :- !, interpretGoalA, interpretGoalB. interpretGoal :clauseGoal,Body, interpretBody.
:- dynamicparent 2. parentmichael,cathy. parentmelody,cathy. parentcharles_gordon,michael. parenthazel,michael. parentjim,melody. parenteleanor,melody. :- dynamicgrandparent 2. grandparentX,Y :- parentZ,Y, parentX,Z. test :- interpretgrandparentA,B, write A,B , nl, fail. prints out all solutions
Figure 5.11
Authors manuscript
Sec. 5.21.
167
This is a METAINTERPRETER or METACIRCULAR INTERPRETER for Prolog. It is, of course, slower than the original Prolog interpreter under which it runs, but the speed difference is not dramatic because the original interpreter is still doing nearly all the work. More importantly, it does not recognize builtin predicates because there are no clauses for them, and it offers no straightforward way to perform cuts. These features can, of course, be added by writing a more complex metainterpreter. Metainterpreters are useful because they can be modied to interpret languages other than the Prolog in which they are written. With minor changes, we could make our metainterpreter use ampersands rather than commas to form compound goals, or even change its inference strategy entirely, for example by making it try to solve each goal using facts before trying any rules. Even when interpreting straight Prolog, a metainterpreter can keep records of the steps of the computation, assist with debugging, and even check for loops.
Exercise 5.20.1 On your Prolog system, does clause have access to all clauses, or only the ones that are declared dynamic? Experiment to nd out. Exercise 5.20.2 Get interpret working on your machine. Demonstrate that it correctly performs the computations used in FAMILY.PL (Chapter 1). Exercise 5.20.3 Add clauses to interpret to handle negation ( +) and equality (=). Using these extensions, add the denition of sibling to FAMILY.PL and show that interpret processes it correctly. Exercise 5.20.4 Extend interpret to handle ordinary builtin predicates (e.g., write) by trying to execute each subgoal via call if no clauses for it can be found. Show that queries such as
?- interpret fatherX,cathy, writeX .
Authors manuscript
168
Chap. 5
we will get loops, because each rule will call the other. A different inference strategy is needed. First we need to record the fact that canineX and dogX stand in a biconditional relation. We could use a predicate called bicond and put the following fact in the knowledge base:
bicondcanineX,dogX.
But a more elegant approach is possible. Instead of bicond, lets call the predicate -:- (an operator similar to :-, but symmetrical). For this we need the operator denition:
:- op950,xfx,'-:-'.
We will call this a BICONDITIONAL RULE. Neither side of it can be a compound goal. File BICOND.PL (Figure 5.12) denes a predicate prove which is like call except that it can also use biconditional rules. Its strategy is as follows: rst see if the goal can be satised with call. If not, see if it matches one side of a biconditional rule, and if so, call the other side of that rule. To see that we get the correct results, consider the following knowledge base and queries:
dogfido. caninerolf. dogX -:- canineX. ?- dogX. X = fido ?- canineX. X = rolf ?- provedogX. X = fido ; X = rolf ?- provecanineX. X = rolf ; X = fido
Queries with prove recognize biconditionals, while ordinary queries do not. Biconditionals do not cause loops because prove does not call itself. Unfortunately, prove has its limitations. Because it is not recursive, it does not recognize that biconditionality is transitive. The knowledge base
fX -:- gX. gX -:- hX.
Authors manuscript
Sec. 5.21.
169
File BICOND.PL Inference engine for biconditionals in Prolog The -:- operator joins the two sides of a biconditional rule. :- op950,xfx,'-:-'.
Inference engine for biconditionals proveGoal :callGoal. GoalA -:- GoalB, callGoalB. GoalA -:- GoalB, callGoalA.
proveGoalA :-
proveGoalB :-
Sample knowledge base dogfido. caninerolf. dogX -:- canineX. test :- provedogX, writeX, nl, fail. prints all solutions
Figure 5.12
Authors manuscript
170
Chap. 5
does not enable the computer to infer ffido from hfido. If we make prove recursive in the straightforward way so that it invokes itself instead of invoking call we get transitivity, but we also reintroduce loops. A more sophisticated version of prove might keep a record of the biconditional rules that it has used so that it can chain rules together without using the same rule more than once.
Exercise 5.21.1 Implement an inference engine that can handle symmetric 2place predicates (those whose arguments can be interchanged) without looping. Put the functor symmetric in front of each fact that is to have the symmetric property, and let symmetric be a prex operator. For example:
symmetric marriedjohn,mary. symmetric marriedmelody,michael. ?- provemarriedmary,john. yes ?- provemarriedjohn,mary. yes ?- provemarriedmichael,Who. Who = melody ?- provemarriedabc,xyz. no
and the user replies by typing a query. Some Prologs also let the user type rules and facts which are added to the knowledge base. File TOPLEVEL.PL (Figure 5.13) denes a top level whose dialogues with the user look like this:
Type a query: fatherX,cathy. Solution found: fathermichael,cathy Look for another? Y N: n Type a query: No more solutions fatherjoe,cathy.
Type a query: parentX,Y. Solution found: parentmichael,cathy Look for another? Y N: y
Authors manuscript
Sec. 5.22.
171
File TOPLEVEL.PL A customized user interface for Prolog top_level Starts the user interface. top_level :repeat, nl, write'Type a query: readGoal, find_solutionsGoal, fail.
',
find_solutions+Goal Satisfies Goal, displays it with instantiations, and optionally backtracks to find another solution. find_solutionsGoal :callGoal, write'Solution found: ', writeGoal, nl, write'Look for another? Y N: ', getChar, nl, Char = 78 ; Char = 110, N or n !. find_solutions_ :write'No more solutions', nl.
Figure 5.13
Authors manuscript
172
Chap. 5
This is just like the usual Prolog top level except that the messages are much more explicit, and answers are reported by displaying the query with the variable values lled in. All Prolog queries are acceptable not only queries about the knowledge base, but also calls to built-in predicates such as consult. The code dening this top level is only 18 lines long. The procedure top_level is an endless repeatfail loop that accepts queries and passes them to find_solutions. (Note by the way that the name top_level has no special signicance. This procedure becomes the top level of the Prolog environment when you start it by typing ?- top_level.) The procedure find_solutions has two clauses. The rst clause nds a solution, prints it, and asks the user whether to look for others. If the user types N or n (for no), find_solutions executes a cut, and control returns to top_level. Otherwise, find_solutions backtracks. If no further solutions can be found and the cut has not been executed, control passes to the second clause and the message No (more) solutions is displayed. How do you get out of top_level? Simple: type halt. to leave Prolog, just as if you were using the ordinary top level. A customized top level can make Prolog much more userfriendly. We have found it useful to introduce beginners to Prolog with a menu-driven user interface that lets them display the knowledge base, add or remove clauses, and execute queries. In this way the use of a le editor is avoided. Moreover, a customized top level can be combined with an enhanced inference engine to turn Prolog into a powerful knowledge engineering system.
Exercise 5.22.1 Get top_level working on your computer. Then modify it so that it uses setof to obtain and display all the solutions at once, rather than asking the user whether to backtrack. Call the modied version top_level_2.
Authors manuscript
Chapter 7
Advanced Techniques
,@ , @
HHH
i k
,@ , @
The crucial concept here is that the structure expresses a relation between a functor and a series of arguments. Tree diagrams like this one are very handy for guring out whether two structures will unify, and, if so, what the result will be. Prolog clauses and goals are structures, too. The clause
a :- b, c, d.
173
Authors manuscript 693 ppid September 9, 1995 Prolog Programming in Depth
174
is really
a :- b, c, d.
Advanced Techniques
Chap. 7
or rather:
,@ , @, a ,@ , @, b ,@ , @
c
:-
Knowing this structure is crucial if you want to write a Prolog program that processes other Prolog programs. Notice that here, the comma is a functor, not just a notational device. Recall that there are three uses for commas in Prolog: As an inx operator that means and, for joining goals together (as in the clause a :- b, c, d above); As an argument separator in structures (as in fa,b,c); As an element separator in lists (as in a,b,c ). The rst kind of comma is a functor; the latter two kinds are not, and do not show up in tree diagrams.
Exercise 7.1.1 Draw tree diagrams of the following structures:
asdfa1,a2,a3,a4 gxy,z,w,qp,ref 2+3*4+5
Hint: Recall that + and * are functors that are written between their arguments. Exercise 7.1.2 Draw tree diagrams of the following two structures, and then of the structure formed by unifying them:
ab,X,cd,X,e aY,p,cd,Z,e
Exercise 7.1.3 Demonstrate all three uses of commas in a single Prolog clause. Label each of the commas to indicate its function. Exercise 7.1.4 (project)
Dene a predicate display_tree that will display any Prolog structure as a tree diagram. (How you display the tree will depend on what tools are available to you; you can use either graphics or typewritten characters.)
Authors manuscript
Sec. 7.2.
Lists as Structures
175
,@ , @. a ,@ , @. b ,@ , @
c
In functorargument notation, this structure would be written .a,.b,.c, , but we normally write a,b,c instead. The two are equivalent; they are two notations for the same thing. Note some further equivalences:
.a, .a,.b,.c, .a,b .a,.b,.c,d = = = = a a,b,c a|b a,b,c|d
Every non-empty list has the dot (.) as its principal functor. Note however that the empty list is not a structure and does not contain the dot functor. Now look again at how lists branch. Each dot functor has two arguments: its rst argument is one element, and its second argument is another list. That is: Every list consists of an element plus another list or .
This is the concept that makes a list what it is. If the whole thing does not end in it is called an IMPROPER LIST (like a dotted pair in Lisp). The big advantage of lists over multiargument structures is that you can process a list without knowing how many elements it has. Any non-empty list will unify with .X,Y (more commonly written X|Y ), regardless of the number of elements. You can process the element X and then recursively pick apart the list Y the same way you did the original list. Listlike structures can be constructed with any twoargument functor; for example, fa,fb,fc, is a listlike structure whose principal functor happens to be f rather than the dot. Inx operators are particularly useful for this purpose. For example, if you are accumulating a sequence of numbers that will eventually be summed, it may be desirable to link them together with the functor + rather than the usual list structure, thus:
1+2 1+2+3 1+2+3+4
or simply or simply
1+2+3 1+2+3+4
When youve constructed the whole sequence, you can nd the sum by putting the sequence on the righthand side of is.
Authors manuscript
176
Advanced Techniques
Chap. 7
Notice that because + is leftassociative, this particular list is a tree that branches on the left rather than on the right. Nonetheless, you can pick off one element at a time, just as you would with a list: 2+3+4+5 unies with X+Y to give X=2+3+4 and Y=5.
Exercise 7.2.1 Draw tree diagrams of each of the following terms:
a,b,c,d a,b,c|d fa,fb,fc,d a+b+c+d
Exercise 7.2.2 Demonstrate, using the computer, that a,b = .a,.b, Exercise 7.2.3 What is the result of the following query? Explain.
?a,b,c =.. What. .
Exercise 7.2.4 What is the most concise way of writing x,y| Exercise 7.2.5 Write ..a,.b, ,.c, in ordinary list notation (with square brackets and commas) and draw a tree diagram of it. Exercise 7.2.6 Dene a predicate improper_list 1 that will succeed if its argument is an improper list, and fail otherwise. ?
Authors manuscript
Sec. 7.4.
177
This is of course an oversimplied example; in real life theres not much point to changing every a to b. But you could use a procedure like this as the basis of a Prolog macro system, to implement extensions to the Prolog language.
Exercise 7.3.1 Modify rewrite_term so that it changes as to bs only when the as are atoms, not functors. (Simple.) Exercise 7.3.2 Dene a predicate count_a 2 that will simply search for occurrences of a (as atom or functor) in any term, and report how many it found, thus:
?- count_afa, b,aX,d ,What. What = 2
Note that you dont have to make a copy of the term just keep a count of as. Exercise 7.3.3 (small project)
Develop an alternative version of rewrite_term that uses functor 3 and arg 3 instead of =... On your system, is it faster or slower than the original?
actually contains only one copy of asdfasdf. Lists are a special case. If the dot functor worked as we described it in the previous section, the internal representation of the list a,b,c would be as shown in Figure 7.3(a). But in fact, most Prolog implementations treat lists specially and
Authors manuscript
178
Advanced Techniques
Chap. 7
rewriteX,Y Tells rewrite_term what to rewrite. rewritea,b :- !. rewriteX,X. change all a to b leave everything else alone
rewrite_term+Term1,-Term2 Copies Term1 changing every atom or functor 'a' to 'b' using rewrite 2 above. rewrite_termX,X :varX, don't alter uninstantiated variables !. rewrite_termX,Y :atomicX, 'atomic' means atom or number !, rewriteX,Y. rewrite_termX,Y :X =.. XList, rewrite_auxXList,YList, Y =.. YList. rewrite_aux , .
Figure 7.1
Authors manuscript
Sec. 7.4.
179
f alpha
Symbol table
bbb c
Figure 7.2
omit the pointers to the dot functor, so that they actually use the more compact representation in Figure 7.3(b). The unier knows about the dot functor and regenerates it whenever needed, so that, as far as the programmer is concerned, it always seems to be there, except that it doesnt take up any space. Each cell contains a TAG (not shown) that indicates whether it is a structure or a list element; tags keep Figure 7.3(b) from being interpreted as abcd, . Either way, the distinctive thing about lists is that the cons cells are arranged in a linear chain. Only the rst element of the list can be accessed in a single step. All subsequent elements are found by following a chain of pointers, a process known as CDRing down the list (again, CDR, pronounced coulder, is the name of a Lisp function). It thus takes ve times as long to reach the fth element of the list as to reach the rst element. This leads to a programming maxim: Work on lists at the beginning, not at the end. It is also a good idea to avoid consing, i.e., avoid creating new lists and structures unnecessarily. Allocating new cons cells requires time as well as memory. Rather than transforming a,b,c,d into a,b,c , unify a,b,c,d with X,Y,Z|_ , if that will do the same job, or store the list elements in the opposite order. As a Prolog program runs, it allocates many cons cells, which are obtained from an unused memory area known as the HEAP or GLOBAL STACK. Just as frequently, it releases cons cells that are no longer needed. Periodically, it must perform GARBAGE COLLECTION and gather up the unused cons cells, returning them to the heap for future use. In Prolog, unlike Lisp, some garbage collection can be performed upon exit from each procedure, since its variable instantiations no longer exist. Some
Authors manuscript
180
Advanced Techniques
Chap. 7
s s s
b c
b c
Figure 7.3
Authors manuscript
Sec. 7.5.
181
implementations take advantage of this fact, and some wait until the heap is empty before performing any garbage collection. In many Prologs, the builtin predicate gc performs garbage collection when you call it.
Exercise 7.4.1 Sketch the internal representations of each of the following structures:
ab,c,d abc,de asdfasdfasdfasdf,asdfasdf
Exercise 7.4.2 Which takes up more space, a,b,c or a+b+c+ Exercise 7.4.3 Assuming that each pointer occupies 4 bytes, how much space is saved by representing a,b,c,d,e,f the compact way instead of the original way? Exercise 7.4.4 Think about the circumstances under which atoms get added to the symbol table. Clearly, atoms that occur in the program itself are placed in the symbol table when the program is initially consulted or compiled. But other predicates, such as read, can introduce new atoms at run time. What other predicates can do this? ? Or are they the same size? Explain.
. . .
These table entries can be looked up very quickly. Of course it isnt easy to add or change entries in this kind of table, though its perfectly possible. Another way to simulate an array is to use a functor with many arguments, and access the arguments individually using arg. For example, given the structure
primes2,3,5,7,9,11,13,17,19,23,29
Authors manuscript
182
Advanced Techniques
Chap. 7
In effect, you are using the argument positions of a cons cell as if they were an array of pointers. Remember that a twodimensional array is equivalent to a one dimensional array of onedimensional arrays, and so on for higher dimensions. Finally, notice that even if Prolog did have arrays, you still wouldnt be able to swap or rearrange the elements of an array in place (without making a copy of the array), because Prolog lacks a destructive assignment statement (that is, Prolog doesnt let you change the value of a variable that is already instantiated). Thus, some arraybased algorithms cannot be implemented directly in Prolog, although they can of course be simulated using extra copying operations or different data structures.
Exercise 7.5.1 Suggest at least two good ways to represent a 3 3 3 array of numbers in Prolog. Exercise 7.5.2 (project)
Implement matrix multiplication in Prolog. Finding the best way to do this will require considerable thought.
is a difference list with the members a, b, and c. Crucially, terms that occur in both the head and the tail do not count; the above list will continue to represent the sequence ha; b; ci no matter what value Tail is instantiated to. Thats why its called a difference list: its contents are the difference between the whole list and the tail. Before we put difference lists to use, lets develop a better notation for them. Any twoargument functor can be substituted for difflist 2 above. An inx operator would be particularly convenient. In fact, we can use the inx operator (the same operator that denotes division in arithmetic expressions).1 The practice of overloading an operator that is, giving it multiple meanings in different contexts is perfectly legitimate. In fact, already has two meanings: it denotes division in arithmetic expressions, and it joins a functor to an arity in queries such as abolishparent 2. We will thus write the difference list ha; b; ci as:
1 In the Prolog literature, it is common to dene as an inx operator for this purpose; using saves us an op declaration. Pereira and Shieber (1987), thinking along similar lines, use the minus sign (-).
Authors manuscript
Sec. 7.7.
Quicksort
Tail
183
a,b,c|Tail
Difference lists can be concatenated in constant time, regardless of their length. Recall that append (Chapter 3) has to CDR all the way down the rst list before it can join it to the second. This is not the case with append_dl, which appends difference lists in a single step with just one clause:
append_dlX Y, Y Z, X Z.
It may take some thought to see how this works. To begin with, the tail of the rst list and the head of the second list must be uniable. This is usually no problem because the tail of the rst list is uninstantiated. Consider the query:
?- append_dl a,b|A A, c,d|C C, Result.
The very last step is crucial by instantiating A, the heretofore uninstantiated tail of the rst list, we add elements to the list itself. Notice, by the way, that although the result has an uninstantiated tail, the rst list no longer does. Accordingly, you cannot use the same list as the rst argument of append_dl more than once in the same clause. Difference lists are powerful but counterintuitive. They appear most often in procedures that were rst implemented with conventional lists and then carefully rewritten for efciency. Sterling and Shapiro (1994) use difference lists extensively and discuss them at length.
Exercise 7.6.1 Dene a predicate app3_dl that concatenates three difference lists:
?- app3_dl a,b|X X, c,d|Y What = a,b,c,d,e,f|Z Z Y, e,f|Z Z,What.
7.7. QUICKSORT
It is often necessary to put the elements of a list into numeric or alphabetical order. Most programmers are familiar with sorting algorithms that swap elements of arrays in place something that is impossible in Prolog. The most efcient way to sort a list is usually to call a builtin machinelanguage routine that can perform an array-based sort on Prolog list elements, without doing any consing. Several Prologs provide a builtin predicate, sort 2, that does this. One sorting algorithm that lends itself well to implementation in Prolog is C. A. R. Hoares famous Quicksort (Hoare 1962), which was the rst recursive algorithm
Authors manuscript
184
Advanced Techniques
Chap. 7
to make an impact on everyday business data processing. The idea behind Quicksort is this: 1. Pick an arbitrary element of the list (e.g., the rst). Call it Pivot. 2. Divide the remaining elements into two lists, those that should come before Pivot and those that should come after it. Call these Before and After respectively. 3. Recursively sort Before, giving SortedBefore, and After, giving SortedAfter. 4. Concatenate SortedBefore + Pivot + SortedAfter to get the result. This algorithm has no simple non-recursive counterpart. It takes time proportional to N log2 N if the elements are initially in random order, or proportional to N 2 if they are initially in an order that prevents the partitioning step (Step 2) from working effectively. File QSORT.PL (Figure 7.4) gives several Prolog implementations of Quicksort: a straightforward implementation that uses append, an implementation with difference lists, and an implementation with what has been called stacks. The original Quicksort uses append, which is excessively slow in some implementations; the latter two Quicksorts get by without append. The published literature on Quicksort is immense; for a thorough treatment, see Knuth (1973:113-123). Many tricks have been developed to keep Quicksort from becoming too inefcient when the elements are already almost in order; a simple one is to use the median of three elements as the pivot. Youll notice that Quicksort uses the comparison operator @ to compare terms for alphabetical order. All our sorting algorithms will do this. Of course, if you are only sorting numbers, you can use to compare terms numerically, and if you are sorting specialized data structures of some kind, you may need to write your own comparison predicate.
Exercise 7.7.1 Look closely at dlqsort and iqsort. Are these the same algorithm? Explain, in detail, how they correspond and what the differences are. What effect would you expect the differences to have on performance? Exercise 7.7.2 Some textbooks say that Quicksort takes time proportional to N log10 N or N ln N rather than N log2 N . Are they right? Explain. Exercise 7.7.3 Modify Quicksort so that it removes duplicates in the list being sorted. That is, after sorting, a,b,r,a,c,a,d,a,b,r,a should come out as a,b,c,d,r , not as a,a,a,a,a,b,b,c,d,r,r .
Authors manuscript
Sec. 7.7.
Quicksort
185
File QSORT.PL Several versions of Quicksort For maximum portability, this code includes some cuts that are unnecessary in implementations that have first-argument indexing. partition+List,+Pivot,-Before,-After Divides List into two lists, one containing elements that should come before Pivot, the other containing elements that should come after it. Used in all versions of Quicksort. partition X|Tail ,Pivot, X|Before ,After :X @ Pivot, !, partitionTail,Pivot,Before,After. partition X|Tail ,Pivot,Before, X|After :!, partitionTail,Pivot,Before,After. partition ,_, , . X precedes Pivot
X follows Pivot
Empty list
Original Quicksort algorithm Sterling and Shapiro, 1994:70; Clocksin and Mellish 1984:157 quicksort X|Tail ,Result :!, partitionTail,X,Before,After, quicksortBefore,SortedBefore, quicksortAfter,SortedAfter, appendSortedBefore, X|SortedAfter ,Result. quicksort , .
Delete next 2 lines if append 3 is built in append ,X,X. append X|Tail ,Y, X|Z :- appendTail,Y,Z.
Figure 7.4
Authors manuscript
186
Advanced Techniques
Chap. 7
Quicksort with difference lists Sterling and Shapiro 1994:289 dlqsortList,Result :- quicksort_dlList,Result quicksort_dl X|Tail ,Result ResultTail :!, partitionTail,X,Before,After, quicksort_dlBefore,Result X|Z , quicksort_dlAfter,Z ResultTail. quicksort_dl ,X X. .
Improved Quicksort using "stacks" separate variables for the tail of the list Kluzniak and Szpakowicz 1985; Clocksin and Mellish 1984:157 iqsortList,Result :- iqsort_auxList, ,Result.
iqsort_aux X|Tail ,Stack,Result :!, partitionTail,X,Before,After, iqsort_auxAfter,Stack,NewStack, iqsort_auxBefore, X|NewStack ,Result. iqsort_aux ,Stack,Stack.
Demonstration predicates test1 :- quicksort 7,0,6,5,4,9,4,6,3,3 ,What, writeWhat. test2 :- dlqsort 7,0,6,5,4,9,4,6,3,3 ,What, writeWhat. test3 :- iqsort 7,0,6,5,4,9,4,6,3,3 ,What, writeWhat.
End of QSORT.PL
Authors manuscript
Sec. 7.8.
187
Find out how to access the system clock on your computer and conduct some timings of your own. Replicate as much of Table 7.1 as you can.
Authors manuscript
188
Advanced Techniques
Chap. 7
TABLE 7.1
Algorithm
TIME AND MEMORY NEEDED TO SORT LISTS OF VARIOUS LENGTHS. Elements in random order Elements already almost sorted Elements initially almost backward
BuiltIn Sort Predicate 10 elements 100 elements 1000 elements Original Quicksort 10 elements 100 elements 1000 elements DifferenceList Quicksort 10 elements 100 elements 1000 elements Improved Quicksort 10 elements 100 elements 1000 elements Mergesort 10 elements 100 elements 1000 elements Treesort 10 elements 100 elements 1000 elements
0.95 ms (0.2 KB) 14.5 ms (3.6 KB) 142 ms (35 KB) 0.78 ms (0.4 KB) 20.3 ms (10 KB) 945 ms (387 KB) 0.83 ms (0.5 KB) 20.8 ms (10 KB) 950 ms (380 KB) 0.73 ms (0.3 KB) 19.8 ms (7.5 KB) 943 ms (357 KB) 1.53 ms (1.2 KB) 28.5 ms (20 KB) 406 ms (269 KB) 0.88 ms (0.6 KB) 24.0 ms (16 KB) 1132 ms (721 KB)
0.85 ms (0.2 KB) 8.34 ms (1.7 KB) 58.3 ms (9.5 KB) 1.18 ms (0.5 KB) 106 ms (40 KB) 10747 ms (3918 KB) 1.28 ms (0.7 KB) 109 ms (42 KB) 10617 ms (3934 KB) 1.20 ms (0.4 KB) 107 ms (39 KB) 10760 ms (3910 KB) 1.55 ms (1.1 KB) 29.0 ms (18 KB) 412 ms (242 KB) 1.43 ms (0.9 KB) 129 ms (80 KB) 12739 ms (7829 KB)
0.85 ms (0.2 KB) 1.33 ms (14 KB) 58.3 ms (11 KB) 1.15 ms (0.7 KB) 43.7 ms (29 KB) 1243 ms (635 KB) 1.15 ms (0.7 KB) 42.0 ms (19 KB) 1212 ms (499 KB) 1.15 ms (0.4 KB) 41.0 ms (17 KB) 1208 ms (476 KB) 1.58 ms (1.1 KB) 29.7 ms (22 KB) 440 ms (291 KB) 1.35 ms (0.9 KB) 48.2 ms (35 KB) 1445 ms (960 KB)
Authors manuscript
Sec. 7.9.
Mergesort
189
7.9. MERGESORT
A faster sorting algorithm is based on the operation of merging (combining) two lists that are already sorted. Clearly, a pair of lists such as
0,1,3,5,6,7,9 2,4,6,8
can be combined into one sorted list by picking off, one at a time, the rst element of one list or the other, depending on which should come rst: rst 0, then 1 (still from the rst list), then 2 (from the second list), then 3 (from the rst list), and so on. You never have to look past the rst element of either list. Heres a merging algorithm in Prolog:
merge+List1,+List2,-Result Combines two sorted lists into a sorted list. merge First1|Rest1 , First2|Rest2 , First1|Rest :First1 @ First2, !, mergeRest1, First2|Rest2 ,Rest. merge First1|Rest1 , First2|Rest2 , First2|Rest :+ First1 @ First2, !, merge First1|Rest1 ,Rest2,Rest. mergeX, merge ,X. ,X,X.
To use merge as the core of a sorting algorithm, well sort in the following way: Partition the original list into two smaller lists. Recursively sort the two lists. Finally, merge them back together. The recursion is not endless because the middle step can be omitted when a list that has fewer than two elements. Heres the implementation:
msort+List1,-List2 Sorts List1 giving List2 using mergesort. msort First,Second|Rest ,Result : list has at least 2 elements !, partition First,Second|Rest ,L1,L2, msortL1,SL1, msortL2,SL2,
Authors manuscript
190
mergeSL1,SL2,Result. msortList,List.
Advanced Techniques
Chap. 7
Finally, we have to tackle the job of partitioning a list into two. One simple way to do this is to put alternate elements into the two partitions, so that 0,1,2,3,4,5 gets split into 0,2,4 and 1,3,5 :
partition+List,-List1,-List2 splits List in two the simplest way, by putting alternate members in different lists partition First,Second|Rest , First|F , Second|S :!, partitionRest,F,S. partitionList,List, . = 2 elements
0 or 1 element
Table 7.1 shows that mergesort is fast, and, unlike Quicksort, its efciency does not suffer if the list is already almost sorted or almost backward.
Exercise 7.9.1 Modify merge so that it removes duplicates from the lists that it creates. Call your version merge_rd. To demonstrate that it works, try the query:
?- merge_rd a,a,c,c , b,b ,What. What = a,b,c
Do not use remove_duplicates or member. Traverse each list only once. Exercise 7.9.2 Get msort working on your computer and modify it to use merge_rd. Call your version msort_rd. Exercise 7.9.3 If your Prolog correctly implements the ifthen operator (Chapter 4, Section 4.7), use it to combine the rst two clauses of merge into one, thereby speeding up the algorithm. Exercise 7.9.4 (small project)
Devise a better partitioning algorithm for mergesort. Your goal should be to preserve (some or all) ordered subsequences that already exist, so that mergesort will proceed much faster if the list is already almost sorted. For example, in the list 5,4,9,0,1,2,3,7,0,6 , the sequence 0,1,2,3,7 should not be broken up, since merge can handle it directly. You do not have to preserve all such sequences; instead, experiment with techniques to increase the probability of preserving them.
Authors manuscript
Sec. 7.10.
Binary Trees
191
Jenkins
Carter
Roberts
Adamson
Davis
Kilgore
Williams
A binary tree. Each cell contains a pointer to a name (or other data) together with pointers to cells that alphabetically precede and follow it.
Figure 7.5
where Element is an element and Left and Right are additional trees. We will use the atom empty to designate an empty tree (one with no elements and no pointers).
Authors manuscript
192
treetreetreeempty, 'Adamson', empty, 'Carter', treeempty, 'Davis', empty, 'Jenkins', treetreeempty, 'Kilgore', empty, 'Roberts', treeempty, 'Williams', empty
Advanced Techniques
Chap. 7
Figure 7.6
Figure 7.6 shows what the tree in Figure 7.5 looks like when encoded this way. Notice that Figure 7.5 is not a full tree diagram of this structure (such as we were drawing in Section 7.1), but the essential relationships are preserved. To insert an element into a tree, you use alphabetic order to search for the place where it ought to go, and put it there:
insert+NewItem,-Tree,+NewTree Inserts an item into a binary tree. insertNewItem,empty,treeNewItem,empty,empty :- !. insertNewItem,treeElement,Left,Right,treeElement,NewLeft,Right :NewItem @ Element, !, insertNewItem,Left,NewLeft. insertNewItem,treeElement,Left,Right,treeElement,Left,NewRight :insertNewItem,Right,NewRight.
If the elements to be inserted are initially in random order, the tree remains well balanced that is, the chances are about equal of branching to the left or to the right in any particular case. If the elements are inserted predominantly in ascending or descending order, the tree becomes unbalanced and list-like (Figure 7.7). Trees can be searched quickly because the process of nding an element is like that of inserting it, except that we stop when we nd a node whose element matches the one we are looking for. On the average, in a tree with N nodes, the number of nodes that must be examined to nd a particular element is proportional to log2 N if the tree is well balanced, or proportional to N if the tree is severely unbalanced.
Authors manuscript
Sec. 7.10.
Binary Trees
193
Adamson
Carter
Davis
Jenkins
Kilgore
Roberts
Williams
An unbalanced binary tree, containing the same data as in Figure 7.5, but inserted in a different order.
Figure 7.7
Authors manuscript
194
Advanced Techniques
Chap. 7
Of course nodes can have other arguments containing information associated with the key element; in this case the binary tree becomes the core of a fast informationretrieval system. Many strategies have been developed for keeping trees balanced as they are built. For a survey, see Wirth (1986:196-268).
Exercise 7.10.1 Get insert working on your computer. What tree do you get by executing the following query? Draw a diagram of it.
?- insertnute,empty,Tree1, insertcovington,Tree1,Tree2, insertvellino,Tree2,Tree3, writeTree3, nl.
Exercise 7.10.2 Dene a predicate retrieveTree,Element that succeeds if Element is an element of Tree. At each branching, determine whether Element should be in the left or the right subtree, and search only that subtree. Using the tree from the previous exercise, demonstrate that your predicate works correctly. Exercise 7.10.3 Our version of insert makes a new copy of at least part of the tree. Implement a version of insert in which empty positions are marked by uninstantiated variables (rather than by the atom empty) and new subtrees can be added by simply instantiating the variables. (What list processing technique does this correspond to?)
7.11. TREESORT
We will demonstrate binary trees by using them as the basis of a sorting algorithm: well transform a list into a tree, then transform it back into a list, and it will come out sorted. This is not a normal use for trees, because normally, if you want the benets of trees, youd keep your data in a tree all along rather than starting out with a list and then transforming it. But it does make a good demonstration of the power of binary trees. The algorithm is shown in le TREESORT.PL (Figure 7.8). Table 7.1 shows that its performance is comparable to Quicksort, and, indeed, it is essentially a Quicksort with much of the recursion expressed in the data structure, not just in the procedures. We emphasize that TREESORT.PL was written for readability, not efciency; it could probably be speeded up a few percent without difculty. The algorithm to convert a list into a tree is simple: CDR down the list and call insert once for each element. To turn the tree back into a list, retrieve the elements in rightleft order and build the list tail rst, starting with the last element and adding the others in front of it. This is done as follows: 1. Recursively extract all the elements in the right subtree, and add them to the list.
Authors manuscript
Sec. 7.11.
Treesort
195
File TREESORT.PL Sorting a list by converting it into a binary tree, then back into a list
insert+NewItem,-Tree,+NewTree Inserts an item into a binary tree. insertNewItem,empty,treeNewItem,empty,empty :- !. insertNewItem,treeElement,Left,Right,treeElement,NewLeft,Right :NewItem @ Element, !, insertNewItem,Left,NewLeft. insertNewItem,treeElement,Left,Right,treeElement,Left,NewRight :insertNewItem,Right,NewRight.
insert_list+List,+Tree,-NewTree Inserts all elements of List into Tree giving NewTree. insert_list Head|Tail ,Tree,NewTree :!, insertHead,Tree,MiddleTree, insert_listTail,MiddleTree,NewTree. insert_list ,Tree,Tree.
Figure 7.8
Authors manuscript
196
Advanced Techniques
Chap. 7
list_to_tree+List,-Tree inserts all elements of List into an initially empty tree. list_to_treeList,Tree :- insert_listList,empty,Tree.
tree_to_list+Tree,-List places all elements of Tree into List in sorted order. tree_to_listTree,List :tree_to_list_auxTree, ,List. tree_to_list_auxempty,List,List. tree_to_list_auxtreeItem,Left,Right,OldList,NewList :tree_to_list_auxRight,OldList,List1, tree_to_list_auxLeft, Item|List1 ,NewList.
End of TREESORT.PL
Authors manuscript
Sec. 7.12.
197
2. Add the element in the top node to the list. 3. Recursively extract all the elements in the left subtree, and add them to the list. This is implemented in the predicate tree_to_list in TREESORT.PL. Much time can be saved by avoiding the use of lists altogether. If all you want to do is write out the elements of a tree in sorted order, without actually converting them back into a list, you can just traverse the tree, like this:
write_sorted+Tree prints out the elements of Tree in sorted order write_sortedempty :- !. write_sortedtreeElement,Left,Right :write_sortedLeft, writeElement, write' ', write_sortedRight.
Trees are a good alternative to lists in situations where the data must be searched quickly or retrieved in a particular order.
Exercise 7.11.1 Get treesort working on your computer and modify it to eliminate duplicates in the list being sorted. Call your version treesort_rd. Exercise 7.11.2 In TREESORT.PL, why does tree_to_list_aux traverse Right before traversing Left?
Authors manuscript
198
Advanced Techniques
Chap. 7
To evaluate X*Y (where X and Y are expressions), rst evaluate X and Y, then multiply the results. Its obvious how to ll in -, , and other arithmetic operations. Moreover, To evaluate a plain number, just leave it alone. File ARITH.PL (Figure 7.9) shows the whole thing. We implement +, -, *, , and rec (which means reciprocal and is put there to show that we can make up our own evaluable functors). You could, of course, add any other functors that you wish. [Algorithms for computing sines, cosines, and many other mathematical functions are given by Abramowitz and Stegun (1964).] Note however that := may run considerably slower than is, because some Prologs (Quintus, for instance) compile isqueries into basic arithmetic operations on the computer, while the only thing the compiler can do with a query to := is make a call to the procedure you have dened.
Exercise 7.12.1 Get := working on your computer. Compare the time taken to execute the two goals X is 2+3 4 and Y := 2+3 4. To get a meaningful comparison, you will have to construct a loop that performs the computation perhaps 100 to 1000 times. Exercise 7.12.2 Modify the denition of := so that rstargument indexing can speed up the choice of clauses. (Hint: the query Result := Expression could call a predicate such as evalExpression,Result which could index on the principal functor of Expression.)
does not work. Clearly, the reason Prolog cant solve such queries is that it cant exhaustively search the whole set of real numbers. But heuristic searching for numbers is quite feasible, and techniques for doing it are at least as old as Sir Isaac Newton. In this section well develop a numerical equation solver that will answer queries such as these:
?- solve X + 1 = 1 X = 0.618034 X .
Crucially, this is a numerical solver, not an analytic one. That is, it does not solve by manipulating the equation itself (except for one minor change which well get to);
Authors manuscript
Sec. 7.13.
199
File ARITH.PL A homemade substitute for 'is' Result := Expression Evaluates expressions in much the same way as 'is'. Evaluable functors are + - * and rec reciprocal. :- op700,xfx,:=. Result := X + Y :!, Xvalue := X, Yvalue := Y, Result is Xvalue + Yvalue. !, Xvalue := X, Yvalue := Y, Result is Xvalue - Yvalue. !, Xvalue := X, Yvalue := Y, Result is Xvalue * Yvalue. !, Xvalue := X, Yvalue := Y, Result is Xvalue !, Xvalue := X, Result is 1 !, numberTerm. write'Error, can''t evaluate ', writeTerm, nl, !, fail.
Result := X - Y
:-
Result := X * Y
:-
Result := X
:-
Yvalue.
Result := recX
:-
Xvalue.
Term
:= Term
:-
:= Term
:-
Figure 7.9
Authors manuscript
200
To solve Left = Right: function Dif X = Left , Right where X occurs in Left and/or in Right;
Advanced Techniques
Chap. 7
procedure Solve: begin Guess1 := 1; Guess2 := 2; repeat Slope := Dif Guess2 , Dif Guess1=Guess2 , Guess1; Guess1 := Guess2; Guess2 := Guess2 , Dif Guess2=Slope until Guess2 is sufciently close to Guess1; result is Guess2 end.
Figure 7.10
instead, it takes the equation, as given, and searches heuristically for the number that will satisfy it.3 The technique that well use is called the secant method. Given an equation Left = Right, well dene a function Dif X = Left , Right where X is a variable that appears in Left, Right, or both. Now, instead of trying to make Left = Right, well be trying to make Dif X = 0. Well do this by taking two guesses at the value of X and comparing the corresponding values of Dif X . One of them will be closer to zero than the other. Not only that, but the amount of difference between them will tell us how much farther to change X in order to get Dif X even closer to zero. Figure 7.10 shows the whole algorithm in Pascallike pseudocode. Figure 7.11 shows how this algorithm is implemented in Prolog. First, free_in searches the equation to nd a variable to solve for. Then define_dif creates an asserts a clause to compute Dif X . Finally, solve_for conducts the search itself. The secant method works surprisingly well but isnt infallible. It can fail in three major ways. First, it may try two values of X that give the same Dif X ; when this happens, it cant tell what to do next, and the program crashes with a division by zero. Thats why
?- solveX*X = X*3.
fails or terminates abnormally. Second, it may simply fail to converge in a reasonable number of iterations; thats why
3 Sterling
and Shapiro (1994) give an analytic equation solver in Prolog, with references to the
literature.
Authors manuscript
Sec. 7.14.
Bibliographical Notes
201
?- solvesinX = 0.001.
never nds a solution (at least on our computer; yours may be different). Last, the secant method may jump over excessively large portions of the sine curve or similar periodic functions, so that even when it ultimately solves a problem, the solution is not the one with X nearest zero. Thats why you get
?- solvesinX = 0.01. X = 213.6383
when you might have expected X (in radians) to be very close to 0.01. All of these failures can often be prevented by choosing different initial guesses for X, instead of always using 1 and 2. We chose the secant method only because of its simplicity. For information on other, better, numerical methods for solving equations, see Hamming (1971) and Press, Flannery, Teukolsky, and Vetterling (1986).
Exercise 7.13.1 Get SOLVER.PL working on your computer and use it to solve Keplers equation, E , 0:01 sin E = 2:5. What solution do you get? Exercise 7.13.2 What happens when you try to solve X = X + 1 using SOLVER.PL? Exercise 7.13.3 (project)
Implement a better numerical equation solver. This could be anything from a minor improvement to SOLVER.PL, all the way to a program that uses considerable intelligence to select the best of several methods.
Authors manuscript
202
File SOLVER.PL Numerical equation solver Covington 1989
Advanced Techniques
Chap. 7
solve+Left=Right Left=Right is an arithmetic equation containing an uninstantiated variable. On exit, that variable is instantiated to a solution. solveLeft=Right :free_inLeft=Right,X, !, define_difX,Left=Right, solve_forX.
free_in+Term,-Variable Variable occurs in Term and is uninstantiated. free_inX,X :- An atomic term varX. free_inTerm,X :- A complex term Term == , Term =.. _,Arg|Args , free_inArg,X ; free_inArgs,X.
define_dif-X,+Left=Right Defines a predicate to compute Left-Right given X. Here X is uninstantiated but occurs in Left=Right. define_difX,Left=Right :abolishdif,2, assertdifX,Dif :- Dif is Left-Right.
solve_for-Variable Sets up arguments and calls solve_aux below. solve_forVariable :dif1,Dif1, solve_auxVariable,1,Dif1,2,1.
Figure 7.11
Authors manuscript
Sec. 7.14.
Bibliographical Notes
203
solve_aux-Variable,+Guess1,+Dif1,+Guess2,+Iteration Uses the secant method to solve for Variable see text. Other arguments: Guess1 -- Previous estimated value. Dif1 -- What 'dif' gave with Guess1. Guess2 -- A better estimate. Iteration -- Count of tries taken. solve_aux_,_,_,_,100 :!, write' Gave up at 100th iteration ',nl, fail. solve_auxGuess2,Guess1,_,Guess2,_ :close_enoughGuess1,Guess2, !, write' Found a satisfactory solution ',nl. solve_auxVariable,Guess1,Dif1,Guess2,Iteration :write Guess2 ,nl, difGuess2,Dif2, Slope is Dif2-Dif1 Guess2-Guess1, Guess3 is Guess2 - Dif2 Slope, NewIteration is Iteration + 1, solve_auxVariable,Guess2,Dif2,Guess3,NewIteration.
close_enough+X,+Y True if X and Y are the same number to within a factor of 1.000001. close_enoughX,Y :Quot is X Y, Quot 0.999999, Quot 1.000001.
End of SOLVER.PL
Authors manuscript
204
Advanced Techniques
Chap. 7
Authors manuscript
Part II
205
Authors manuscript 693 ppid September 9, 1995 Prolog Programming in Depth
Authors manuscript
Contents
207
Authors manuscript 693 ppid September 9, 1995 Prolog Programming in Depth
208
Authors manuscript
Chapter 8
209
Authors manuscript 693 ppid September 9, 1995 Prolog Programming in Depth
210
Chap. 8
what its name implies: the attempt to build intelligent artifacts. But one of the most important lessons of articial intelligence research is that we know very little about what intelligence really is. The more we tinker with our computers, the more we are impressed with how easily our minds perform complex tasks even though we cant begin to explain how we are able to do these things. Alan Turing, the noted British mathematician, proposed the famous Turing test for machine intelligence. He proposed that a machine was intelligent if a person communicating with it, perhaps over a teletype so that he couldnt see it, couldnt tell that he was communicating with a machine rather than another human being. But Turings test doesnt tell us what intelligence is. It proposes an independent criterion for deciding whether a machine has intelligence whatever it is. Turing predicted that some machine would pass his test by the year 2000. Most people working in AI agree that no machine will pass Turings test by 2000, and perhaps no machine will ever pass it. Some critics argue that it is impossible for a machine to be intelligent in the way that humans are intelligent. At best, these arguments show that none of the machines we have or envision now are humanly intelligent. They really do not show that such a machine is impossible. Until we can say just what intelligence is, its difcult to see how anyone could prove that machine intelligence is either possible or impossible. Not every scientist whose work falls within the vague realm of AI is trying to build an intelligent machine. What other goals are there for AI? One possible goal is to build machines that do things a human needs intelligence to do. We arent trying to build machines that are intelligent, but only to build machines that simulate intelligence. For example, whatever intelligence is, it is needed to play chess or to prove theorems in mathematics. Lets see if we can build computers that simulate intelligence by performing these tasks as well as a human can. This criterion does not separate what we have come to call articial intelligence from many other things. Pocket calculators and even thermostats perform tasks that a human needs intelligence to perform, but we probably wouldnt say that these devices simulate intelligence. They certainly arent usually included in lists of great successes in AI research. On the other hand, AI is concerned with things like vision and hearing. Do humans require intelligence to see and hear? Very primitive animals can do these things as well as or better than we can. Part of our problem is that we are once again running into the question, What is intelligence? If we set this question aside and just try to build machines that simulate intelligence, we may also end up taking the machine as the denition of intelligence. Something like this has happened with IQ tests already: some people propose that intelligence as whatever it is the IQ test measures. Even if we avoid this pitfall, it may only be an illusion to think that it is easier to simulate intelligence than to build a truly intelligent machine. If we are not sure what intelligence is, it is no easier to recognize simulated intelligence than real intelligence in a machine. The AI scientist must therefore try to reach a better understanding of what intelligence is. He differs from other investigators interested in this same problem mainly because his primary research tool is the computer. This, then, is a third
Authors manuscript
Sec. 8.1.
211
possible goal for AI research: to investigate intelligence by means of computers. Perhaps we should stretch our boundaries a bit. Human intelligence may include many things like reasoning, learning, problem solving, rule following, language understanding and much more. Further, these are only part of what many AI researchers are investigating. For example, perception and memory, with its attendant failures of forgetting, are among the topics explored by some AI scientists. Yet we might hesitate to call these processes intelligent, although we might agree that they somehow contribute to intelligence. The whole range of human mental activities and abilities are called cognitive processes. One goal of AI research, then, is to use computers to help us better understand these processes. On this view of articial intelligence, AI research begins not with computers but with humans. Scientists from different disciplines, including psychology, linguistics, and philosophy, have been studying cognitive processes since long before computers became commonplace. When these cognitive scientists begin to use computers to help them construct and test their theories of human cognition, they become AI scientists. Using computers in this way has certain advantages over other methods of investigation. Unlike abstract theories, computer programs either run or they dont; and when they run they give denite outputs for specic inputs. We might think of the computer as a kind of cognitive wind tunnel. We have a theory about how humans perform some cognitive process, analogous to a theory about how a wing lifts an airplane. We build a program based on our theory just as an aeronautical engineer builds a model wing based on his theory. We run the program on our computer as the aeronautical engineer tries out his model wing in the wind tunnel. We see how it runs and use what we learn to improve our theory. Suppose we devise a theory about human cognition and then embody the theory in a program. When we run the program, it gives results that agree with our observations of real, living humans. Does this mean that the theory we used to build the program was correct? Unfortunately, no. The theory and the program may get the same results a human gets, but do it in a very different way than a human does. For example, a pocket calculator does arithmetic by converting to binary numbers, whereas a human being works directly with decimal numbers. So although computer modeling can help us nd aws in our theories, it can never nally conrm them. AI represents a new technology as well as an area of basic research. Technological spinoffs of AI include new programming languages and techniques that can be used for many different purposes. Prolog is one of these. Besides scientists who do what we might call basic AI research, we should also talk about a group that we could call AI technicians. These are people whose goal is to nd ways to solve real, everyday problems using computers. What distinguishes them from other programmers and software designers is that they use the tools that have been developed through AI research. Some AI practitioners not only use existing AI techniques but also try to develop new techniques for using computers to solve problems. How do we distinguish these researchers from computer scientists generally? We will base the distinction on the kinds of problems AI scientists are trying to solve. Humans do many different
Authors manuscript
212
Chap. 8
kinds of things, and they are good at explaining exactly how they do some of them. Examples are adding a column of numbers or alphabetizing a list of words. Humans have great difculty explaining how they do other things, such as understanding a sentence or riding a bicycle. We have good theories about arithmetic, but our theories about understanding language are primitive by comparison. Some AI researchers would describe what they do as trying to nd ways to get computers to do the things that humans can do without being able to say exactly how they do them. So we have four possible goals that an AI researcher might pursue: building intelligent machines, building machines that simulate intelligence, using computers to better understand intelligence, and nding ways to get a computer to do things that humans do but cant explain how they do. Of course, these are not exclusive goals and a particular AI scientist might be chasing all of these goals at once. Nor does this list exhaust the goals of articial intelligence. Different AI researchers might give quite different accounts of what they are doing. None of this gives us a tight denition of articial intelligence, but we hope it provides some understanding of what some of us working in AI think we are about. Humans love games and puzzles. Even simple games and puzzles often require considerable intelligence. Since the early days of AI, researchers have been intrigued with the possibility of a machine that could play a really complex game like chess as well as any human could. Many of us have met chess programs that play better than we do. The original hope was that in developing game playing programs we might learn something crucial about the nature of intelligence. Unfortunately, it is often difcult to apply much of what we learn from one program directly to another program. But some things have become clear through this and other kinds of research, including efforts to build automated theorem provers. Lets look at what is involved in playing a game or solving a puzzle. There is something that is manipulated. This may be a board and pieces, letters and words, or even our own bodies. There is some initial conguration in which the things to be manipulated are placed. There is a set of rules determining how these things may be manipulated during the game. And there is a specication of a situation which, if achieved, constitutes success. All of these factors dene the game or puzzle. Besides the dening rules, there may be rules of strategy associated with a game. These rules recommend certain of the legal moves we can make in different situations during the game. Because they are only advisory, we can violate these rules and still play the game. To write a program that can play a game or solve a puzzle, we will need to nd a way to represent the initial situation, the rules for making legal moves, and the denition of a winning situation. Most of us know how to play tic-tac-toe. We know how to draw the grid and how to make the moves. We know what constitutes a win. But it is not obvious how best to represent all this knowledge in a program. This problem of knowledge representation is one of the most common problems in all AI efforts. Different ways of representing knowledge may make solving a problem more or less difcult. (Consider, for instance, trying to do long division using Roman numerals.) All of us have tangled with a frustrating problem at some time, then
Authors manuscript
Sec. 8.2.
213
suddenly seen a new way of looking at the problem that makes its solution simple. The way we choose to represent the knowledge needed to play a game can make the task of writing a program that plays the game difcult or easy. We will limit our attention to puzzles and other problems that can be solved by a single person. These are easier to understand and analyze than competitive games where we must take into account the moves of another person. Some of the puzzles we will discuss are not just for entertainment; they represent practical problems. Once we develop a good representation of the situations that can arise in a puzzle, dene the initial situation and the legal moves that can be made, and characterize the criteria for success, we should be able to use the method of exhaustive search to solve the puzzle. That is, we can try all possible sequences of moves until we nd one that solves the puzzle. To be exhaustive, the procedure for generating sequences of moves must eventually generate every sequence of moves that could possibly be made. If there is a solution to the puzzle, exhaustive search must eventually nd it. We will use the method of exhaustive search to solve a maze, to solve the missionaries and cannibals puzzle, to solve a peg-board puzzle, to color a map, to search a molecular structure for specied substructures, and to make ight connections. We will also look at some faster methods for searching for solutions which are not exhaustive. Finally, we will develop routines for conducting forward-chaining and breadth-rst inference within Prolog as alternatives to Prologs normal mode of depth-rst backward-chaining inference.
A path through the maze is a list of positions with start at one end of the list and verb"nish" at the other, such that every position in the list is connected to the
Authors manuscript
214
Chap. 8
START
1 7 13 19 25 31
2 8 14 20 26 32
FINISH
3 9 15 21 27 33
4 10 16 22 28 34
5 11 17 23 29 35
6 12 18 24 30 36
Figure 8.1
Authors manuscript
Sec. 8.2.
215
positions before and after it. Initially, our path contains the single point start which we place into a list. From this initial path, we want to generate a complete path from start to finish. Once a solution is found, we want our program to display it for us.
solve_maze :- path start ,Solution, writeSolution.
The procedure path will nd a solution to the maze puzzle. Of course, when we reach finish we have a solution and our search is ended.
path finish|RestOfPath , finish|RestOfPath .
At each intermediate step in our search for a solution to the maze, we have a list of the positions we have already visited. The rst member of this list is our current position. We proceed by looking for a new position that we can reach from our current position. This new position must be connected to our current position. We dont want to move around in a circle or back and forth between the same positions; so our new position will also have to be a point that isnt already in the path we are building.
path CurrentLocation|RestOfPath ,Solution :connected_toCurrentLocation,NextLocation, + memberNextLocation,RestOfPath, path NextLocation,CurrentLocation|RestOfPath ,Solution.
If the procedure path reaches a point where it cannot nd a new position, Prolog will backtrack. Positions will be dropped off the front of the path we have built until we reach a point where a new position can be reached. Then the search will move forward again until we reach finish or another dead-end. Since the maze has a solution, Prolog will eventually nd it if we have dened our procedure correctly. The complete program is called MAZE.PL.
Exercise 8.2.1 Construct a maze that has more than one solution, i.e., more than one path through the maze. Dene a procedure shortest_path that nds a shortest solution to the maze. Exercise 8.2.2 How would you represent a three-dimensional maze constructed in a 4 4 4 grid? How would you modify solve_maze to nd a path through this three-dimensional maze?
Authors manuscript
216
Chap. 8
Authors manuscript
Sec. 8.3.
217
missionaries and the cannibals across the river, we will have some missionaries on the left bank of the river and some on the right bank, some of the cannibals on the left bank of the river and some on the right bank, and the boat will be on either the left or the right bank. We can represent this information with a structure that tells us how many missionaries are on the left bank, how many cannibals are on the left bank, and the location of the boat: stateM,C,B. Here the variables M and C can take values from 0 to 3 and the variable B can take values l (left bank) or r (right bank). The structure state3,3,l represents the situation at the beginning of the puzzle, and the structure state0,0,r represents the situation we want to achieve. A solution to the puzzle will be a list of situations with state3,3,l at one end of the list and state0,0,r at the other end. Since the program produces a list of states in the reverse order that they occur, we have the program reverse the solution before displaying it.
cannibal :- solve_cannibal state3,3,l ,Solution, reverseSolution, ,OrderedSolution, show_statesOrderedSolution. solve_cannibal state0,0,r|PriorStates , state0,0,r|PriorStates .
Any two successive states in the solution will have to satisfy several conditions. First, the boat will be on opposite sides of the river in the two states. That is, nobody crosses the river without the boat. Second, the cannibals will never outnumber the missionaries on either side of the river. Third, each of the states could be produced from the other by sending a boat-load of missionaries and cannibals across the river in the appropriate direction. We solve the puzzle in much the same way that we solved the maze problem, except that at each stage in our search for a solution the restrictions placed on the next move are more complicated. We want Prolog to explore all possible combinations of trips across the river until it nds one that works. At any point in this process, we will have a list of states that resulted from the trips that have been made so far. The rst member of this list is the current state. We want Prolog to look for a next state that can be reached from the current state. The next state must be the result of a legal boat trip beginning from the current state, must not endanger any missionaries, and must be a state that did not occur earlier in our list. This last requirement prevents Prolog from moving the same people back and forth across the river without ever making any progress. To accomplish this, we add two clauses to our denition of solve_cannibal, one for when the oat is on the left bank and one for when the boat is on the right bank.
solve_cannibal stateM1,C1,l|PriorStates ,Solution :member M,C , 0,1 , 1,0 , 1,1 , 0,2 , 2,0 , Condition M1 = M, Condition C1 = C, Condition M2 is M1 - M, Condition C2 is C1 - C, Condition
1 2 3 4 5
Authors manuscript
218
Chap. 8
member M2,C2 , 3,_ , 0,_ , N,N , Condition 6 + memberstateM2,C2,r,PriorStates, Condition 7 solve_cannibal stateM2,C2,r,stateM1,C1,l|PriorStates , Solution. solve_cannibal stateM1,C1,r|PriorStates ,Solution :member M,C , 0,1 , 1,0 , 1,1 , 0,2 , 2,0 , 3 - M1 = M, 3 - C1 = C, M2 is M1 + M, C2 is C1 + C, member M2,C2 , 3,_ , 0,_ , N,N , + memberstateM2,C2,l,PriorStates, solve_cannibal stateM2,C2,l,stateM1,C1,r|PriorStates , Solution.
To understand the program, we must understand the conditions in the rst clause above. Condition 1 species that the boat carries at least one and at most two individuals across the river on the next trip. Conditions 2 and 3 insure that no more missionaries and cannibals enter the boat than are presently on the left bank of the river. Conditions 4 and 5 determine how many missionaries and cannibals will be on the left bank of the river after the next trip. Condition 6 checks to see that the missionaries are all safe after the trip. A state is safe if all the missionaries are on the same bank (and thus cannot be outnumbered) or if there is an equal number of missionaries and cannibals on the left bank (and thus also an equal number on the right bank). Finally, condition 7 guarantees that the program does not return to a previous state. If solve_cannibal reaches a point where it cannot nd a safe trip that will produce a situation it hasnt already tried, Prolog backtracks to a point where a new situation is available. Then the search moves forward again.
MMMCCC MMCC MMMCC MMM MMMC MC MMCC CC CCC C CC ____ ____ ____ ____ ____ ____ ____ ____ ____ ____ ____ ____
Figure 8.2
We display the solution to our puzzle with a little pizzazz by constructing a series of pictures. This involves reversing the solution list so that the solutions are
Authors manuscript
Sec. 8.3.
219
displayed in the order generated. The predicate show_states and its auxiliaries do this, drawing pictures with ordinary ASCII characters as shown in Figure 8.2.
Exercise 8.3.1 Another version of the missionaries and cannibals puzzle assumes that the missionaries are safe from the cannibals, but that the cannibals are in danger of conversion if they are ever outnumbered by the missionaries. What changes in CANNIBAL.PL will protect the cannibals from the missionaries? Exercise 8.3.2 A farmer needs to transport a fox, a goose, and a bag of grain across a small stream. He can carry only one item at a time. Given the opportunity, the fox will eat the goose and the goose will eat the grain. Write a Prolog program that nds and displays a solution to this puzzle. The program should be invoked by the query ?- farmer. Exercise 8.3.3 With an unlimited water supply, a 5 gallon bucket, and a 3 gallon bucket, you need to measure out exactly one gallon of water. Write a Prolog program to nd a way to do this. The program should be invoked by the query ?- buckets.
Authors manuscript
220
Chap. 8
bank after the crossing. + memberstateM2,C2,r,PriorStates, No earlier state is repeated. solve_cannibal stateM2,C2,r,stateM1,C1,l|PriorStates ,Solution. solve_cannibal stateM1,C1,r|PriorStates ,Solution :member M,C , 0,1 , 1,0 , 1,1 , 0,2 , 2,0 , One or two persons cross the river. 3 - M1 = M, 3 - C1 = C, The number of persons crossing the river is limited to the number on the right bank. M2 is M1 + M, C2 is C1 + C, The number of persons remaining on the right bank is decreased by the number that cross the river. member M2,C2 , 3,_ , 0,_ , N,N , The missionaries are not outnumbered on either bank after the crossing. + memberstateM2,C2,l,PriorStates, No earlier state is repeated. solve_cannibal stateM2,C2,l,stateM1,C1,r |PriorStates ,Solution. show_states . show_states stateM,C,Location|LaterStates :write_n_times'M',M, write_n_times'C',C, N is 6 - M - C, write_n_times' ',N, draw_boatLocation, MM is 3 - M, CC is 3 - C, write_n_times'M',MM, write_n_times'C',CC, nl, show_statesLaterStates. write_n_times_,0 :- !. write_n_timesItem,N :writeItem, M is N - 1, write_n_timesItem,M. draw_boatl :- write' ____ '. draw_boatr :- write' ____ '. memberX, X|_ . memberX, _|Y :- memberX,Y. reverse ,List,List. reverse X|Tail ,SoFar,List :reverseTail, X|SoFar ,List.
Authors manuscript
Sec. 8.4.
221
Initially, there is a peg in each hole. The player removes one peg, then proceeds to jump one peg over another, each time removing the peg that has been jumped. A jump must always be in a straight line, and only one peg can be jumped at a time. The object of the game is to nish with only one peg in the board. A solution to the puzzle is represented by a sequence of boards, with fourteen pegs in the initial board and one peg in the nal board. For each pair of adjacent boards in the solution, we must be able to produce the later member of the pair from the earlier by a legal jump. Our program has at the top level a procedure triangleN that removes the th peg from the triangle, calls the procedure solve_triangle that actually nds N the solution, reverses the solution and displays it one triangle at a time.
triangleN :- remove_pegN,StartingTriangle, solve_triangle14, StartingTriangle ,Solution, reverseSolution, ,OrderedSolution, nl,nl, show_trianglesOrderedSolution.
The procedure solve_triangle uses a number as its rst argument to keep track of how many pegs are in the current triangle. This makes checking for success easy: a solution to the puzzle has been reached if there is one peg left. If there are more pegs left, solve_triangle nds a triangle that can be produced from the current triangle by a legal jump and adds it to the list of triangles. The procedure prints the number of triangles left in the new triangle so we have something to watch while the search proceeds. Then it recurses.
solve_triangle1,Solution,Solution. solve_triangleCount, CurrentTriangle|PriorTriangles ,Solution:jumpCurrentTriangle,NextTriangle, NewCount is Count - 1, writeNewCount,sp, solve_triangleNewCount, NextTriangle,CurrentTriangle|PriorTriangles , Solution.
Notice that solve_triangle doesnt check to see if we have returned to a previous position. In the maze or the missionaries and cannibals puzzle we had to do this,
Authors manuscript
222
Chap. 8
but each jump in the triangle puzzle eliminates a peg and produces a position that we could not have reached earlier. Before we can use triangle_solver, we need a way to compute the triangles that result from making a legal jump in the current triangle. There are many ways to do this, but one of the most elegant was suggested by Richard OKeefe of the Royal Melbourne Institute of Technology in an electronic mail discussion of the triangle puzzle. First, we represent each triangle with a list of fteen holes where each hole is represented by a variable:
A, B,C, D,E,F, G,H,I,J, K,L,M,N,P
Representing a jump as a transformation from one triangle to another, we see that only three holes in a triangle are affected by any jump. The remaining holes remain empty or occupied just as they were prior to the jump. Using 1 to represent a peg and 0 to represent an empty hole, we can dene a binary predicate jump by a set of clauses like the following:
jumptriangle1, 1,C, 0,E,F, G,H,I,J, K,L,M,N,P, triangle0, 0,C, 1,E,F, G,H,I,J, K,L,M,N,P.
We need one such clause for each possible jump. All that remains is to dene a procedure that removes the initial peg and routines for displaying the nished solution.
Exercise 8.4.1 A variation on the triangle puzzle is to remove all but one peg, but to leave the last peg in the hole that was empty at the beginning of the puzzle. Dene a predicate called my_triangle that looks for a solution like this. Is there a solution to this problem? Exercise 8.4.2 Another variation of the triangle puzzle is to leave 8 pegs on the board with no legal jump left. Write a predicate called triangle8 that looks for such a solution. Does it succeed? Exercise 8.4.3 Analyze the solution you get to the query ?- triangle1. You will see that the moves come in a certain order. Reorder the clauses in the denition of jump so the moves
Authors manuscript
Sec. 8.4.
223
in this solution are at the top of the denition of jump. What effect does this have on the time it takes to nd a solution to ?- triangle1? Do you get the same solution? Explain what you observe. Exercise 8.4.4 Dene a predicate triangle10 1, together with all the auxiliary predicates needed, to search for a solution for the triangle puzzle with ten pegs in this arrangement:
o o o o o o o o o o
Authors manuscript
224
Chap. 8
remove_peg1,triangle0,1,1,1,1,1,1,1,1,1,1,1,1,1,1. remove_peg2,triangle1,0,1,1,1,1,1,1,1,1,1,1,1,1,1. remove_peg3,triangle1,1,0,1,1,1,1,1,1,1,1,1,1,1,1. remove_peg4,triangle1,1,1,0,1,1,1,1,1,1,1,1,1,1,1. remove_peg5,triangle1,1,1,1,0,1,1,1,1,1,1,1,1,1,1. remove_peg6,triangle1,1,1,1,1,0,1,1,1,1,1,1,1,1,1. remove_peg7,triangle1,1,1,1,1,1,0,1,1,1,1,1,1,1,1. remove_peg8,triangle1,1,1,1,1,1,1,0,1,1,1,1,1,1,1. remove_peg9,triangle1,1,1,1,1,1,1,1,0,1,1,1,1,1,1. remove_peg10,triangle1,1,1,1,1,1,1,1,1,0,1,1,1,1,1. remove_peg11,triangle1,1,1,1,1,1,1,1,1,1,0,1,1,1,1. remove_peg12,triangle1,1,1,1,1,1,1,1,1,1,1,0,1,1,1. remove_peg13,triangle1,1,1,1,1,1,1,1,1,1,1,1,0,1,1. remove_peg14,triangle1,1,1,1,1,1,1,1,1,1,1,1,1,0,1. remove_peg15,triangle1,1,1,1,1,1,1,1,1,1,1,1,1,1,0. jump+CurrentTriangle,-NextTriangle Finds a NextTriangle that can be produced from CurrentTriangle by a legal jump. To save space, all but the first clause are displayed in linear format. jumptriangle1, 1,C, 0,E,F, G,H,I,J, K,L,M,N,P, triangle0, 0,C, 1,E,F, G,H,I,J, K,L,M,N,P. jumptriangle1,B,1,D,E,0,G,H,I,J,K,L,M,N,P, triangle0,B,0,D,E,1,G,H,I,J,K,L,M,N,P. jumptriangleA,1,C,1,E,F,0,H,I,J,K,L,M,N,P, triangleA,0,C,0,E,F,1,H,I,J,K,L,M,N,P. jumptriangleA,1,C,D,1,F,G,H,0,J,K,L,M,N,P, triangleA,0,C,D,0,F,G,H,1,J,K,L,M,N,P. jumptriangleA,B,1,D,1,F,G,0,I,J,K,L,M,N,P, triangleA,B,0,D,0,F,G,1,I,J,K,L,M,N,P. jumptriangleA,B,1,D,E,1,G,H,I,0,K,L,M,N,P, triangleA,B,0,D,E,0,G,H,I,1,K,L,M,N,P. jumptriangle0,1,C,1,E,F,G,H,I,J,K,L,M,N,P, triangle1,0,C,0,E,F,G,H,I,J,K,L,M,N,P. jumptriangleA,B,C,1,1,0,G,H,I,J,K,L,M,N,P,
Authors manuscript
Sec. 8.4.
225
triangleA,B,C,0,0,1,G,H,I,J,K,L,M,N,P. jumptriangleA,B,C,1,E,F,G,1,I,J,K,L,0,N,P, triangleA,B,C,0,E,F,G,0,I,J,K,L,1,N,P. jumptriangleA,B,C,1,E,F,1,H,I,J,0,L,M,N,P, triangleA,B,C,0,E,F,0,H,I,J,1,L,M,N,P. jumptriangleA,B,C,D,1,F,G,1,I,J,K,0,M,N,P, triangleA,B,C,D,0,F,G,0,I,J,K,1,M,N,P. jumptriangleA,B,C,D,1,F,G,H,1,J,K,L,M,0,P, triangleA,B,C,D,0,F,G,H,0,J,K,L,M,1,P. jumptriangle0,B,1,D,E,1,G,H,I,J,K,L,M,N,P, triangle1,B,0,D,E,0,G,H,I,J,K,L,M,N,P. jumptriangleA,B,C,0,1,1,G,H,I,J,K,L,M,N,P, triangleA,B,C,1,0,0,G,H,I,J,K,L,M,N,P. jumptriangleA,B,C,D,E,1,G,H,1,J,K,L,0,N,P, triangleA,B,C,D,E,0,G,H,0,J,K,L,1,N,P. jumptriangleA,B,C,D,E,1,G,H,I,1,K,L,M,N,0, triangleA,B,C,D,E,0,G,H,I,0,K,L,M,N,1. jumptriangleA,0,C,1,E,F,1,H,I,J,K,L,M,N,P, triangleA,1,C,0,E,F,0,H,I,J,K,L,M,N,P. jumptriangleA,B,C,D,E,F,1,1,0,J,K,L,M,N,P, triangleA,B,C,D,E,F,0,0,1,J,K,L,M,N,P. jumptriangleA,B,0,D,1,F,G,1,I,J,K,L,M,N,P, triangleA,B,1,D,0,F,G,0,I,J,K,L,M,N,P. jumptriangleA,B,C,D,E,F,G,1,1,0,K,L,M,N,P, triangleA,B,C,D,E,F,G,0,0,1,K,L,M,N,P. jumptriangleA,0,C,D,1,F,G,H,1,J,K,L,M,N,P, triangleA,1,C,D,0,F,G,H,0,J,K,L,M,N,P. jumptriangleA,B,C,D,E,F,0,1,1,J,K,L,M,N,P, triangleA,B,C,D,E,F,1,0,0,J,K,L,M,N,P. jumptriangleA,B,0,D,E,1,G,H,I,1,K,L,M,N,P, triangleA,B,1,D,E,0,G,H,I,0,K,L,M,N,P. jumptriangleA,B,C,D,E,F,G,0,1,1,K,L,M,N,P, triangleA,B,C,D,E,F,G,1,0,0,K,L,M,N,P. jumptriangleA,B,C,0,E,F,1,H,I,J,1,L,M,N,P, triangleA,B,C,1,E,F,0,H,I,J,0,L,M,N,P. jumptriangleA,B,C,D,E,F,G,H,I,J,1,1,0,N,P, triangleA,B,C,D,E,F,G,H,I,J,0,0,1,N,P. jumptriangleA,B,C,D,0,F,G,1,I,J,K,1,M,N,P, triangleA,B,C,D,1,F,G,0,I,J,K,0,M,N,P. jumptriangleA,B,C,D,E,F,G,H,I,J,K,1,1,0,P, triangleA,B,C,D,E,F,G,H,I,J,K,0,0,1,P. jumptriangleA,B,C,D,E,F,G,H,I,J,0,1,1,N,P, triangleA,B,C,D,E,F,G,H,I,J,1,0,0,N,P. jumptriangleA,B,C,0,E,F,G,1,I,J,K,L,1,N,P, triangleA,B,C,1,E,F,G,0,I,J,K,L,0,N,P. jumptriangleA,B,C,D,E,0,G,H,1,J,K,L,1,N,P, triangleA,B,C,D,E,1,G,H,0,J,K,L,0,N,P. jumptriangleA,B,C,D,E,F,G,H,I,J,K,L,1,1,0, triangleA,B,C,D,E,F,G,H,I,J,K,L,0,0,1. jumptriangleA,B,C,D,E,F,G,H,I,J,K,0,1,1,P, triangleA,B,C,D,E,F,G,H,I,J,K,1,0,0,P.
Authors manuscript
226
Chap. 8
jumptriangleA,B,C,D,0,F,G,H,1,J,K,L,M,1,P, triangleA,B,C,D,1,F,G,H,0,J,K,L,M,0,P. jumptriangleA,B,C,D,E,F,G,H,I,J,K,L,0,1,1, triangleA,B,C,D,E,F,G,H,I,J,K,L,1,0,0. jumptriangleA,B,C,D,E,0,G,H,I,1,K,L,M,N,1, triangleA,B,C,D,E,1,G,H,I,0,K,L,M,N,0. Procedure to display a solution. show_triangles :- !. show_triangles triangleA, B,C, D,E,F, G,H,I,J, K,L,M,N,P|LaterTriangles :sp4,writeA,nl, sp3,writeB,sp1,writeC,nl, sp2,writeD,sp1,writeE,sp1,writeF,nl, sp1,writeG,sp1,writeH,sp1,writeI,sp1,writeJ,nl, writeK,sp1,writeL,sp1,writeM,sp1, writeN,sp1,writeP,nl, write'Press any key to continue. ', get0_, nl,nl, show_trianglesLaterTriangles. sp0. spN :- write' ', M is N - 1, spM.
Authors manuscript
Sec. 8.5.
Coloring a Map
227
Country,Hue |List ,Solution.
We have added the write command so we can watch what the routine is doing as it searches for a solution. Given the current list of countries and their assigned colors, we cannot assign a color to a new country if one of its neighbors is on the list and already has that color:
prohibitedCountry,Hue,List :- bordersCountry,Neighbor, member Neighbor,Hue ,List.
It can be proven mathematically that we will never need more than four colors for any map. So we store four colors in clauses for the predicate color 1. We have selected the colors red, blue, green and yellow. The predicate prohibited 3 calls the predicate borders 2. We list each pair of neighboring countries using the predicate beside 2. Then we use beside to dene borders.
bordersCountry,Neighbor :- besideCountry,Neighbor. bordersCountry,Neighbor :- besideNeighbor,Country.
We call the main predicate map. Used as a query, this predicate calls color_map with an initially empty list of country-color pairs. When color_map returns a complete coloring scheme for the map, it is displayed using the writeln 1 routine.
map :- color_map ,Solution, writelnSolution.
Finally, we need a representation of the map itself. This is a series of clauses for the predicates country and beside. In the listing MAP.PL, we have included geographical data for South America.
Exercise 8.5.1 Develop a listing similar to SAMERICA.PL called AFRICA.PL that provides geographical data on Africa to be used with MAP.PL. Run MAP.PL with AFRICA.PL to see the results. Exercise 8.5.2 Could you color a map of either South America or Africa using only three colors? Explain. How could you modify MAP.PL to get Prolog to answer this question for you? Exercise 8.5.3 Consider a three-dimensional assembly without any spaces inside such a Chinese puzzle cube. It can be proven that the parts of such an assembly can be painted in four different colors so that no two parts that come into contact with each other are the same color. How would modify map 0 to color such an assembly? How would you represent the assembly for the purposes of your program?
Authors manuscript
228
Chap. 8
color_map+Sofar,-Solution Searches for a legitimate assignment Solution of colors for regions on a map that includes the partial assignment Sofar. color_mapSofar,Solution :countryCountry, + member Country,_ ,Sofar, colorHue, + prohibitedCountry,Hue,Sofar, writeCountry,nl, color_map Country,Hue |Sofar ,Solution. color_mapSolution,Solution.
prohibited+Country,-Hue,+Sofar Country cannot be colored Hue if any region adjacent to Country is already assigned Hue in Sofar. prohibitedCountry,Hue,Sofar :bordersCountry,Neighbor, member Neighbor,Hue ,Sofar. borders+Country,-Neighbor Succeeds if Country and Neighbor share a border. bordersCountry,Neighbor :- besideCountry,Neighbor. bordersCountry,Neighbor :- besideNeighbor,Country. writeln . writeln X|Y :writeX, nl,
Authors manuscript
Sec. 8.6.
Examining Molecules
229
writelnY. Only four colors are ever needed colorred. colorblue. colorgreen. coloryellow. memberX, X|_ . memberX, _|Y :memberX,Y.
besideantilles,venezuela. besideargentina,brazil. besideargentina,paraguay. besidebolivia,brazil. besidebolivia,paraguay. besidebrazil,columbia. besidebrazil,guyana. besidebrazil,peru. besidebrazil,uruguay. besidechile,peru. besidecolumbia,peru. besideecuador,peru. besideguyana,surinam.
Authors manuscript
230
Chap. 8
As we have seen, Prolog is a good tool for solving mazes. Solving a maze amounts to nding a subgraph within a graph. Prolog is just as good at nding other kinds of subgraphs as it is at solving mazes. Searching a molecule for particular substructures is a not very special case of nding subgraphs in a graph. So Prolog is a very good tool for what we might call molecular query, illustrated in the program CHEM.PL. Rather than use bare connectivity tables to represent the structure of a molecule, we will let the connectivity tables store other information about the molecule as well. In a molecular structure diagram, each node in the graph represents an atom of a particular element. So what we want is a labeled graph where each node is labeled by the name of an element. Consider the organic compound 3-chloro-toluene which has the structure shown in Figure 8.3. The molecule contains seven carbon atoms and
H1 H2 H3 C3 H5 C4 C6 H6 C1
Ch C2 C5 C7 H7 H4
Figure 8.3
seven hydrogen atoms, plus a single chlorine atom. A single line in the structural diagram indicates that the two atoms are connected by a single bond (one shared electron); a double line indicates a double bond (two shared electrons). (We use Ch for the chlorine atom instead of the usual Cl because Cl is so easy to confuse with the carbon atom C1.) We represent this molecule by recording information about each atom in clauses for the predicate atom_specs 3:
3CTOLUEN.PL A molecular structure example. atom_specsh1,hydrogen, atom_specsh2,hydrogen, atom_specsh3,hydrogen, atom_specsh4,hydrogen, atom_specsh5,hydrogen, atom_specsh6,hydrogen, atom_specsh7,hydrogen, c1 c3 c3 c5 c3 c6 c7 . . . . . . .
Authors manuscript
Sec. 8.6.
Examining Molecules
231
H H C H
Figure 8.4
atom_specsc1,carbon, c2,c4,h1 . atom_specsc2,carbon, c1,c5,ch . atom_specsc3,carbon, c4,h2,h3,h5 . atom_specsc4,carbon, c1,c3,c6 . atom_specsc5,carbon, c2,c7,h4 . atom_specsc6,carbon, c4,c7,h6 . atom_specsc7,carbon, c5,c6,h7 . atom_specsch,chlorine, c2 .
The rst argument for atom_specs is the name of an atom, the second is the element-type for the atom, and the third is a list of other atoms to which the rst atom is bonded (the connectivity information). Some information is represented in this list of clauses more than once. For example, we can infer from either
atom_specsc1,carbon, c2,c4,h1 .
or
atom_specsc2,carbon, c1,c5,ch .
that the two carbon atoms C1 and C2 are bonded to each other. This repetition makes it easier to write routines that nd substructures in the molecule. We do not explicitly record information about which of the chemical bonds in the molecule are single or double. With enough information about chemistry, Prolog should be able to deduce this. We dene predicates that tell us whether two atoms are bonded to each other and whether an atom is an atom of a certain element.
bondedA1,A2 :- atom_specsA1,_,Neighbors, memberA2,Neighbors. elementA1,Element :- atom_specsA1,Element,_.
Next we dene some molecular structure query predicates. We begin with a very easy one. A methyl group is a substructure with one carbon atom and three hydrogen atoms arranged as in Figure 8.4. We identify a methyl group by looking for its single carbon atom since that is the only atom in the group that can combine with other parts of the molecule.
Authors manuscript
232
Chap. 8
Figure 8.5
methylC :- elementC,carbon, bondedC,H1, elementH1,hydrogen, bondedC,H2, elementH2,hydrogen, H1 == H2, bondedC,H3, elementH3,hydrogen, H3 == H1, H3 == H2.
A more complicated structure is a six-membered ring of carbon atoms. We nd these with the following procedure.
six_membered_carbon_ring A1,A2,A3,A4,A5,A6 :elementA1,carbon, bondedA1,A2, elementA2,carbon, bondedA2,A3, A1 == A3, elementA3,carbon, bondedA3,A4, + memberA4, A1,A2,A3 , elementA4,carbon, bondedA4,A5, + memberA5, A1,A2,A3,A4 , elementA5,carbon, bondedA5,A6, elementA6,carbon, bondedA6,A1, + memberA6, A1,A2,A3,A4,A5 .
This is not a very efcient procedure, but it gives us correct answers and it is easy to understand. There are several tests to make sure the ring does not loop back on itself. We might eliminate or simplify some of these tests if we incorporate more chemistry into our procedure. For example, if we know that three-membered carbon rings are impossible, we could simplify the procedure by leaving out tests for combinations that could only occur if the procedure found a three-membered carbon ring. We might want to know if there is a methylated six-membered ring of carbon atoms within a molecule. To nd these for us, the predicate meth_carbon_ring 1 is dened using previously dened predicates.
meth_carbon_ring C|Ring :- six_membered_carbon_ringRing, memberA,Ring, bondedA,C, methylC.
The predicates methyl, six_membered_carbon_ring, and meth_carbon_ring will all succeed when provided the atom specications for 3-chloro-toluene. An example of a molecular query predicate that will fail for this molecule is the hydroxide predicate. A hydroxide group is a hydrogen atom and an oxygen atom bonded to each other as in Figure 8.5. We identify a hydroxide group by looking for its oxygen atom since this is the part of the structure that can bond to other parts of the molecule.
Authors manuscript
Sec. 8.6.
Examining Molecules
233
H C C C C H C H H C C H
H C H C C H
H C
C H
Figure 8.6
Each substructure predicate we dene can be used in denitions of other more complicated substructures. A library of molecular substructure predicates provides a powerful tool for reasoning about molecular structure and chemistry.
Exercise 8.6.1 The compound diphenyl has the structure shown in Figure 8.6. Number the atoms in the molecule and write a description of it in the atom_specs format. Do the following queries succeed for this molecule?
????methylX. six_membered_carbon_ringX. meth_carbon_ringX. hydroxideX.
Exercise 8.6.2 A nitro group is an oxygen atom and a nitrogen atom in the arrangement shown in Figure 8.7. Notice that the bond between the oxygen atom and the nitrogen atom in a nitro group is a double bond. Write a procedure called nitro to nd nitro groups in a molecule. Then write atom_specs for tri-nitro-toluene (TNT, Figure 8.8). Your nitro routine should nd the three nitro groups in the tri-nitro-toluene molecule. Next, test your nitro routine on hydroxylamine (Figure 8.9). Since there is only a single bond between the oxygen atom and the nitrogen atom in this molecule, your nitro routine should fail.
Authors manuscript
234
Chap. 8
Figure 8.7
O N H H C H C C N O C H C H C C N O
Figure 8.8
H H O N H
Figure 8.9
Authors manuscript
Sec. 8.6.
Examining Molecules
235
Procedures to find bonds and identify elements. bondedA1,A2 :atom_specsA1,_,Neighbors, memberA2,Neighbors. memberX, X|_ . memberX, _|Y :- memberX,Y. elementA1,Element :- atom_specsA1,Element,_. Procedures to identify molecular substructures. methylC :elementC,carbon, bondedC,H1, elementH1,hydrogen, bondedC,H2, elementH2,hydrogen, H1 bondedC,H3, elementH3,hydrogen, H3 H3 == H2.
== H2, == H1,
six_membered_carbon_ring A1,A2,A3,A4,A5,A6 :elementA1,carbon, bondedA1,A2, elementA2,carbon, bondedA2,A3, A1 == A3, elementA3,carbon, bondedA3,A4, + memberA4, A1,A2,A3 , elementA4,carbon, bondedA4,A5, + memberA5, A1,A2,A3,A4 , elementA5,carbon, bondedA5,A6, elementA6,carbon, bondedA6,A1, + memberA6, A1,A2,A3,A4,A5 . meth_carbon_ring C|Ring :six_membered_carbon_ringRing, memberA,Ring, bondedA,C, methylC. hydroxideO :- elementO,oxygen, bondedO,H, elementH,hydrogen. Demonstrations A Prolog representation of a molecule such at the one in 3CTOLUEN.PL must be in memory before these demonstrations are used. demo1 :write'Searching for a methyl group...', nl, methylX, write'Found one centered on atom: ',
Authors manuscript
236
demo1 :demo2 :-
Chap. 8
demo2 :-
Authors manuscript
Sec. 8.7.
237
Figure 8.10
Given our goal nding the shortest ight plan between any two cities there is a simple procedure that is guaranteed to give the right result. First, nd all noncircuitous routes between the starting city and the destination. Second, compare the total distance traveled for each ight plan and pick a plan with a minimal total distance traveled. The problem with this procedure is that nding one path between two cities is relatively easy; nding all paths is daunting. Suppose we had another procedure that searched our maze in some intelligent fashion. This procedure only nds one path, but it nds it fairly quickly and the path it nds is usually pretty short. Suppose the procedure usually takes about 10% as much time as the exhaustive search procedure we described in the last paragraph and suppose the path it nds is usually no more than about 10% longer than the path found using exhaustive search. If we have to nd new ight plans frequently, this second procedure sounds like a pretty good deal. So we have two procedures which we will call the exhaustive search procedure and the intelligent search procedure. Our original goal was to nd a shortest ight path between any two cities that Acme Airlines serves. The exhaustive search procedure is guaranteed to satisfy this goal. Thus, it represents an algorithmic solution to the problem. The intelligent search procedure, on the other hand, is not guaranteed to nd a shortest path although it usually nds a path of reasonable length. A procedure which usually produces an acceptable solution to a problem, although it is not guaranteed to nd a maximal solution in every case, provides a heuristic solution to the problem. In fact, a heuristic procedure for solving a problem may sometimes fail to nd any solution at all. There is some confusion in the way the term "algorithm" is used when talking about computer programs. In one sense, every syntactically correct program or
Authors manuscript
238
Chap. 8
procedure may be called an algorithm. Such a program should execute until it halts, or reaches some error condition, or exhausts some computational resource. In fact, you may run across the phrase "heuristic algorithm" in the literature. This is not the sense in which we are using the term "algorithm", and we will always contrast the term "algorithm" with the term "heuristic". As we are using these terms, the same procedure may be called either an algorithm or a heuristic depending on the goal we are trying to reach when we use the procedure. If the procedure is guaranteed to solve the problem, then it is an algorithm relative to that problem. If the procedure is not guaranteed to solve the problem but usually produces an acceptable approximation of a solution, then it is a heuristic relative to that problem. So a procedure could be an algorithm relative to one goal and a heuristic relative to another, but the way we are using these terms, the same procedure could not be both an algorithm and a heuristic relative to the same goal. Getting back to our airline example, lets get some more information about the cities Acme serves. In the listing ACME.PL (Fig. 8.11), we use the four-place predicate data to record this information. The rst two arguments of a data clause represent two cities Acme serves, the third argument gives the distance between these two cities, and the fourth argument tells us whether Acme has direct ights between these two cities. Please note that the distances given are taken from road atlases and represent driving distances rather than distances by air. But it is not important for our example that these distances be precise. This is all the information we will need to develop our exhaustive search and intelligent search procedures for nding ight plans for Acme Airlines. We begin by dening a procedure that will nd a path from our starting city to our destination without regard for total distance, but we will compute the total distance as part of the path. The predicate plan will take three arguments: the starting city, the destination, and the path.
planStart,Goal,Plan :- plannerGoal, 0,Start ,Path, reversePath, ,Plan.
A path from one city to another will be a list whose rst member is the total length of the path. The remaining members of the list will be a sequence of cities such that adjacent members of the list are connected by Acme ights. The initial list contains only 0 (since our initial path is a single city and has no length) and the name of the starting city. The auxiliary procedure planner looks for a city that can be reached from the starting city, adds it and the distance to it to the path, and recurses until it nally reaches the destination. planner constructs the path in reverse order and plan then reverses it to put it into the proper order.
plannerGoal, Miles,Goal|Cities , Miles,Goal|Cities :- !. plannerGoal, OldMiles,Current|Cities ,Path :connectedCurrent,Next,MoreMiles, + memberNext,Cities, NewMiles is OldMiles + MoreMiles, plannerGoal, NewMiles,Next,Current|Cities ,Path. connectedCity1,City2,Miles :- dataCity1,City2,Miles,y.
Authors manuscript
Sec. 8.7.
239
ACME.PL Travel data for Acme Airlines. The first two arguments in each clause list a pair of cities served by Acme, the third argument gives the distance between these cities, and the fourth tells whether Acme has direct flights between these cities. dataatlanta,boston,1108,n. dataatlanta,chicago,715,y. dataatlanta,dallas,808,n. dataatlanta,denver,1519,y. dataatlanta,los_angeles,2091,n. dataatlanta,miami,862,y. dataatlanta,new_orleans,480,y. dataatlanta,seattle,2954,n. dataatlanta,washington,618,y. databoston,chicago,976,n. databoston,dallas,1868,n. databoston,denver,2008,n. databoston,los_angeles,3017,n. databoston,miami,1547,n. databoston,new_orleans,1507,n. databoston,seattle,3163,n. databoston,washington,448,y. datachicago,dallas,936,n. datachicago,denver,1017,y. datachicago,los_angeles,2189,n. datachicago,miami,1340,n. datachicago,new_orleans,938,n. datachicago,seattle,2184,y. datachicago,washington,696,y. datadallas,denver,797,n. datadallas,los_angeles,1431,n. datadallas,miami,1394,n. datadallas,new_orleans,495,y. datadallas,seattle,2222,n. datadallas,washington,1414,y. datadenver,los_angeles,1189,y. datadenver,miami,2126,n. datadenver,new_orleans,1292,n. datadenver,seattle,1326,y. datadenver,washington,1707,n. datalos_angeles,miami,2885,n. datalos_angeles,new_orleans,1947,n. datalos_angeles,seattle,1193,y. datalos_angeles,washington,2754,n. datamiami,new_orleans,881,y. datamiami,seattle,3469,n. datamiami,washington,1096,n. datanew_orleans,seattle,2731,n. datanew_orleans,washington,1099,n. dataseattle,washington,2880,n.
Figure 8.11
Listing of ACME.PL
Authors manuscript
240
Chap. 8
connectedCity1,City2,Miles :- dataCity2,City1,Miles,y.
We put a cut in the rst clause for planner to prevent the procedure from trying to extend the path beyond the destination upon backtracking. This will be important later when we use planner in conjunction with findall. Notice that the only constraint on planner is that the same city cant be visited twice. But planner, and therefore plan, might come up with the kind of path we considered above: one where we visit every city Acme serves before arriving at our destination. The rst path plan nds is a function only of the order in which we list our travel data. We will use planner in dening an algorithm for nding a shortest path between any two cities.
best_planStart,Goal, Min|Plan :setofPath,plannerGoal, 0,Start ,Path, reverseBest, ,Plan. Min|Best |_ ,
In the denition of best_plan 3, the call to setof uses planner to construct a list of all possible paths between the starting city and the destination. setof sorts these paths using their rst element which is their length. The rst (shortest) member of the list of paths is pulled out and reversed to produce a shortest ight plan. Our heuristic approach constructs a single path but uses information about the distances between cities to guide path construction. The top-level predicate, good_plan, looks very much like plan but it calls the auxiliary predicate smart_planner instead of planner.
good_planStart,Goal, Miles|Plan :smart_plannerGoal, 0,Start , Miles|Good , reverseGood, ,Plan. smart_plannerGoal, Miles,Goal|Cities , Miles,Goal|Cities . smart_plannerGoal, OldMiles,Current|Cities ,Plan :setofoptionMiles,City, X^connectedCurrent,City,X, distanceCity,Goal,Miles, List, memberoptionMin,Next,List, connectedCurrent,Next,MoreMiles, + memberNext,Cities, NewMiles is OldMiles + MoreMiles, smart_plannerGoal, NewMiles,Next,Current|Cities ,Plan. distanceCity,City,0. distanceCity1,City2,Miles :- dataCity1,City2,Miles,_. distanceCity1,City2,Miles :- dataCity2,City1,Miles,_.
The rst clauses for smart_planner and planner are identical except that we dont need a cut for smart_planner because we never intend to backtrack into it. Both procedures stop when the goal city has been added to the path. But at each step in the construction, smart_planner is much pickier about the next city to be added
Authors manuscript
Sec. 8.7.
241
to the path. The rst thing smart_planner does at each step is to use connected in a call to findall to generate a list of cities that can be reached from the last city added to the path. Then distance is used in a call to setof to generate a sorted list of the distances from these cities to the destination smart_planner is trying to reach. The rst member of this list is then used to pick the next city to place in the path. In this way, smart_planner always picks the next city that can be reached that is closest to the destination. If smart_planner goes down a blind alley and reaches a city from which no city can be reached that is not already in the path, it backtracks to a point where it can pick a city that is next-closest to the destination. Because smart_planner can backtrack, it will always nd a path from any city in the network to any other. But because it isnt guaranteed to nd a shortest path, smart_planner is a heuristic rather than an algorithm relative to the goal of nding a shortest path. If we replaced the two clauses
setofoptionMiles,City, X^connectedCurrent,City,X, distanceCity,Goal,Miles, List, memberoptionMin,Next,List,
in the second clause for the predicate smart_planner with the single clause
setofoptionMiles,City, X^connectedCurrent,City,X, distanceCity,Goal,Miles, optionMin,Next|_ ,
then smart_planner would be unable to backtrack from dead-ends and in some cases it might not be able to nd an existing path between two cities at all. This modication of smart_planner an example of the hill-climbing heuristic. This heuristic gets its name from the search problem with which it is commonly associated: nding the highest point in a piece of terrain. The strategy is always to go uphill. The problem, of course, is that you could nd yourself at the top of a hill that is not the highest hill in the region. Since you wouldnt be able to go directly to a higher point from that location, you would be stuck. This is sometimes called the problem of the local maxima (or the local minima for tasks like minimizing the energy of a system.) Seeking a global maxima, your search trategy has stranded you at a local maxima. In our example, higher points are cities closer to the destination, and the destination is the highest point (the global maxima.) If you reach a city from which you cannot go to a city nearer your destination, you have reached a local maxima. But smart_planner can retreat from local maxima and explore other paths. Still, it always moves to the highest untried location (the city next nearest the destination.) This strategy is called best-rst search. Each time setof is called, a stack of possible next cities is generated and ordered by a criterion of goodness (in this case, nearness to the destination.) Then each option in the stack is tried best rst. From the ten cities served by Acme Airlines we can choose 90 different combinations of starting cities and destination cities. good_plan nds the shortest path for 73 of these 90 pairs. In the worst case, good_plan nds a plan from Washington to
Authors manuscript
242
Chap. 8
Denver that is 2.28 times as long as the shortest plan. For all pairs of cities, the plan found by good_plan is on average 1.11 times as long as the shortest plan. For jsut the 17 pairs where good_plan does not produce the shortest path, the path it produces is on average 1.56 times as long as the shortest path. For starting and destination cities connected by direct ights, best_plan invariably takes less time to compute a ight plan than good_plan. The reason for this is obvious: smart_planner always takes time to look at the distance from a candidate next city to the destination even when they are one and the same. As is typical of a heuristic, it is less efcient in the easiest cases. We could eliminate this advantage by making the following clause the rst clause in the denition of good_plan:
good_planStart,Goal, Miles,Start,Goal :connectedStart,Goal,Miles.
Of course, we could add a similar clause to best_plan; then the two procedures would perform with equal speed on these cases. By adding these clauses, we increase the amount of time each procedure will require for other cases by a constant amount. Since either routine will nd a ight plan quickly in these easiest cases, we shouldnt be too concerned about improving their performance here. good_plan doesnt always give us a shortest plan, but it is supposed to nd a good plan faster than best_plan. How efcient is good_plan relative to best_plan? We compared the time it took for good_plan to nd paths between all 90 city pairs with the time best_plan took for the same task. good_plan took only 1.70 seconds while best_plan took 11.64 seconds. So best_plan took 6.8 times as long as good_plan. Of course, these timing gures will vary depending on your hardware and the Prolog implementation you use. But you should expect good_plan to be at least ve times as fast as best_plan on average. These gures give a clear example of the kind of trade off we get when comparing algorithmic and heuristic approaches to the same problem. With an algorithm, we are guaranteed a best solution. The solutions we get with a heuristic may not always be maximal, but with a good heuristic we usually get quite good solutions much faster than we do with the algorithm. Of course, if no algorithm is known for a problem we have no choice but to rely on a heuristic.
Exercise 8.7.1 In the text, we describe a circuitous route from Boston to Chicago, one that stops at every city Acme serves. Arrange the Acme travel data so plan initially nds this route. Does the order of the clauses for data affect the ight plans that best_plan and good_plan nd? Why or why not? Exercise 8.7.2 Shortest total distance traveled is not the only consideration in making airline reservations. What other criteria might we use for comparing alternative ight paths? Pick one of these and modify best_plan and good_plan to search for best and good ight plans using this alternative criterion. Exercise 8.7.3 Dene a predicate discrepancies of one argument that returns a list of all pairs of starting and destination cities such that best_plan and good_plan nd different ight
Authors manuscript
Sec. 8.7.
243
plans for these pairs. Of course, you may dene whatever auxiliaries for discrepancies you require. Conrm our results that best_plan and good_plan produce the same ight plan in all but 17 of the 90 possible cases for Acme Airline. Exercise 8.7.4 Acme Airlines has just announced ights between Atlanta and San Francisco. Add the following clauses to ACME.PL:
dataatlanta,san_francisco,2554,y. databoston,san_francisco,3163,n. datachicago,san_francisco,2233,n. datadallas,san_francisco,1791,n. datadenver,san_francisco,1267,n. datalos_angeles,san_francisco,377,n. datamiami,san_francisco,3238,n. datanew_orleans,san_francisco,2300,n. datasan_francisco,seattle,786,n. datasan_francisco,washington,2897,n.
Compare some ight plans between San Francisco and some other cities Acme serves generated by good_plan and best_plan. Describe and explain what you observe. Exercise 8.7.5 Project: Here is an informal description of another algorithm for nding the shortest ight plan for any two cities. Generate an initial plan. Then begin generation of another plan. As you generate the second plan, check at each step to make sure that it is not longer than the plan you already have. If it is, backtrack and try again. If you generate a shorter plan, replace your original plan with the new, shorter plan and keep going. Stop when you have tried every plan. The plan you have at the end of this process will be a shortest plan. Dene a predicate smartest_plan that implements this algorithm. Compare its efciency with that of best_plan.
Authors manuscript
244
Chap. 8
plannerGoal, Miles,Goal|Cities , Miles,Goal|Cities :- !. plannerGoal, OldMiles,Current|Cities ,Path :connectedCurrent,Next,MoreMiles, + memberNext,Cities, NewMiles is OldMiles + MoreMiles, plannerGoal, NewMiles,Next,Current|Cities ,Path. best_plan+Start,+Goal,-Plan An algorithm for finding the shortest flight plan linking two cities. best_planStart,Goal, Min|Plan :findallPath,plannerGoal, 0,Start ,Path,PathList, setofMiles,member Miles|_ ,PathList, Min|_ , member Min|Best ,PathList, reverseBest, ,Plan. good_plan+Start,+Goal,-Plan A heuristic for quickly finding a flight plan of reasonable length linking two cities. good_planStart,Goal, Miles|Plan :smart_plannerGoal, 0,Start , Miles|Good , reverseGood, ,Plan. smart_planner+Goal,+PartialPlan,-CompletePlan Takes a partial flight plan and completes it all the way to the destination city, giving priority at each step to those cities which can be reached from the current city that are nearest the destination city. smart_plannerGoal, Miles,Goal|Cities , Miles,Goal|Cities . smart_plannerGoal, OldMiles,Current|Cities ,Plan :findallCity,connectedCity,Current,_,CityList, setofMiles,distanceGoal,CityList,Miles,MilesList, memberMin,MilesList, distanceNext, Goal ,Min, connectedCurrent,Next,MoreMiles, + memberNext,Cities, NewMiles is OldMiles + MoreMiles, smart_plannerGoal, NewMiles,Next,Current|Cities ,Plan.
Authors manuscript
Sec. 8.8.
Scheduling
245
Predicates for interpreting information about the travel data. connectedCity1,City2,Miles :- dataCity1,City2,Miles,y. connectedCity1,City2,Miles :- dataCity2,City1,Miles,y. distanceCity, City|_ ,0. distanceCity1, City2|_ ,Miles :- dataCity1,City2,Miles,_. distanceCity1, City2|_ ,Miles :- dataCity2,City1,Miles,_. distanceCity1, _|RestOfCities ,Miles :- distanceCity1,RestOfCities,Miles. memberX, X|_ . memberX, _|Y :- memberX,Y. reverse ,List,List. reverse X|Tail ,SoFar,List :reverseTail, X|SoFar ,List.
8.8. SCHEDULING
In the last section, we ignored the rather crucial fact that Acme Airlines ights were scheduled for particular times. We acted as if the next ight we needed was available as soon as we stepped off the ight before it. This may not be the case. Suppose, for example that there are ights every hour between Miami and Atlanta and between Atlanta and New Orleans, but there are only weekend ights from Miami to New Orleans. Then if today is Monday, we are in Miami, and we have to get to New Orleans today, we will go through Atlanta. It is no help at all that there is a direct ight available on Saturday. Of course, we expect Acme Airlines to schedule its ights so we can make connections easily and y whenever and wherever we need. Acme, on the other hand, wants to make sure that it is not ying empty airplanes around the country. The problem of developing ight schedules for airlines is overwhelming, requiring enormous computational resources. But even relatively simple scheduling problems can be demanding. In this section we will look at a simple scheduling problem and at how Prolog can be used to solve it. The example we will use is fairly typical. Suppose we need to schedule workers for a small departmental library at a university. The library is only open from 8:00 am until noon and from 1:00 pm until 5:00 pm Mondays through Fridays. We begin by dividing the work week into ten shifts, a morning shift and an afternoon shift each day. On each shift, we need two workers to shelve books and one worker to operate the check-out desk. We need an additional worker every morning shift to catalog items that arrive in the morning mail. So we have a total of 35 slots to ll where each kind of slot is represented by a shift and a job type. We will represent the seven Monday slots with the following clauses:
slotsmon,am,cataloger,1. slotsmon,am,deskclerk,1.
Authors manuscript
246
Chap. 8
Besides this information, we also need to know what workers are available, when they can work, and what kinds of jobs they can do. We will record information about workers in clauses of a ve-place predicate personnel where the rst argument is the workers name, the second is the minimum number of shifts the worker wants to work each week, the third is the maximum number of shifts the worker wants to work each week, the fourth is the maximum number of shifts the worker is willing to work in a single day, and the fth is a list of days that the worker is available. (To simplify our task, we will assume that if a worker is willing to work at all on a particular day of the week, then he or she is willing to work either shift.) We will use a binary predicate job to record which workers are qualied for each job. Our three workers are Alice, Bob, Carol, Don, Ellen, and Fred. Here is the information for them:
personnelalice,6,8,2, mon,tue,thu,fri . personnelbob,7,10,2, mon,tue,wed,thu,fri . personnelcarol,3,5,1, mon,tue,wed,thu,fri . personneldon,6,8,2, mon,tue,wed . personnelellen,0,2,1, thu,fri . personnelfred,7,10,2, mon,tue,wed,thu,fri . jobcataloger, alice,fred . jobdeskclerk, bob,carol,fred . jobshelver, alice,bob,carol,don,ellen,fred .
A weekly schedule will be a list of assignments of workers to all the slots for the week. The list will be constructed recursively in much the same way that solutions to puzzles and problems we have looked at earlier in this chapter have been constructed. At each step, we look for a worker who satises the requirements for some empty slot and assign him or her to that slot. If no one is available for a slot, we backup and try different combinations in our earlier assignments until someone becomes available. When the schedule is complete, we display it. The top-level predicate in our program, schedule, is dened as follows:
schedule :findallslotsDay,Time,Job,Number, slotsDay,Time,Job,Number,Slots, schedule_auxSlots, ,Schedule, write_reportSchedule. schedule rst calls findall to generate a list of the slots that need to be lled. These are then passed to the recursive procedure schedule_aux which actually generates
Authors manuscript
Sec. 8.8.
Scheduling
247
schedule_aux slots_,_,_,0|RestOfSlots ,Partial,Schedule :schedule_auxRestOfSlots,Partial,Schedule. schedule_aux slotsDay,Time,Job,Number|RestOfSlots ,Partial,Schedule :Number 0, availableDay,Time,Job,Person,Partial, write'Trying: ', reportDay,Time,Job,Person, NewNumber is Number - 1, schedule_aux slotsDay,Time,Job,NewNumber|RestOfSlots , schedDay,Time,Job,Person|Partial ,Schedule. reportDay,Time,Job,Person :- writeDay,write' ', writeTime,write' ', writeJob,write' ', writePerson,nl.
As we would expect, the schedule is complete when the list of slots remaining to be lled is empty. This is what the rst clause does. The second clause handles the case where we have lled all the slots of a particular kind. The third clause checks to be sure that there are slots of the kind under consideration to be lled, nds someone to assign to one of these slots, reduces the number of this kind of slot to be lled, and recurses. We have added two lines in the middle to give the user something to watch as the program runs. We will make further use of the procedure report in dening write_report later. The predicate available denes when a worker lls the requirements for a particular assignment. First, the worker must be competent to perform the job. Second, the worker cannot already have an assignment for the time slot. Third, the worker must be available on the appropriate day. Fourth, the worker must not have already been assigned the maximum number of shifts that he or she is willing to work that day. Finally, we must take into account the number of shifts the worker has worked during the week. We begin with a clause that denes a worker as available if he or she has not yet been assigned to the minimum number of shifts desired for the week.
availableDay,Time,Job,Person,Partial :jobJob,JobList, memberPerson,JobList, + memberschedDay,Time,_,Person,Partial, personnelPerson,MinWeekly,_,Daily,Days, memberDay,Days, findallT,memberschedDay,T,J,Person,Partial,DList, lengthDList,D, D Daily, findallT,memberschedD,T,J,Person,Partial,WList, lengthWList,W, W MinWeekly.
If the rst clause fails, meaning all workers who are otherwise suitable have already
Authors manuscript
248
Chap. 8
been assigned the minimum number of shifts they want, then we loosen the requirements in the second clause and consider all workers who have not yet been assigned the maximum number of shifts they can work in a week.
availableDay,Time,Job,Person,Partial :jobJob,JobList, memberPerson,JobList, + memberschedDay,Time,_,Person,Partial, personnelPerson,_,MaxWeekly,Daily,Days, memberDay,Days, findallT,memberschedDay,T,J,Person,Partial,DList, lengthDList,D, D Daily, findallT,memberschedD,T,J,Person,Partial,WList, lengthWList,W, W MaxWeekly.
The remainder of the program consists of routines for reporting the schedule once it is found. These display information about the schedule on the screen, but they could easily be modied to send the output to a le or to a printer. We put a cut at the end of write_report so the user can force backtracking to schedule_aux without backing into the middle of write_report. The program is a heuristic for the assigned scheduling task since it is not guaranteed to satisfy all our intended constraints on a schedule. In fact, if you run the program you will nd that it does not assign every worker the minimal number of shifts specied. Furthermore, if the list of clauses for the predicate slots is reordered, the program may take a very long time to nd a schedule at all. We have taken advantage of another informal heuristic in ordering these clauses, putting the job types for which the fewest workers are qualied at the top of the list for each day. If this had not produced schedules fairly quickly, we could have gone a step further and placed all the clauses for these "bottleneck" jobs rst without regard to the days of the week. Assigning workers their minimal numbers of shifts is a more difcult problem. One way to handle a requirement like assigning each worker at least the minimal number of shifts indicated is by introducing a global constraint that a complete schedule must satisfy.
schedule :findallslotsDay,Time,Job,Number, slotsDay,Time,Job,Number,Slots, schedule_auxSlots, ,Schedule, + violate_global_constraintSchedule, write_reportSchedule. violate_global_constraintSchedule :personnelPerson,MinWeekly,_,_,_, findallT,memberschedD,T,J,Person,Schedule,List, lengthList,L,
Authors manuscript
Sec. 8.8.
L
Scheduling
MinWeekly.
249
The resulting program is an algorithm: if there exists a schedule that satises all our requirements, this program is guaranteed to nd it. However, it is not a very efcient algorithm. The very rst assignment made could render a schedule impossible. This algorithm will try all possible combinations of later assignments before it will backtrack far enough to revise that original assignment. It is surprisingly difcult to nd an algorithm even for this relatively simple scheduling problem that is signicantly more efcient or to nd a heuristic that routinely produces better schedules than the program we have looked at here. One thing that makes this so surprising is that we can look at the schedule produced by our program and see fairly quickly how to modify it to produce a schedule where every worker gets the minimum number of shifts. This suggests a third approach: use schedule_aux to generate an initial schedule and develop another program that corrects the faults in this schedule to produce a nal schedule.
schedule :findallslotsDay,Time,Job,Number, slotsDay,Time,Job,Number,Slots, schedule_auxSlots, ,IntermediateSchedule, improve_scheduleIntermediateSchedule,Schedule, write_reportSchedule.
Given a nearly-correct schedule, how can we dene an improvement on it? This would be a schedule where a shift assigned in the original schedule to a worker who has more than the minimum number of shifts is reassigned to a worker who does not have the minimum number of shifts.
improve_scheduleSchedule,Schedule :+ needs_shifts_,Schedule. improve_scheduleCurrent,Schedule :needs_shiftsPerson1,Current, has_extra_shiftPerson2,Current, personnelPerson1,_,_,Daily,Days, memberschedDay,Time,Job,Person2,Current, memberDay,Days, + memberschedDay,Time,_,Person1,Current, jobJob,JobList, memberPerson1,Joblist, findallT,memberschedDay,T,J,Person1,Current,DList, lengthDList,D, D Daily, !, write'Rescheduling: ', reportDay,Time,Job,Person2, removeschedDay,Time,Job,Person2,Current,Partial, improve_schedule schedDay,Time,Job,Person1|Partial ,Schedule. improve_scheduleSchedule,Schedule.
Authors manuscript
250
Chap. 8
needs_shiftPerson,Schedule :personnelPerson,MinWeekly,_,_,_, findallT,memberschedD,T,J,Person,Schedule,Shifts, lengthShifts,S, S MinWeekly. has_extra_shiftPerson,Schedule :personnelPerson,MinWeekly,_,_,_, findallT,memberschedD,T,J,Person,Schedule,Shifts, lengthShifts,S, S MinWeekly. removeX, X|Y ,Y :- !. removeX, Y|Z , Y|W :- removeX,Z,W.
The rst clause for improve_schedule denes a schedule as improved if every worker already has at least his or her minimum number of shifts. The second clause denes a schedule as improved if a shift is reassigned from a worker with extra shifts to one who does not yet have his or her minimum number of shifts. This clause is recursive, and the program will continue to improve a schedule until it cannot nd an acceptable reassignment. If this happens when there are still workers without their minimum numbers of shifts, then the rst two clauses will fail and the third clause returns whatever schedule has been produced. After the report has been presented, the user can force failure and the program will backtrack and produce revisions of the original schedule. Even with improve_schedule added, our scheduling program is a heuristic and not an algorithm for nding schedules. Suppose, for example, that we have three workers available for a particular job: George who can work any day, Henrietta who can only work on Monday, Wednesday, or Friday, and Inez who can only work on Tuesday or Thursday. schedule_aux might produce a schedule where George is scheduled for exactly his minimum number of shifts, Henrietta has extra shifts, and Inez has not been assigned her minimum number of shifts. Since George has no extra shifts and since Henrietta and Inez dont work on the same days, there is no way for improve_schedule to make a change. A scheduling problem involves assigning resources under a set of constraints. Since all kinds of constraints are possible, it is impossible to design a general scheduling program that ts every situation. We have seen that applying global constraints to completed schedules, thus forcing backtracking when these constraints are violated, will produce a scheduling algorithm. But such an algorithm is usually inefcient. A better strategy is to look for ways to design an intelligent search mechanism that takes constraints into account at each step of schedule construction. In our example, the predicate available and particularly the rst clause of the predicate available does this. Even using this approach, we may be unable to produce perfect schedules in reasonable time and we may be forced to accept a heuristic that generates schedules that usually come close to satisfying all of our constraints. We have seen how a second heuristic that takes a global view of a schedule produced by the rst
Authors manuscript
Sec. 8.8.
Scheduling
251
heuristic may produce improvements on a schedule faster than backtracking into the original heuristic. Arranging the data available to a scheduler so that it solves more difcult scheduling problems early while the schedule is still relatively uid is another strategy that we used when we placed slots clauses for jobs that fewer workers can perform ahead of clauses for jobs that many workers can perform. This might be automated by writing programs that analyze the data before the scheduler is run and rearrange it to make the schedulers job easier.
Authors manuscript
252
Chap. 8
availableDay,Time,Job,Person,Partial :jobJob,JobList, memberPerson,JobList, + memberschedDay,Time,_,Person,Partial, personnelPerson,_,MaxWeekly,Daily,Days, memberDay,Days, findallT,memberschedDay,T,J,Person,Partial,DList, lengthDList,D, D Daily, findallT,memberschedD,T,J,Person,Partial,WList, lengthWList,W, W MaxWeekly. improve+Current,-Better replaces the Current schedule with a Better schedule by reassigning job slots occupied by persons with extra shifts to persons who need additional shifts. improve_scheduleSchedule,Schedule :+ needs_shifts_,Schedule. improve_scheduleCurrent,Schedule :needs_shiftsPerson1,Current, has_extra_shiftPerson2,Current, personnelPerson1,_,_,Daily,Days, memberschedDay,Time,Job,Person2,Current, memberDay,Days, + memberschedDay,Time,_,Person1,Current, jobJob,JobList, memberPerson1,JobList, findallT,memberschedDay,T,J,Person1,Current,DList, lengthDList,D, D Daily, !, write'Rescheduling: ', reportDay,Time,Job,Person2, removeschedDay,Time,Job,Person2,Current,Partial, improve_schedule schedDay,Time,Job,Person1|Partial ,Schedule. improve_scheduleSchedule,Schedule. Procedures for finding persons who have fewer or more shifts than requested in a schedule.
Authors manuscript
Sec. 8.8.
Scheduling
253
needs_shiftPerson,Schedule :personnelPerson,MinWeekly,_,_,_, findallT,memberschedD,T,J,Person,Schedule,Shifts, lengthShifts,S, S MinWeekly. has_extra_shiftPerson,Schedule :personnelPerson,MinWeekly,_,_,_, findallT,memberschedD,T,J,Person,Schedule,Shifts, lengthShifts,S, S MinWeekly. removeX, X|Y ,Y :- !. removeX, Y|Z , Y|W :- removeX,Z,W. memberX, X|_ . memberX, _|Y :- memberX,Y. Procedures for displaying schedules. write_reportSchedule :nl, nl, write'Schedule for the Week: ', nl, nl, memberDay, mon,tue,wed,thu,fri , findallschedDay,Time,Job,Person, memberschedDay,Time,Job,Person,Schedule, DaySchedule, report_list1DaySchedule, nl, write'Press key to continue. ', get0_, nl, nl, Day = fri, findallpersonPerson,Min,Max, personnelPerson,Min,Max,_,_,PersonList, report_list2PersonList,Schedule, !. report_list1 . report_list1 schedDay,Time,Job,Person|RestOfSchedule :reportDay,Time,Job,Person, report_list1RestOfSchedule. reportDay,Time,Job,Person :writeDay, write' ', writeTime, write' ', writeJob, write' ', writePerson, nl. report_list2 ,_ :- write'Report finished.', nl, nl. report_list2 personPerson,Min,Max|Rest ,Schedule :writePerson, write'''s schedule ', writeMin, write' to ',
Authors manuscript
254
Chap. 8
writeMax, write' shifts per week:', nl, nl, memberDay, mon,tue,wed,thu,fri , findallschedDay,Time,Job,Person, memberschedDay,Time,Job,Person,Schedule,DaySchedule, report_list2_auxDaySchedule, Day = fri, nl, write'Press key to continue. ', get0_, nl, nl, report_list2Rest,Schedule. report_list2_aux . report_list2_aux schedDay,Time,Job,_|Rest :reportDay,Time,Job,'', report_list2_auxRest. Sample scheduling data. slotsmon,am,cataloger,1. slotsmon,am,deskclerk,1. slotsmon,pm,deskclerk,1. slotsmon,am,shelver,2. slotsmon,pm,shelver,2. slotstue,am,cataloger,1. slotstue,am,deskclerk,1. slotstue,pm,deskclerk,1. slotstue,am,shelver,2. slotstue,pm,shelver,2. slotswed,am,cataloger,1. slotswed,am,deskclerk,1. slotswed,pm,deskclerk,1. slotswed,am,shelver,2. slotswed,pm,shelver,2. slotsthu,am,cataloger,1. slotsthu,am,deskclerk,1. slotsthu,pm,deskclerk,1. slotsthu,am,shelver,2. slotsthu,pm,shelver,2. slotsfri,am,cataloger,1. slotsfri,am,deskclerk,1. slotsfri,pm,deskclerk,1. slotsfri,am,shelver,2. slotsfri,pm,shelver,2. personnelalice,6,8,2, mon,tue,thu,fri . personnelbob,7,10,2, mon,tue,wed,thu,fri . personnelcarol,3,5,1, mon,tue,wed,thu,fri . personneldon,6,8,2, mon,tue,wed . personnelellen,0,2,1, thu,fri . personnelfred,7,10,2, mon,tue,wed,thu,fri .
Authors manuscript
Sec. 8.9.
255
Authors manuscript
256
Chap. 8
Now suppose the lamp is not on. Then the robot needs to ip the switch. Treating this as a new goal, we get a second rule: Rule 2: if
gturn_onlamp and fstatuslamp,off
are in the database, then add gflipswitch to the database. Finally, we need two rules that actually cause the robot to perform some physical action. Rule 3: if
gflipswitch and fstatuslamp,on
are in the database, then ip the switch and remove gflipswitch from the database and remove fstatuslamp,on from the database and add fstatuslamp,off to the database. Rule 4: if
gflipswitch and factstatuslamp,off
are in the database, then ip the switch and remove gflipswitch from the database and remove fstatuslamp,off from the database and add fstatuslamp,on to the database. Lets see what happens when the robot is given these rules and a database containing gturn_onlamp and fstatuslamp,on. Only the condition for Rule 1 is satised. So Rule 1 is red and gturn_onlamp is removed from the database. Now the inference engine looks for another rule to re. Every rule has a goal in its condition, but there are no goals in the database. So the inference engine stops. Now suppose the database contains goturn_onlamp and fstatuslamp,off. Only the condition for Rule 2 is staised; so it res and gflipswitch is added to the database. Now there are two rules, Rule 2 and Rule 4, whose conditions are sitised. Which shall the inference engine re? Of course, we want the inference engien to re Rule 4, but it may not. We call the set of rules whose conditions are satised at any given time the current conict set. When the conict set is empty, the inference engine stops. When the conict set has only one rule in it, that rule is red. When the conict set contains
Authors manuscript
Sec. 8.10.
257
several rules, complex procedures may be necessary to decide which rule to re. This decision can be very important. In our example, if we re Rule 2 every time we can, we will be caught in a loop where we keep adding new instances of gflipswitch to the database and never actually ips the switch. When the forward-chaining inference engine selects some rule from the conict set (using whatever principles it may), we say that it has resolved the conict set. For our example, we will adopt the simple principle that the inference engine res the rst rule it nds whose condition is satised. This doesnt solve the looping problem we noticed. To do this, we must also specify the every rule should change the database so its own condition is no longer satised. This will stop loops of the kind we found in our example. In this case, we can eliminate the loop by adding a condition rather than an action to Rule 2: Rule 2: if
gturn_onlamp
is not in the database, then add gflipswitch to the database. Now after Rule 2 is red, only the condition for Rule 4 is satised. After Rule 4 res, the database contains only gturn_onlamp and fstatuslamp,on. Rule 1 res, and gturn_onlamp is removed from the database. Then there is no rule whose condition is satised and the robot (inference engine) stops. Three rules were red: Rule 2, Rule 4, and Rule 1. The problem with Rule 2 also points out a more general principle. All of the rules in a production system should operate independently of their order in the system. Exactly the same rules should re in the same order no matter how the rules are arranged in the knowledge base. A forward-chaining inference engine that determines the entire conict set and decides which of these rules to re guarantees this. If the rst rule that can be red always res, the user must guarantee that this is the only rule that can re under the circumstances. This means that the rules shoudl be written so a situation cannot arise where the conditions for two or more rules are satised. If such a situation did arise, then changing the order of the rules would cause different rules to re. In effect, writing the rules so they are independent in this way insures that the conict set never has more than one member.
Authors manuscript
258
Chap. 8
forward_chainer :- findallX,satisfiedX,ConflictSet, resolve_setConflictSet,ID, ruleID, write'Fired rule: ', writeID, write'.', nl, !, forward_chainer.
Of course, this is only the top-level of the inference engine. We must still dene satisfied 1 and resolve_set 2. The rst is rather easy. The second, of course, will depend on the particular conict resolution principles we decide to use. What makes the forward chainer so simple is the way we write our forwardchaining rules in Prolog. In backward-chaining, we look at the conclusion (the head) of the rule rst and then look at the conditions (the body). As we saw in the last section, forward-chaining rules are usually written in the opposite order: conditions, then actions. We will be able to use a translation method that rewrites our production rules so the Prolog inference engine does most of the work for us. In a sense, the rules will satisfy and re themselves. Before we look at the way we will rewrite production rules in Prolog, lets dene a few predicates that will make our rules easier to write. These predicates are part of the inference engine rather than part of any particular production rule set. Predicates af 1 and ag 1 add facts and goals to a database, and predicates rf 1 and rg 1 remove facts and goals. The predicate then does nothing and always succeeds. It serves only to separate the condition from the action in a production rule. The simple denitions for these predicates are found at the end of the listing for FCHAIN.PL.
We want to write our rules so that Prolog does the least work possible. This means that the head, or conclusion, of the Prolog rule will be the identier, not the action. Both the condition and the action will be subgoals. Every Prolog production rule will have a head of the form ruleID, where ID is a number, phrase, or other identier for the rule. This lets Prolog pick out the production rules stored in memory very quickly. In the body of the rule, we will list the condition rst and then the action. Because Prolog is trying subgoals in sequence one by one, the action will only be reached if the condition is succeeds. The action should be a subgoal that always succeeds whenever the condition succeeds. We will separate the condition and the action with the special predicate then both to make the rule easier to read and to enable Prolog to distinguish conditions from goals in computing the conict set. The general form of a Prolog production rule is:
ruleID :- Condition, then, Action.
Authors manuscript
Sec. 8.11.
259
To specify when the condition of a Prolog production rule is satised, we simply test the body of the rule until we reach the special predicate then:
satisfiedX :- clauseruleX,Body, satisfied_conditionsBody. satisfied_conditionsthen,_ :- !. satisfied_conditionsFirst,Rest :- First, satisfied_conditionsRest.
We could adopt any number of methods for resolving conict sets. We could add an integer as a second argument to the head of the Prolog production rule and use this as an index to determine the priority of rules. We could look at the system clock to get a time stamp that we saved with each fact that we added to the temporary database, then prefer rules whose conditions were satised most recently. These and other methods have been used in production systems. We will use the very simple method of always ring the rst rule whose condition is satised. Since we use findall to generate our conict set, this will just be the rst rule identied by findall:
resolve_set ID|_ ,ID :- !.
Using this resolution method, we will devise our production rules so the resolution set never has more than one member. If we can do this, then our resolution method will in practice give us the same results as every other resolution method. Lets develop another set of production rules for aour robot, this time in Prolog. Suppose we want our robot to stack cartons in a wharehouse. Initially the robot knows where each carton is and knows hwo the cartons are supposed to be stacked. lets say there are four cartons marked a, b, c, and d. The two cartons a and b are on the warehouse oor, c is on top of b, and d is on top of c. The goal of the robot is to stack the cartons with a on the bottom, b on a, c on b, and d on d. We represent this initial situation in the robots temporary database with ve clauses:
fsupportsfloor,a. fsupportsfloor,b. fsupports,b,c. fsupportsc,d. gstack a,b,c,d .
The robot can lift only one carton at a time. To stack the cartons, the robot will need to remove d from c and c from b. Thus it must set some intermediate goals for itself while remembering its original goal. That is, it must do some simple planning. We will not concern ourselves with the details of moving the robot hand, grasping and ungrasping cartons, and the like. We will tackle only the question of deciding which cartons to put where at each point in the process. Our rules with update the robots temporary database as it moves the cartons. Thus the database reects the changing positions fo the cartons and the robots changing goals. The rst thing we want the robot to do is to stack b on a. In general, when the robot has a goal of stacking some cartons, it should try to stack the second carton on
Authors manuscript
260
Chap. 8
the rst. If the second carton is already on the rst, the robot moves on to stacking the third carton on the second.
rule1 :- gstack X,Y|Rest , fsupportsX,Y, then, rgstack X,Y|Rest , agstack Y|Rest .
Usually, the second carton to be stacked will not already be on the rst carton. Then our robot must place the second carton on the rst. But it it cannot do this if there is already a carton on either the rst or the second carton. (We assume that only one carton can be stacked on another and that the robot can only lift one carton at a time.) The following rule picks up the second carton and puts it on top of the rst, provided it is possible to do so:
rule2 :- gstack X,Y|Rest , + factsupportsX,_, + factsupportsY,_, fsupportsZ,Y, then, rfsupportsZ,Y, afsupportsX,Y, rgstack X,Y|Rest , agstack Y|Rest .
If there is another carton on either the rst or the second carton to be stacked, the robot must remove this obstacle before it can stack the tow cartons. We need two rules to handle these possibilities:
rule3 :- gstack X,Y|Rest , fsupportsX,Z, Y = Z, + gremoveZ, then, agremoveZ. rule4 :- gstack _,X|Rest , fsupportsX,Y, + gremoveY, then, agremoveY.
Now we need to say how to remove an obstacle. Either the obstacle has another carton on it or it does not. Thus two rules are need:
rule5 :- gremoveX, fsupportsX,Y,
Authors manuscript
Sec. 8.11.
261
The terminating condition for the stacking operation is reached when there is only one carton left in the list of cartons to be stacked:
rule7 :- gstack _ , then, rgstack _ .
These rules and initial conditions are collected in the listing CARTONS.PL.
:- dynamic f 1, g 1, rule 1. :- multifile f 1, g 1, rule 1. forward-chainer finds a production rule that can be fired, fires it, and informs the user, then calls itself to repeat the process.
forward_chainer :- findallX,satisfiedX,ConflictSet, resolve_setConflictSet,ID, ruleID, write'Fired rule: ', writeID, write'.', nl, !, forward_chainer. satisfied?Rule succeeds if every condition for ruleRule tha comes
Authors manuscript
262
Chap. 8
resolve+ConflictSet,+RuleNumber Returns the RuleNumber of the first member of the ConflictSet which is the first rule in the database whose condition is satisfied. resolve_set ID|_ ,ID :- !. The remaining predicates are provided to make writing and reading production rules easier. afX :- assertafX. rfX :- retractfX. agX :- assertgX. rgX :- retractgX. then. Supervisory program fc :display_help_message, repeat, write' ', readX, X = stop,abolishf 1,abolishg 1 ; processX, nl, fail . display_help_message provides a list of commands to use with the forward chaining supervisor. display_help_message :nl, nl, write'FCHAIN - A Forward Chaining Inference Engine', nl, nl, write'This is an interpreter for files containing production',
Authors manuscript
Sec. 8.11.
263
nl, write'rules nl, nl, write'The write'load. write' write'list. write' write'go. write'stop. write'help.
written in the FCHAIN format.', prompt accepts four commands:', nl, nl, - prompts for names of rules files', nl, enclose names in single quotes', nl, - lists facts and goals in working', nl, memory', nl, - starts the forward chainer', nl, nl, - exits FCHAIN', nl, nl, - display this message', nl, nl.
process+Command provides procedures for processing each of the four kinds of commands the user may give to the supervisor. processgo :- nl, forward_chainer. processgo :- !. * if forward_chainer failed *
processload :- nl, write'File name? ', readFilename, reconsultFilename, !. processlist :- nl, write'Facts:', nl, fX, write' ', writeX, nl, fail. processlist :- nl, write'Goals:', nl, gX, write' ', writeX, nl, fail. processlist :- !. processlistX :- nl, fX, write' ', writeX, nl, fail. processlist_ :- !. Starting query :- fc.
Authors manuscript
264
Chap. 8
This is a set of production rules for a robot that stacks cartons in a warehouse. The rules have been translated into Prolog rules that can be used with the forward-chaining inference engin in FCHAIN.PL.
:- dynamic f 1, g 1, rule 1. :- multifile f 1, g 1, rule 1. rule1 :- gstack X,Y|Rest , fsupportsX,Y, then, rgstack X,Y|Rest , agstack Y|Rest . rule2 :- gstack X,Y|Rest , + fsupportsX,_, + fsupportsY,_, fsupportsZ,Y, then, rfsupportsZ,Y, afsupportsX,Y, rgstack X,Y|Rest , agstack Y|Rest . rule3 :- gstack X,Y|Rest , fsupportsX,Z, Y == Z, + gremoveZ, then, agremoveZ. rule4 :- gstack _,X|Rest , fsupportsX,Y, + gremoveY, then, agremoveY. rule5 :- gremoveX, fsupportsX,Y, + gremoveY, then, agremoveY. rule6 :- gremoveX, + fsupportsX,_, fsupportsY,X, then, rfsupportsY,X, afsupportsfloor,X, rgremoveX.
Authors manuscript
Sec. 8.12.
Bibliographical notes
265
rule7 :- gstack _ , then, rgstack _ . initial facts and goals for the carton-stacking robot fsupportsfloor,a. fsupportsfloor,b. fsupportsb,c. fsupportsc,d. gstack a,b,c,d . End of CARTONS.PL
Authors manuscript
266
Chap. 8
Authors manuscript
Chapter 9
267
Authors manuscript 693 ppid September 9, 1995 Prolog Programming in Depth
268
Chap. 9
to some area of special expertise and an understanding of some basic principles of expert systems construction. The domain of an expert system does not have to be anything as glamorous as medical diagnosis or as esoteric as mass spectroscopy. We can build an expert system to trouble-shoot some electrical appliance like a stereo system, or to tell whether people qualify for social security benets. The information needed to build many simple but useful expert systems can be found in users manuals for appliances, government brochures, and other readily available sources. An expert system can be a more intelligent substitute for a reference book or handbook. In this chapter, we will look at simple expert systems and how to build them. We will need to think about how an expert works and the different tasks an expert system needs to do. Then we will develop general tools and techniques for building small expert systems in Prolog.
Authors manuscript
Sec. 9.3.
269
listens, and talks. He communicates with the client. This is the obvious part of what he does. But the hidden part, the reason we consulted the expert in the rst place, is his use of special knowledge and problem solving skills.
Authors manuscript
270
Chap. 9
The idea behind an expert system shell is that the user can produce a true expert system for whatever problem domain he wants by llingthe shell with the expert knowledge needed for his application. These shells can simplify the task of building an expert system, but there is a price to be paid. Different problem domains may require different kinds of reasoning using different kinds of knowledge. Part of what makes an expert is his special problem solving skills. These are often represented in expert systems by special features of their inference engines. We might also want different kinds of user interfaces for different domains. Thus, merely lling in the knowledge base may not be enough to build a satsifactory expert system. The less expensive expert system shells usually support only one kind of inference. It is difcult or impossible to alter either the inference engine or the user interface provided by these shells. It is very important in choosing a shell to select a product that provides both the appropriate kind of inference engine and the appropriate kind of user interface for the application you have in mind. Other expert system shells provide inference engines with a wide range of capabilities and provide user interfaces with great exibility. These tend to be expensive, and the greater expense may not be justied for a particular application. You may need only a few of the features these systems provide, but you have paid for many more features that you may never use. The alternative to using a shell for expert system development is to build the inference engine and user interface you need in some programming language. Prolog is a good choice for this because it comes with a built-in inference engine. Of course, you can build an inference engine in Lisp, Pascal, FORTRAN, or any other programming language. Indeed, you can build a Prolog interpreter in any of these languages. But Prolog has the advantage that a sophisticated inference engine is immediately available and ready to use. Furthermore, Prolog provides a rudimentary user interface. We can enter a query, and Prolog will tell us whether it can satisfy the query from its knowledge base. If our query contains variables, Prolog will give us values for these variables that satisfy the query. Prolog also offers a basic explanatory facility. To see how Prolog reaches a conclusion, all we need to do is invoke the trace function. Of course, this will slow down execution and it will usually give us much more information than we really want. But there is no doubt that tracing Prolog execution will provide us with a complete explanation of how Prolog reached its conclusions. Notice that when you rst enter the Prolog programming environment, the inference engine and the primitive user interface are already in place. Until you either consult a le containing some facts and rules or enter some facts and rules directly at the keyboard, the inference engine has no knowledge base to use in answering your queries. Prolog thus provides a very simple expert system shell. Many expert system shells can perform the kind of inference Prolog performs and can use knowledge representations similar to Prolog facts and rules. Most shells offer user interfaces with more features than those available in Prolog. Why, then, would we ever use Prolog to develop expert systems? The reason is Prologs exibility combined with the relatively small price tag for a good Prolog interpreter or compiler. Not only does Prolog provide a useful inference engine, but it also
Authors manuscript
Sec. 9.5.
271
provides the means to build far more powerful inference engines. Not only does Prolog provide a primitive user interface, but it is an excellent language to use to build more powerful user interfaces. In fact, some of the commercially available expert system shells are written in Prolog. There is a trade-off here. Selecting the right expert system shell for a particular application takes time and effort. Sometimes we are forced to pay a high price to get the features we want. And it is usually impossible to modify the inference engine or user interface of a commercial expert system shell. Prolog, on the other hand, is relatively inexpensive and very exible. But it takes time and effort to write your own user interface, and more time and effort if you need to modify the Prolog inference engine.
Authors manuscript
272
Exercise 9.5.3
Chap. 9
An expert system consultation driver to be used with separately written knowledge bases. Procedures in the file include XSHELL, XSHELL_AUX, FINISH_XSHELL, PROP, PARM, PARMSET, PARMRANGE, EXPLAIN, MEMBER, and WAIT. Requires various procedures defined in the files READSTR.PL, READNUM.PL, and GETYESNO.PL from Chapter 5.
xshell The main program or procedure for the expert system consultation driver. It always succeeds. xshell :- xkb_introStatement, writelnStatement, nl, wait, xkb_identifyRULE,TextList, assertaknownidentification,RULE, append_listTextList,Text, writelnText, nl, explain, xkb_uniqueyes, !, xshell_aux. xshell :- xshell_aux.
Authors manuscript
Sec. 9.5.
273
xshell_aux Prevents an abrupt end to a consultation that ends without an identification, or a consultation where multiple identifications are allowed. xshell_aux :+ knownidentification,_, writeln'I cannot reach a conclusion.', !, finish_xshell.
xshell_aux :- xkb_uniqueno, knownidentification,_, writeln'I cannot reach any further conclusion.', !, finish_xshell. xshell_aux :- finish_xshell. finish_xshell Erases the working database and asks if the user wants to conduct another consultation. finish_xshell :retractallknown_,_, writeln'Do you want to conduct another consultation?', yes, nl, nl, !, xshell. finish_xshell. prop+Property Succeeds if it is remembered from an earlier call that the subject has the Property. Otherwise the user is asked if the subject has the Property and the user's answer is remembered. In this case, the procedure call succeeds only if the user answers 'yes'. propProperty :- knownProperty,Value,
Authors manuscript
274
!, Value == y.
Chap. 9
propProperty :- xkb_questionProperty,Question,_,_, writelnQuestion, yes, nl, nl, !, assertknownProperty,y. propProperty :- assertknownProperty,n, nl, nl, !, fail. parm+Parameter,+Type,+Value Type determines whether Value is to be a menu choice, an atom, or a number. Value becomes the remembered value for the parameter if there is one. Otherwise the user is asked for a value and that value is remembered. Calls to this procedure are used as test conditions for identification rules. Value is instantiated before the procedure is called and parmParameter,Type,Value only succeeds if the remembered value, or alternatively the value reported by the user, matches Value. parmParameter,_,Value :- knownParameter,StoredValue, !, Value = StoredValue. parmParameter,m,Value :- xkb_menuParameter,Header,Choices,_, lengthChoices,L, writelnHeader,nl, enumerateChoices,1, readnumber_in_range1,L,N, nl, nl, assertknownParameter,N, !, Value = N. parmParameter,a,Value :- xkb_questionParameter,Question,_,_, writelnQuestion, readatomResponse, nl, nl, assertknownParameter,Response, !, Value = Response.
Authors manuscript
Sec. 9.5.
275
parmParameter,n,Value :- xkb_questionParameter,Question,_,_, writelnQuestion, readnumberResponse, nl, nl, assertknownParameter,Response, !, Value = Response. parmset+Parameter,+Type,+Set Type indicates whether the Parameter takes a character, an atom, or a number as value, and Set is a list of possible values for Parameter. A call to the procedure succeeds if a value for Parameter is established that is a member of Set. parmsetParameter,Type,Set :- parmParameter,Type,Value, memberValue,Set. parmrange+Parameter,+Minimum,+Maximum Parameter must take numbers as values, and Minimum and Maximum must be numbers. A call to the procedure succeeds if a value for Parameter is established that is in the closed interval Minimum,Maximum . parmrangeParameter,Minimum,Maximum :parmParameter,n,Value, Minimum = Value, Maximum = Value. explain and explain_aux Upon request, provide an explanation of how an identification was made. explain :- xkb_explainno, wait, !. explain :- writeln 'Do you want to see the rule that was used', 'to reach the conclusion?' , + yes, nl, !.
Authors manuscript
276
Chap. 9
explain :- knownidentification,RULE, clausexkb_identifyRULE,_,Condition, nl,nl, write'Rule ', writeRULE, write': reach this conclusion IF', nl, explain_auxCondition, nl, wait, nl, !. explain_auxCondition,RestOfConditions :!, interpretCondition, explain_auxRestOfConditions. explain_auxCondition :interpretCondition. interpret+Condition. Uses questions and menus associated with a condition of and identification rule to display the condition in a format that makes sense to the user. interpretpropProperty :!, xkb_questionProperty,_,Text,_, Text is a message that says the subject to be identified has the Property. writeText, nl. interpret +propProperty :!, xkb_questionProperty,_,_,Text, Text is a message that says the subject to be identified does not have the Property. writeText, nl. interpretparmParameter,m,N :!, xkb_menuParameter,_,Choices,Prefix, Prefix is a phrase that informs the user which Parameter is involved. nth_memberN,Text,Choices, nth_member is used to retrieve the user's choice from the menu associated with the Parameter. writePrefix, writeText, write'.', nl. interpret +parmParameter,m,N :-
Authors manuscript
Sec. 9.5.
277
!, xkb_menuParameter,_,Choices,Prefix, Prefix is a phrase that informs the user which Parameter is involved. nth_memberN,Text,Choices, nth_member is used to retrieve the user's choice from the menu associated with the Parameter. writePrefix, write'NOT ', writeText, write'.', nl. interpretparmParameter,_,Value :!, For any Parameter whose Value is not obtained by using a menu. xkb_questionParameter,_,Prefix,_, writePrefix, writeValue, write'.', nl. interpret +parmParameter,_,Value :!, For any Parameter whose Value is not obtained by using a menu. xkb_questionParameter,_,Prefix,_, writePrefix, write'NOT ', writeValue, write'.', nl. interpretparmsetParameter,m,Set :!, xkb_menuParameter,_,Choices,Prefix, writePrefix, write'one of the following -', nl, Since parmset is involved, any value for Parameter included in Set would have satisfied the condition. list_choices_in_setSet,Choices,1. interpret +parmsetParameter,m,Set :!, xkb_menuParameter,_,Choices,Prefix, writePrefix, write'NOT one of the following -', nl, Since parmset is involved, any value for Parameter not in Set would have satisfied the condition. list_choices_in_setSet,Choices,1. interpretparmsetParameter,_,Set :!, For any Parameter whose Value is not obtained by using a menu. xkb_questionParameter,_,Prefix,_, writePrefix, write'one of the following - ', nl, enumerateSet,1. interpret +parmsetParameter,_,Set :!, For any Parameter whose Value is not obtained by using a menu. xkb_questionParameter,_,Prefix,_, writePrefix, write'NOT one of the following - ', nl, enumerateSet,1. interpretparmrangeParameter,Min,Max :-
Authors manuscript
278
Chap. 9
!, xkb_questionParameter,_,Prefix,_, writePrefix, write'between ', writeMin, write' and ', writeMax, write'.', nl. interpret +parmrangeParameter,Min,Max :!, xkb_questionParameter,_,Prefix,_, writePrefix, write'NOT between ', writeMin, write' and ', writeMax, write'.', nl. interpret +Condition :clauseCondition,Conditions, Any condition that does not have prop, parm, parmset, or parmrange as its functor must corres pond to some Prolog rule with conditions of its own. Eventually, all conditions must terminate in conditions using prop, parm, parmset, or parmrange. write'A condition between here and "end" is NOT satisfied -', nl, explain_auxConditions, write' end', nl. interpretCondition :clauseCondition,Conditions, Any condition that does not have prop, parm, parmset, or parmrange as its functor must corres pond to some Prolog rule with conditions of its own. Eventually, all conditions must terminate in conditions using prop, parm, parmset, or parmrange. explain_auxConditions.
enumerate+N,+X Prints each atom in list X on a separate line, numbering the atoms beginning with the number N. Used to enumerate menu choices. enumerate ,_. enumerate H|T ,N :- writeN,write'. ',writeH,nl, M is N + 1, enumerateT,M. list_choices_in_set+X,+Y,+N
Authors manuscript
Sec. 9.5.
279
The members of the list of atoms Y corresponding to the positions in the list indicated by the members of the list of integers X are printed on separate lines and numbered beginning with the number N.
list_choices_in_set ,_,_. list_choices_in_set N|Tail ,Choices,M :nth_memberN,Choice,Choices, writeM, write'. ', writeChoice, nl, K is M + 1, list_choices_in_setTail,Choices,K. readnumber_in_range+Min,+Max,-Response Evokes a numerical input from the user which must be between Min and Max inclusively. readnumber_in_rangeMin,Max,Response :readnumberNum, testnumber_in_rangeMin,Max,Num,Response. testnumber_in_range+Min,+Max,+Input,-Response Tests user Input to insure that it is a number between Min and Max inclusively. If it is not, instructions for the user are printed and readnum 1 is called to accept another numerical input from the user. testnumber_in_rangeMin,Max,Num,Num :Min = Num, Num = Max, !. testnumber_in_rangeMin,Max,_,Num :write'Number between ', writeMin, write' and ', writeMax, write' expected. Try again. ', readnumber_in_rangeMin,Max,Num. wait Stops execution until the user presses a key. Used to
Authors manuscript
280
Chap. 9
wait :- write'Press Return when ready to continue. ', get0_, nl, nl. yes Prompts the user for a response and succeeds if the user enters 'y' or 'Y'. yes :write'|: ', get_yes_or_noResponse, !, Response == yes.
memberX, X|_ . memberX, _|Y :- memberX,Y. nth_member+N,-X,+Y X is the nth element of list Y. nth_member1,X, X|_ . nth_memberN,X, _|Y :- nth_memberM,X,Y, N is M + 1. append_list , . append_list N|Tail ,Text :- append_listTail,Text1, xkb_textN,Text2, appendText2,Text1,Text. writeln+Text Prints Text consisting of a string or a list of strings, with each string followed by a new line. writeln :- !.
Authors manuscript
Sec. 9.6.
281
(which, in many Prologs, can be embedded in the system as a starting query). The procedure xshell does the rst three of the four jobs we have listed, then calls xshell_aux, which, together with finish_xshell, ends the consultation smoothly. The xshell procedure has two clauses with the rst doing most of the work. It rst calls xkb_intro 1, which supplies an introductory message for the user. Note that a clause for xkb_intro must be included in our XSHELL knowledge base since
Authors manuscript
282
Chap. 9
it will be different for each expert system. The argument to xkb_intro is a list of atoms which are displayed using the writeln 1 procedure from Chapter 5. Next xshell attempts an identication. This is the meat of the system, and we will need several procedures to support the interaction needed to make an identication. All of these are called, indirectly, by the single goal xkb_identifyRULE,TextSet. The xkb_ prexis used to indicate that the identication rules will be part of the XSHELL knowledge base. In order to remember every identication that has been made, xshell uses asserta to add a fact to the denition of a special predicate known 2. We will also use clauses for the predicate known to store other temporary information during a consultation. This is information supplied by the user or inferred from the users information and the knowledge base. We will reserve the term knowledge base for all the rules and facts the system has at the beginning of a consultation. We will call the temporary information or conclusions remembered during a consultation the working database, and we will call predicates like known that are used to store this information database predicates. The knowledge base is an integral part of the expert system, but the working database is a temporary phenomenon belonging only to a particular consultation. Once it has made an identication, the system should inform the user of its ndings. A system that identies barnyard animals might say The animal is a cow. A medical diagnostic system might say A possible diagnosis is pneumonia. For any domain, there will probably be some standard phrases the system will use repeatedly to report conclusions. In these examples, the phrases are The animal is a and A possible diagnosis is . Rather than store the entire text for the conclusion in each rule, pieces of text are stored in the knowledge base in clauses for the predicate xkb_text 2. What is stored in the rules is a list of atoms corresponding to these pieces of text. The procedure append_list 2 uses the list of text indices to assemble a list of atoms that are displayed by writeln 1. Next XSHELL offers to explain how it reached its conclusion. We mentioned a simple expert system that identies animals commonly found in a barnyard. This identication system should only give one identication for each subject. But not all systems will work this way. A medical diagnostic system, for example, might give two or three possible diagnoses and a prescription for each. After an identication has been made, reported, and explained, the system should either end the consultation or backtrack to look for other identications. If identication is unique for the domain of the knowledge base, the consultation should end. If identication is not unique, xshell should fail and backtrack. We tell the system whether to try to nd more than one identication by putting a special fact in the knowledge base either xkb_uniqueyes or xkb_uniqueno. Then we include xkb_uniqueyes as a subgoal in xshell. If this subgoal succeeds, the cut in xshell is executed and xshell_aux is called. If xshell does not nd xkb_uniqueyes in the knowledge base, it backtracks to look for another identication. Then xshell continues to bounce between xkb_identifyRULE,TextList) and xkb_uniqueyes until it can nd no more identications. When xkb_identifyRULE,TextList fails, execution moves to the second clause in the denition of xshell, and xshell_aux is called. It simply reports that
Authors manuscript
Sec. 9.7.
283
no (further) conclusion can be reached, and calls finish_xshell, which erases the temporary database (retracts all clauses for the predicate known) and then offers to start another consultation.
Exercise 9.6.1 What is the difference between the Prolog inference engine and the XSHELL inference engine? Exercise 9.6.2 In talking about XSHELL, we drew a distinction between a knowledge base and a working database. Explain this distinction. Why is it important?
Taken together, the rule and the associated text tell us "Identify the animal as a horse if it has the properties called hooves and mane." We will have other rules and text combinations in our knowledge base for the other animals we want to identify. We need a routine prop 1 that will automatically nd out whether the animal to be identied has hooves or a mane. Our routine gets this information by asking the user, using the yes routine from Chapter 5. When it gets an answer, it remembers the answer in case it needs it later. It resembles the routines used in CAR.PL (Chapter 2) to collect information from the user.
propProperty :- knownProperty,Value, !, Value == y. propProperty :- xkb_questionProperty,Question,_,_, writelnQuestion, yes' ', nl, nl, assertknownProperty,y, !. propProperty :- assertknownProperty,n, nl, nl,
Authors manuscript
284
!, fail.
Chap. 9
prop is called with the name of a property as its single argument. If it can be established that the subject has the property, the call to prop succeeds; otherwise it fails. In the process, one of three things happens.
1. First, prop looks in the working database to see if there is already information on the property. If there is, a cut is executed and the call to prop succeeds or fails conclusively (because of the cut) depending on whether the working database says the subject does or does not have the property. 2. Otherwise, prop asks the user about the property. To do this, it looks in the knowledge base for the appropriate form of the question, then uses the yes routine to get the users answer. If the call to yes succeeds, prop records that the subject has the property and succeeds conclusively (ending with a cut). 3. If all attempts to establish that the subject has the property have failed, prop records that the subject does not have the property and fails conclusively. We will store a separate question in the knowledge base for each property we include in an identication rule. For our horse example, we might use the following two questions.
xkb_questionhooves,'Does the animal have hooves?', 'The animal has hooves.', 'The animal does not have hooves.'. xkb_questionmane, 'Does the animal have a mane of hair on its neck?', 'The animal has a mane.', 'The animal does not have a mane.'
The third and forth arguments to question 4 are used by the explanatory facility described later. Lets think about another animal in the barnyard for a moment. A cow is an animal with hooves, too. It also chews cud. We might put this identication rule and text in our knowledge base.
xkb_identify3, isa,cow :- prophooves, propchews_cud. xkb_textcow, 'a cow.' .
Suppose the animal we are trying to identify is a cow. The rst rule in the knowledge base is the rule for identifying horses. So prop will ask us if the animal has hooves, and we will answer yes. Then prop will ask us if the animal has a mane, and we will answer no. At this point, the rule for identifying horses will fail and Prolog will try the rule for cows. First, it will see if the animal has hooves. But we dont want it to ask us again if the animal has hooves it should remember the answer we already gave. This is exactly what prop does. It always looks in the database to see if the question has already been answered before asking the question again. The rst time
Authors manuscript
Sec. 9.8.
285
through, it does not nd the answer in the database, so it asks the question. Later, it nds an answer recorded for the question that was asked previously. The order for the clauses of prop may seem backward at rst. The rst thing prop does asking the question is described in the second clause, not the rst clause. Remember, however, that the order of clauses species the order in which Prolog tries alternatives on each invocation of a predicate, not the order in which clauses will succeed in a series of invocations. Notice that properties can also be used negatively in conditions for rules. Suppose, for example, that we were building a system for determining whether a student was eligible for certain kinds of nancial aid at a particular university. To receive a Graduate Fellowship, the student must have recieved a baccalureate degree; but to receive an Undergraduate Scholarship, the student must not have received a baccalaureate degree. But both graduate and undergraduate students are eligible for student loans. Then a rule for Graduate Fellowships would have
propbaccalaureate
as a condition, and a rule for student loans would have neither. An xkb_intro 1 clause and either xkb_uniqueyes or xkb_uniqueno will be in our knowledge base. It will also contain clauses for xkb_identify 2, xkb_text 2 , and xkb_question 4. If all the identication rules use only properties, we will have a question for each property used in any of the rules. Then our main program, together with the procedure prop, will be able to make our identications for us.
Authors manuscript
286
Chap. 9
There are three arguments to parm: parameter name, parameter type, and parameter value. Usually all three of these arguments will already be instantiated since the call to parm will be used as a condition in an identication rule. The call to parm succeeds if the parameter has the required value for the subject; otherwise it fails. In the process, parm does one or the other of three things. 1. Looks in the working database to see if there is already information on the parameter. If there is, the call to parm succeeds or fails conclusively depending on whether the value given in the call to parm matches the one stored in the database. 2. If no value is stored and the expected type of response is a menu choice, parm nds the appropriate menu, prints the header for the menu, enumerates the choices, evokes a numerical response from the user corresponding to one of the choices, stores it, and tests it against the required value 3. If no value is stored and the expected type of response is an atom or a number, parm retrieves and prints the appropriate question, evokes the appropriate type of response from the user, stores it, and tests it against the required value. In the same way that properties can be used negatively in rules, so can parameters. For example, consider a system that determines the ight awards for which a frequent yer is eligible. A frequent yer with a certain number of miles might be eligible for a free round-trip ticket to anywhere in the United States except Alaska and Hawaii. We could use a destination parameter for this. The corresponding rule might say that the frequent yer is entitled to a ticket provided he or she meets conditions including
+ parmdestination,a,alaska, + parmdestination,a,hawaii.
In this case, it is easier to indicate the destinations that are not allowed in negative conditions than to indicate the destinations that are allowed in positive conditions. Another useful procedure is parmset 3, which uses the auxiliary predicate member.
parmset+Parameter,+Type,+Set :- parmParameter,Type,Value, memberValue,Set.
Suppose a condition for some identication rule is that the subject lives in New England. Rather than make this a property, we can make lives_in_state a parameter and make the condition succeed if the state is one of the New England states. Using two-letter postal codes for states, we can do this with the subgoal
parmsetlives_in_state,a, 'CT','MA','ME','NH','RI','VT' .
Authors manuscript
Sec. 9.8.
287
If we dont want to require that the postal code be given in capital letters, we could add Ct, ct, etc., to the set of values that satisfy the call to parmset. We can represent the negative condition for frequent yer eligibility in our earlier example more simply using parmset. It would be
+ parmsetdestination,a, alaska,hawaii .
Finally, we dene a predicate parmrange 3 that checks to see if the numerical value of a parameter is in some specied range:
parmrangeParameter,Minimum,Maximum :parmParameter,n,Value, Minimum = Value, Maximum = Value.
This is the predicate we would use for our earlier examples age_less_than_18, age_between_18_and_60, etc. Instead of asking if the subject has each of these properties, we would use the subgoals parmrangeage,0,17 and parmrangeage,18,60. Notice that we dont need to tell parmrange the type of input to expect since it can only be numeric. As with our earlier predicates, parmrange can of course be used in negative conditions. Suppose minors and senior citizens qualify for certain benets under certain conditions. The rule for this might have either the condition
parmrangeage,0,20, parmrangeage,65,1000
. The advantage of using parmset and parmrange rather than a group of related properties is that the user only needs to answer a single question. This one answer is then used to satisfy or fail any number of conditions. Even if each condition species a unique value for a parameter rather than a set or range of acceptable values, it may be better to use parm instead of prop. Take the example of eye color. If the only eye color that ever shows up as a condition in any identication rule in the knowledge base is blue, then we might as well use the condition propblue_eyes. But some rules might require blue eyes, others brown eyes, and others green eyes. Then the conditions to use would have the form parmeye_color,m,N where the menu is
xkb_menueye_color, 'What color are your eyes?' , 'blue', 'green', 'brown', 'other.' , 'Eye color: '.
Authors manuscript
288
Chap. 9
With a single keystroke, the user gives information that can be used to satisfy or fail several different conditions. These are all the tools we will need to make our identications. An XSHELL knowledge base will contain a set of identication rules, each of them a clause for the xkb_identify predicate. Each rule will use the predicates prop, parm, parmset, and parmrange in its conditions or subgoals. For every property or parameter named in a condition for an identication rule, we will also need to include an appropriate question or menu in the knowledge base. These will be stored in clauses for the predicates xkb_question and xkb_menu.
Authors manuscript
Sec. 9.9.
289
are using the predicate known in part as a stack for storing identications with the most recent identication always on the top of the stack. The procedure explain is invoked by xshell after a conclusion has been reached and reported. First explain checks whether xkb_uniqueno is in the knowledge base. If so, no explanation is required and the procedure wait is called. This procedure prints a message telling the user to press any key to continue and inputs a keystroke, allowing the user time to read the reported conclusion before the screen scrolls to print another question or menu. If xkb_explainno is not in the knowledge base, explain asks the user if he would like to see the rule used to derive the last conclusion. If the user answers no, explain succeeds without further action. If the user answers yes, explain nds knownidentification,RULE to nd the last identication rule that succeeded. It then prints the rule number and invokes explain_aux 1. This procedure does nothing more than pass individual conditions to the routine interpret 1, rst separating complex conditions into their individual clauses. It is the procedure interpret that actually displays the conditions of the rule. Corresponding to a property used in a rule is an xkb_question 4 clause. The rst argument for this clause is the internal property name and the second is the question itself. The third argument is a short piece of text saying that the subject has the property, and the fourth is a short piece of text saying that the subject does not have the property. The rst clause in the denition of interpret prints the positive message as the interpretation of a prop condition, and the second clause prints the negative message as the interpretation of a + prop condition. To interpret a condition involving a parameter where a menu is used to obtain the value of the parameter, the third clause for interpret uses the number in the condition indicating the menu choice to retrieve the text corresponding to that choice stored in the xkb_menu 4 clause for that parameter. The auxiliary procedure nth_member is used for this. Also stored as the fourth argument for the nth_member clause is a short phrase identifying the parameter used in the condition. This phrase together with the text corresponding to the menu choice are printed to explain the condition. For conditions involving parameters where the value of the parameter is entered as an atom or a number, interpret simply prints the short phrase identifying the parameter and the stored parameter value. For parmset and parmrange conditions, interpret prints, respectively, all allowable values or the maximum and minimum values. In interpreting negative conditions using parm, parmset, or parmrange, the only difference is that NOT is printed at an appropriate point in the display. The last two clauses for interpret involve a kind of condition that we have not yet discussed. Consider once again a system for discovering the kinds of nancial aid for which a student is eligible. There may be many scholarships available for juniors and seniors majoring in computer science who have a grade point average of 3.0 or higher. Each eligibility rule for these scholarships could contain the complex condition
parmmajor,a,'computer science', parmsetyear,a, junior,senior , parmrangegpa,3.0,4.0.
Authors manuscript
290
Chap. 9
Alternatively, we could combine these conditions in dening a new condition that we might call good_upper_cs:
good_upper_cs :- parmmajor,a,'computer science', parmsetyear,a, junior,senior , parmrangegpa,3.0,4.0.
Then we can use good_upper_cs as a condition in the scholarship eligibility rules rather than the much longer complex condition. This makes the rules easier to write and easier to understand. But the user may not understand the condition good_upper_cs. So interpret must replace this condition with the parm, parmset, and parmrange conditions that dene it. The next to last clause for interpret handles the case for negative dened conditions of this sort, and the last clause handles positive conditions. The clauses appear in this order because with the cuts in all earlier clauses, the nal clause catches everything that drops through. These will be exactly the positive dened conditions. For negative dened conditions, interpret reports that one of the dening conditions is not satised. The reports would become difcult to interpret if one negative condition were dened using another negative dened condition. However, this situation probably wont arise frequently. Notice that all dened conditions must ultimately be grounded in conditions that use prop, parm, parmset, or parmrange, or interpret will fail. What other kinds of conditions might we include in XSHELL rules? An example might be a rule that required that the ratio of a persons body weight to height in inches should fall in a certain range. This complex condition could be represented as
parmweight,n,W, parmheight,n,H, R is W H, Min = R, R = Max.
If conditions like this occur in an XSHELL knowledge base, the explanatory facility should be turned off. Of course, it could be left on during system development since explain will at least print the rule number before encountering the uninterpretable condition and failing.1 As is clear from the code, interpret recurses until all the conditions upon which the rule depends have been interpreted.
Authors manuscript
Sec. 9.10.
291
2. an introductory statement (a clause for xkb_intro 1), 3. xkb_uniqueyes or xkb_uniqueno, 4. xkb_explainyes or xkb_explainno, 5. a set of identication rules (clauses for xkb_identify 2), 6. text from which conclusions can be constructed (clauses for xkb_text 2), 7. a set of questions and menus for the properties and parameters used in the identication rules (clauses for xkb_question 4 and xkb_menu 4), and 8. a starting query (:- xshell. or ?- xshell.) to begin the consultation when the knowledge base is loaded, or instructions to tell the user how to type the starting query. An example is the le CICHLID.PL, which contains an identication system that identies tropical sh from the family Cichlidae. Rules for nine small or "dwarf" species are included. We will call the system itself CICHLID. CICHLID has a lengthy introductory statement, given as a list of atoms to be printed by writeln. Also notice that we have included empty atoms where we want blank lines to appear in the message, and that quotation marks that are to be printed are written twice. The identication of cichlids is not always easy. It is unlikely that anyone who knew much about these sh would confuse the species we have included in our knowledge base, but other species are difcult to distinguish. This is why we have allowed more than one identication (xkb_explainno is in the knowledge base) and use the phrase Possible identication in each of the conclusions we display. We include a line in the knowledge base to turn the explanatory facility on. Some characteristics used to identify dwarf cichlids are the shape of the tail, the shape of the body, the shape of the other ns, and the presence or absence of a long stripe down the side of the body. We will use these features to make a rst cut in identifying these sh. The tail of a cichlid is either spear shaped with the tail coming to a point, lyre shaped with points at the top and bottom of the tail n, or normal (rounded or triangular). These options are recorded in an xkb_menu clause for the parameter caudal. Two of our sh have spear-shaped tails, two have lyre-shaped tails, and the tails of the rest are normal. We use this parameter in the rst condition of each identication rule. The bodies of these sh are either long and narrow, short and deep, or what we might think of as a normal sh-shape. Again, we have a parameter that can take one of three values. We use another short menu to ask the user for the shs shape. The dorsal (or top) n of these sh may have some rays or spines near the front extended to form a crest or some rays at the back extended to form a streamer. The anal (or rear bottom) n and the ventral (or front bottom) ns may also show a streamer. We represent each of these features as a distinct property the sh might have and provide an appropriate question for each.
Authors manuscript
292
Chap. 9
The lateral stripe, or stripe down the side of the sh, is very distinct on some sh and irregular on others. On some sh, there is no lateral stripe at all. This gives us another parameter with three possible values. Other features used to identify these sh visually include color and special markings. Color is represented in this knowledge base as a parameter. Other special markings are represented as properties. You can decipher these by comparing the names of the properties with their questions. We have included far more information in our identication rules than we need to distinguish the nine species CICHLID can identify. However, all of this information would not be enough to make a positive identication if rules for other dwarf cichlids were added to the knowledge base. The knowledge base given here is actually an excerpt of a much larger knowledge base that includes dozens of species. This raises an interesting dilemma for building knowledge bases. We could edit CICHLID.PL by eliminating many of the conditions in the identication rules. The resulting knowledge base would still allow us to identify all of the sh in the knowledge base, and the user would have to answer fewer questions. Shouldnt we do this? If we are quite sure that we will never want to add any more species, then by all means we should simplify our rules. This will make the system easier to use. But if we anticipate adding more sh to the knowledge base, we should probably make each rule as complete as possible. We dont just want to give rules that will identify these nine species. We want rules that will help us distinguish these species from other species we might add to the knowledge base at a later time. The knowledge base has another peculiarity. Only one sh is pale blue, only one sh has white-tipped ns, and other sh in the knowledge base have some property that distinguishes them from all the others. Why dont we put these distinguishing properties at the top of the rule in which they occur? Then we could identify each sh with a single, unique property. Surprisingly, this approach will usually force the user to answer more questions rather than fewer. Suppose each of our nine sh had some unique property. If these were the only properties listed, then a user would have to answer anywhere from one to nine questions to identify a particular sh. How many questions he had to answer would depend an how far down in the knowledge base the rule for his sh came. On average, he would have to answer ve questions. Suppose on the other hand that we could divide our nine sh into three groups of three with a single parameter. Then suppose we could divide each of these subgroups into individuals with another parameter. If we could do this, the user would always have to answer exactly two questions to identify any sh. Of course, this strategy is defeated because we have included more information than we really need just to tell these nine species apart. But the basic strategy is correct. Usually the best way to organize your rules will be to break the possible conclusions into large groups with a single question, then break each group into subgroups, and so on down to the level of the individual.
Authors manuscript
Sec. 9.10.
293
XKB_IDENTIFY must be declared dynamic so the explanatory routine INTERPRET can access its clauses. :- dynamic xkb_identify 2. xkb_intro '', 'CICHLID: An Expert System for Identifying Dwarf Cichlids', '', 'The cichlids are a family of tropical fish. Many of', 'these fish are large and can only be kept in large', 'aquariums. Others, called ''dwarf cichlids'', rarely', 'exceed 3 inches and can be kept in smaller aquariums.', '', 'This program will help you identify many of the more', 'familiar species of dwarf cichlid. Identification of', 'these fish is not always easy, and the program may offer', 'more than one possible identification. Even then, you', 'should consult photographs in an authoritative source', 'such as Staek, AMERIKANISCHE CICHLIDEN I: KLEINE', 'BUNTBARSCHE Melle: Tetra Verlag, 1984, or Goldstein,',
Authors manuscript
294
Chap. 9
'CICHLIDS OF THE WORLD Neptune City, New Jersey:', 't.f.h. Publications, 1973 for positive identification.', '', 'To use the program, simply describe the fish by', 'answering the following questions.' . xkb_uniqueno. xkb_explainyes. xkb_identify-Rule,-TextSet Each clause for this predicate provides a rule to be used with the utility predicates in the XSHELL.PL file to determine whether the fish to be identified is likely to belong to the Species. xkb_identify1, isa,agassizii :parmcaudal,m,2, spear-shaped parmbody_shape,m,1, long and narrow parmlateral_stripe,m,1, sharp, distinct propdorsal_streamer, proplateral_stripe_extends_into_tail. xkb_identify2, isa,borelli :parmcaudal,m,3, normal parmbody_shape,m,2, deep, heavy, short parmlateral_stripe,m,2, irregular propdorsal_streamer, propventral_streamer, proplateral_stripe_extends_into_tail, parmcolor,m,5. yellow xkb_identify3, isa,cockatoo :parmcaudal,m,1, lyre-shaped parmbody_shape,m,2, deep, heavy, short parmlateral_stripe,m,1, sharp, distinct propdorsal_crest, propdorsal_streamer, propanal_streamer, propstripes_in_lower_body, proplateral_stripe_extends_into_tail. xkb_identify4, isa,trifasciata :parmcaudal,m,3, normal
Authors manuscript
Sec. 9.10.
295
parmbody_shape,m,3, normal parmlateral_stripe,m,1, sharp, distinct propdorsal_crest, propdorsal_streamer, propanal_streamer, propventral_streamer, proplateral_stripe_extends_into_tail, propangular_line_above_ventral. xkb_identify5, isa,brichardi parmcaudal,m,1, parmbody_shape,m,3, parmlateral_stripe,m,3, parmcolor,m,2, propgill_spot, propfins_trimmed_white. : lyre-shaped normal not visible pale gray
xkb_identify6, isa,krib :parmcaudal,m,2, spear-shaped parmbody_shape,m,1, long and narrow propdorsal_streamer, propanal_streamer, proporange_spots_in_tail. xkb_identify7, isa,ram :parmcaudal,m,3, parmbody_shape,m,2, parmlateral_stripe,m,3, propdorsal_crest, parmcolor,m,4. xkb_identify8, isa,nannacara parmcaudal,m,3, parmbody_shape,m,2, parmlateral_stripe,m,3, parmcolor,m,3.
normal deep, heavy, short not visible violet, yellow, claret : normal deep, heavy, short not visible metallic bronze, green
xkb_identify9, isa,nudiceps :parmcaudal,m,3, normal parmbody_shape,m,1, long and narrow parmlateral_stripe,m,3, not visible parmcolor,m,1. pale blue xkb_questiondorsal_crest, 'Are any fin rays at the front of the dorsal fin', 'clearly extended above the rest of the fin?' ,
Authors manuscript
296
Chap. 9
xkb_questiondorsal_streamer, 'Are any fin rays at the 'clearly extended into a 'Rear rays of dorsal fin 'Rear rays of dorsal fin
back of the dorsal fin', long streamer?' , are extended.', are not extended.'.
xkb_questionanal_streamer, 'Are any fin rays at the back of the anal fin', 'clearly extended into a long streamer?' , 'Rear rays of anal fin are extended.', 'Rear rays of anal fin are not extended.'. xkb_questionventral_streamer, 'Are any fin rays at the bottom of the ventral', 'fins clearly extended into streamers?' , 'Rays of anal fin are extended.', 'Rays of anal fin are not extended.'. xkb_questionlateral_stripe_extends_into_tail, 'Does the stripe down the side extend into the base', 'of the tail?' , 'The lateral stripe extends into the tail.', 'The lateral stripe does not extend into the tail.'. xkb_questionstripes_in_lower_body, 'Are there horizontal stripes in the lower part', 'of the body?' , 'Horizontal stripes present in the lower body.', 'There are no horizontal stripes in the lower body.'. xkb_questionangular_line_above_ventral, 'Is there an angular line above the ventral fin', 'slanting from the pectoral downward toward the', 'stomach region?' , 'Slanting body line is present.', 'Slanting body line is absent.'. xkb_questionorange_spots_in_tail, 'Are there black spots trimmed in orange in', 'the tail fin?' , 'Orange-trimmed black spots present in tail.', 'There are no orange trimmed black spots in the tail.'.
Authors manuscript
Sec. 9.10.
297
xkb_questiongill_spot,'Is there a dark spot on the gill?', 'Dark spot present on gill.', 'There is no dark spot on the gill.'. xkb_questionfins_trimmed_white, 'Are the unpaired fins trimmed with a white edge?', 'Unpaired fins are trimmed with white edge.', 'The unpaired fins do not have a white edge.'. xkb_menucaudal, 'What is the shape of the tail-fin?' , 'lyre-shaped', 'spear-shaped', 'normal, i.e, round or fan-shaped' , 'Tail fin is '. xkb_menubody_shape, 'What is the shape of the body?' , 'long and narrow', 'deep, heavy and short', 'normal fish shape' , 'Body is '. xkb_menulateral_stripe, 'Describe the line running the length of the body.' , 'sharp and distinct from eye to base of tail', 'irregular, indistinct or incomplete', 'not visible or barely visible' , 'Lateral body stripe is '. xkb_menucolor, 'What is the basic color of the body?' , 'pale blue', 'pale gray', 'metallic bronze or green', 'violet, yellow and claret highlights', 'yellow', 'not listed' , 'The basic body color is '. xkb_textisa, 'Possible identification: ' . xkb_textagassizii, 'Apistogramma agassizii ', 'Agassiz''s dwarf cichlid' .
Authors manuscript
298
xkb_textborelli, 'Apistogramma borelli ', 'Borell''s dwarf cichlid' . xkb_textcockatoo, 'Apistogramma cacatuoides ', 'cockatoo dwarf cichlid' . xkb_texttrifasciata, 'Apistogramma trifasciata ', 'three-striped dwarf cichlid' . xkb_textbrichardi, 'Lamprologus brichardi' . xkb_textkrib, 'Pelvicachromis pulcher ', 'krib or kribensis' .
Chap. 9
xkb_textram, 'Microgeophagus ramirezi ', 'Ram, or butterfly dwarf cichlid' . xkb_textnannacara, 'Nannacara anomala' . xkb_textnudiceps, 'Nanochromis nudiceps' . :- write'Type xshell. to start.'.
Authors manuscript
Sec. 9.11.
299
What is the shape of the tail-fin? 1. lyre-shaped 2. spear-shaped 3. normal, i.e, round or fan-shaped 2
What is the shape of the body? 1. long and narrow 2. deep, heavy and short 3. normal fish shape 1
Describe the line running the length of the body. 1. sharp and distinct from eye to base of tail 2. irregular, indistinct or incomplete 3. not visible or barely visible 1
Are any fin rays at the back of the dorsal fin clearly extended into a long streamer? y Does the stripe down the side extend into the base of the tail? y Possible identification: Apistogramma agassizii Agassiz's dwarf cichlid Do you want to see the rule that was used to reach the conclusion? y Rule 1: reach this conclusion IF Tail fin is spear-shaped. Body is long and narrow. Lateral body stripe is sharp and distinct from eye to base of tail. Rear rays of dorsal fin are extended. The lateral stripe extends into the tail.
Authors manuscript
300
Chap. 9
Are any fin rays at the back of the anal fin clearly extended into a long streamer? n I cannot reach any further conclusion. Do you want to conduct another consultation? n yes
The program terminates by returning to the Prolog top level environment, which answers yes because the original query has succeeded.
Exercise 9.11.1 Lamprologus leleupi is a small yellow cichlid with a long, narrow body and a rounded tail. It does not have streamers on any of its ns and it does not have a line on the side of its body. Its iris is blue. Modify the CICHLID knowledge base so that it can identify Lamprologus leleupi.
Authors manuscript
Sec. 9.12.
301
forget a relevant question or mistakenly draw conclusions from the silence of the user the way a human expert might. But this means that we must consider what the clerk would do in the case that the customer volunteers no more than that he or she needs to paint something. The rst thing the clerk needs to know is what is to be painted: walls, oors, etc. Next the clerk needa to know the material that the surface to be painted is made of: wood, plaster, etc. Third, the clerk needs to know if the surface to be painted has been painted previously and, if so, the condition of this prior coating. Finally, the clerk needs to know something about the environment. We are assuming an interior environment; the only other question we will consider is whether the paint will be exposed to high moisture as it might be in a kitchen or bathroom. To keep our system simple, these are the only considerations we will include. A reasonable way to proceed is with a ow chart. We put the rst question to be asked at the top of the chart and draw arrows from it to other questions, one arrow for each possible answer to the rst question. The completed chart represents the search space for our system. In the completed chart, each path from the top to the bottom of the chart represents a sequence of questions and answers that might take place between the clerk and the customer. Each of these paths also represents a rule in our knowledge base. Each question represents a property or parameter and each answer represents whether a property condition is positive or negative, or represents a value for a parameter. This is how PAINT.PL was developed. In looking at the ow chart for PAINT.PL, we notice that certain combinations of questions and answers concerning prior coatings occur together regularly at midlevels in paths. If the surface has been painted before, we need to know if the old paint is in sound condition. If it is, then we need to know whether the old paint is glossy. It was convenient to group the possibilities using one negative property condition and three dened conditions:
+ proppainted, bad_paint, glossy_paint, nonglossy_paint.
A complete paint recommendation does not consist simply in recommending a product. Any professional painter will tell you that proper preparation of the surface to be painted is essential. Instructions for preparing the surface must be part of a complete recommendation. Once prepared, it may be necessary to apply more than one product in a certain sequence. For typical residential applications, it may be necessary to apply one product as a base coat or primer and a different product as the nish coat. In some industrial applications, a third product applied between the primer and the nish coat may be advisable. Thus, the system should recommend an appropriate paint system. This is where the advantage of representing conclusions for rules as lists of boiler-plate text that can be assembled for the report becomes obvious. Recommendations for preparation, primer, nish coat, and other special instructions can be stored separately and assembled to t a particular paint
Authors manuscript
302
Chap. 9
application. This also simplies maintenance of the system. If a new preparation method were adopted for several paint applications, or if a new product became available, it would only be necessary to revise the text in a few xkb_text clauses rather than in affected xkb_identify clauses that might number in the hundreds in a medium sized system.
XKB_IDENTIFY and the following predicates defined in the knowledge base must be declared dynamic so the explanatory routine INTERPRET can access their clauses. ::::dynamic dynamic dynamic dynamic xkb_identify 2. bad_paint 0. glossy_paint 0. nonglossy_paint 0.
xkb_intro 'PAINT PRO:', '', 'This system makes recommendations for common interior', 'painting situations. The recommendations include advice', 'on preparing the surface for painting and advice on the', 'coating products to use for the job', '', 'To use the system, just answer the following questions',
Authors manuscript
Sec. 9.12.
303
'about your painting job.' . xkb_uniqueno. xkb_explainyes. xkb_identify1, new_drywall_prep,enamel :parmsurface,m,1, walls or ceilings parmmaterial1,m,1, drywall sheet rock + proppainted, prophigh_moisture. kitchen, bathroom, laundry xkb_identify2, new_drywall_prep,latex :parmsurface,m,1, walls or ceilings parmmaterial1,m,1, drywall sheet rock + proppainted, + prophigh_moisture. xkb_identify3, standard_prep,stain_killer,enamel :parmsurface,m,1, walls or ceilings parmmaterial1,m,2, wood or vinyl panelling + proppainted, prophigh_moisture. kitchen, bathroom, laundry xkb_identify4, standard_prep,stain_killer,latex :parmsurface,m,1, walls or ceilings parmmaterial1,m,2, wood or vinyl panelling + proppainted, + prophigh_moisture. xkb_identify5, bare_plaster_prep,enamel :parmsurface,m,1, walls or ceilings parmmaterial1,m,3, plaster + proppainted, prophigh_moisture. kitchen, bathroom, laundry xkb_identify6, bare_plaster_prep,latex :parmsurface,m,1, walls or ceilings parmmaterial1,m,3, plaster + proppainted, + prophigh_moisture. xkb_identify7, bare_masonry_prep,latex_primer,enamel :parmsurface,m,1, walls or ceilings parmmaterial1,m,4, masonry + proppainted,
Authors manuscript
304
prophigh_moisture.
Chap. 9
xkb_identifystandard_prep, bare_masonry_prep,latex_primer,latex :parmsurface,m,1, walls or ceilings parmmaterial1,m,4, masonry + proppainted, prophigh_moisture. xkb_identify9, bad_paint_prep,enamel :parmsurface,m,1, walls or ceilings parmsetmaterial1,m, 1,2,3 , sheet rock, panelling, or plaster bad_paint, prophigh_moisture. xkb_identify10, bad_paint_prep,latex :parmsurface,m,1, walls or ceilings parmsetmaterial1,m, 1,2,3 , sheet rock, panelling, or plaster bad_paint, + prophigh_moisture. xkb_identify11, glossy_prep,standard_prep,enamel, latex_over_oil_prep :parmsurface,m,1, walls or ceilings parmsetmaterial1,m, 1,2,3 , sheet rock, panelling, or plaster glossy_paint, prophigh_moisture. xkb_identify12, glossy_prep,standard_prep,latex, latex_over_oil_prep :parmsurface,m,1, walls or ceilings parmsetmaterial1,m, 1,2,3 , sheet rock, panelling, or plaster glossy_paint, + prophigh_moisture. xkb_identify13, standard_prep,enamel :parmsurface,m,1, walls or ceilings parmsetmaterial1,m, 1,2,3 , sheet rock, panelling, or plaster nonglossy_paint, prophigh_moisture. xkb_identify14, standard_prep,latex :-
Authors manuscript
Sec. 9.12.
305
parmsurface,m,1, walls or ceilings parmsetmaterial1,m, 1,2,3 , sheet rock, panelling, or plaster nonglossy_paint, + prophigh_moisture. xkb_identify15, painted_masonry_prep,enamel :parmsurface,m,1, wall, ceilings, or floors parmmaterial1,m,4, masonry proppainted, prophigh_moisture. xkb_identify16, painted_masonry_prep,latex :parmsurface,m,1, wall, ceilings, or floors parmmaterial1,m,4, masonry proppainted, + prophigh_moisture. xkb_identify17, bare_wood_prep,polyurethane :parmsurface,m,2, wood doors, trim, cabinets + proppainted. xkb_identify18, bad_coating_on_wood_prep,polyurethane :parmsurface,m,2, wood doors, trim, cabinets bad_paint. xkb_identify19, glossy_prep,standard_prep,polyurethane, opaque_wood_finish,latex_over_oil_prep :parmsurface,m,2, wood doors, trim, cabinets glossy_paint. xkb_identify20, standard_prep,polyurethane, opaque_wood_finish :parmsurface,m,2, wood doors, trim, cabinets nonglossy_paint. xkb_identify21, wood_floor_prep,polyurethane :parmsurface,m,3, floors parmmaterial2,m,1. wood xkb_identify22, painted_masonry_prep,masonry_sealer, trim_enamel :parmsurface,m,3, floors parmmaterial2,m,2, masonry proppainted.
Authors manuscript
306
Chap. 9
xkb_identify23, bare_masonry_prep,masonry_sealer, trim_enamel :parmsurface,m,3, floors parmmaterial2,m,2, masonry + proppainted. bad_paint :- proppainted, + propsound_paint. glossy_paint :- proppainted, propsound_paint, propglossy_paint. nonglossy_paint :- proppainted, propsound_paint, + propglossy_paint. xkb_questionhigh_moisture, 'Are you painting in an area where you can expect', 'high moisture or where frequent cleaning may be', 'necessary kitchen, bathroom, laundry?' , 'High moisture or frequent cleaning expected.', 'Neither high moisture nor frequent cleaning expected.'. xkb_questionpainted, 'Has the surface been painted or varnished before?', 'The surface has a previous coating.', 'The surface has no previous coating.'. xkb_questionsound_paint, 'Is the existing paint or varnish in sound condition?', 'Previous coating in sound condition.', 'Previous coating is unsound.'. xkb_questionglossy_paint, 'Is the existing paint or varnish glossy?', 'Previous coating is glossy.', 'Previous coating is not glossy.'. xkb_menusurface, 'What kind of surface do you plan to paint or varnish?', 'walls or ceiling', 'wood doors, trim, or cabinets', 'floors' , 'Surface: '.
Authors manuscript
Sec. 9.12.
307
xkb_menumaterial1, 'What kind of material is the surface made of?', 'drywall sheet rock', 'wood or vinyl panelling', 'plaster', 'masonry concrete, concrete block, brick' , 'Material: '. xkb_menumaterial2, 'What kind of material is the surface made of?', 'wood', 'masonry concrete, concrete block, brick' , 'Material: '. xkb_textnew_drywall_prep, 'Remove all dust from taping and finishing work using', 'wet vac or damp not wet cloth.', 'Prime with a latex enamel undercoater. Spot prime pathces', 'of drywall mud, then cover entire surface.' . xkb_textstain_killer, 'Prime vinyl panelling with a commercial stain killer.' . xkb_textbare_plaster_prep, 'Allow all new plaster to age at least 30 days before painting.', 'Remove dust with damp not wet cloth.', 'Prime with a latex enamel undercoater.' . xkb_textbare_masonry_prep, 'Efflorescence, a white powdery alkaline salt, is present in', 'and on most masonry surfaces. This must be removed completely', 'before painting. Wire brush all heavy deposits. Mix 1 part', 'muriatic acid with three parts water and apply to surfaces.', 'Rinse with clear water as soon as the foaming action stops.', 'Never allow muriatic solution to dry on the surface before', 'rinsing as this will damage the surface or cause premature', 'coating failure. If acid etching is not practical, power wash', 'the surface to remove all powdery deposits, chalk, dirt,', 'grease, and oil.' . xkb_textbad_paint_prep, 'Scrape away all loose and peeling paint. Sand to a sound', 'surface. Sand adjoining area to feather into peeled area.', 'Prime any cracks, holes, or large repairs BEFORE patching', 'and then again AFTER patching. The primer provides a sound', 'bond for the patching material and then prepares the patch',
Authors manuscript
308
Chap. 9
'for painting. Use an interior latex undercoater for spot', 'priming and to prime entire surface before painting.' . xkb_textglossy_prep, 'Sand glossy surfaces lightly.' . xkb_textlatex_over_oil_prep, 'If existing paint is oil-based, prime with a commercial', 'stain killer before painting with latex paint.' . xkb_textstandard_prep, 'Use a household detergent to clean away all dirt, dust,', 'grease, and oil from all surfaces to be painted. Rinse', 'and wipe all detergent residue from surfaces with clean', 'water before painting.' . xkb_textpainted_masonry_prep, 'Scrape away peeling paint or use a power grinder to grind', 'away peeling paint.', 'Efflorescence, a white powdery alkaline salt, is present in', 'and on most masonry surfaces. This must be removed completely', 'before painting. Wire brush all heavy deposits. Mix 1 part', 'muriatic acid with three parts water and apply to worst areas.', 'Rinse with clear water as soon as the foaming action stops.', 'Never allow muriatic solution to dry on the surface before', 'rinsing as this will damage the surface or cause premature', 'coating failure. If acid etching is not practical, power wash', 'the surface to remove all powdery deposits, chalk, dirt,', 'grease, and oil.' . xkb_textbare_wood_prep, 'Sand lightly and remove dust with a wet vac or a damp cloth.' . xkb_textbad_coating_on_wood_prep, 'Scrape away all loose and peeling paint or varnish.', 'Sand to a sound surface. Sand adjoining area to feather', 'into peeled area. Lightly sand entire surface. Remove all', 'dust with a wet vac or a damp cloth.' . xkb_textwood_floor_prep, 'Sand smooth and remove all sanding dust. Remove or', 'neutralize residues from any wood lighteners wood', 'bleach or removers before finishing.' . xkb_textlatex_primer, 'Prime with latex enamel undercoater.' .
Authors manuscript
Sec. 9.13.
309
xkb_textmasonry_sealer, 'Prime with an alkyd resin based masonry primer sealer.' . xkb_textenamel, 'Finish with interior latex semigloss enamel or interior alkyd', 'enamel for resistance to moisture and easy cleaning.' . xkb_textlatex, 'Finish with flat or semigloss interior latex paint.' . xkb_textpolyurethane, 'Stain to desired shade and finish with a clear poly-', 'urethane finish.' . xkb_textopaque_wood_finish, 'For opaque finishes, prime with an alkyd sealer primer', 'and finish with a gloss or semigloss alkyd, acrylic latex,', 'or polyurethane enamel.' . xkb_texttrim_enamel, 'Paint with acrylic latex floor and trim enamel.' . :- write'Type xshell. to start.'.
Authors manuscript
310
Chap. 9
sh and make recommendations for paint applications. As you build your rules, be sure to include comments in your code that indicate the situation a particular rule is intended to cover. Your choices of property and parameter names will help in this regard, but a brief comment combining several conditions into one clear statement can be invaluable to anyone trying to understand the knowledge base later. Also, notice how we have added comments to parm conditions that use menus to indicate the menu choice represented by the number in the condition. At rst, we suggest you use the name of a property or a parameter enclosed in asterisks (*) as its own question or menu heading. For example, you might have this question in your knowledge base:
xkb_questionage,'*age*',prefix,''.
As you add rules that use a particular property or parameter, you may rene your idea of what that property or parameter involves. This will affect the eventual form of the question. By putting in a place-holder initially, you avoid having to revise the question or menu repeatedly. Similarly, use short phrases for menu choices. When the rules are complete, you can then put your questions and menus into their nal forms. By postponing exact formulation of questions and menus, you can also get your prototype system up and running faster. Once you have your simplied rules and questions, you will need to add a few lines to your knowledge base to get it to run. First, put
xkb_intro'Introduction'.
at the beginning. When you run XSHELL with your knowledge base, the word Introduction will appear when the consultation starts. Later you will replace this with a description of your expert system and instructions for using it. You should already know whether your expert system will give only one identication or might give several. Put
xkb_uniqueyes.
or
xkb_uniqueno.
in your knowledge base to reect this. To turn on the explanatory facility, put
xkb_explainyes.
in the knowledge base. You may want to change this later, but you will want to see explanations while you are verifying and debugging your knowledge base. Notice in CICHLID.PL and PAINT.PL how we have used descriptive atoms to label the text stored in xkb_text clauses. Add the clause
xkb_textX, X .
Authors manuscript
Sec. 9.13.
311
to your knowledge base. This will allow you to test and debug your system before you put in any actual text for conclusions. When a conclusion is reached, the system will report it by printing only the labels for the text blocks. If you use this strategy to get your prototype running quickly, be sure to remove this clause later when you enter your actual xkb_text clauses. Otherwise, you will get multiple reports for your conclusions containing combinations of text labels and actual text. Eventually you may want to put lines at the beginning of your knowledge base that erase any XSHELL knowledge base predicates already in memory when you load your knowledge base. You can copy these from the les CICHLID.PL or PAINT.PL. But for now just add the command line
:- ensure_loaded'xshell.pl'.
at the very beginning of your knowledge base and the command line
:- xshell.
at the very end. (Some Prologs may not read the last line properly unless a blank line follows it.) These will automatically load XSHELL.PL if it is not already loaded, and then start the consultation after loading your knowledge base. You are ready for your rst run. Start your Prolog interpreter and load your knowledge base. If you get any error messages during loading, write them down and consult the users manual for your Prolog interpreter. Common errors are omitted commas and misplaced periods. XSHELL.PL should load automatically when you load your knowledge base, and this le will in turn load various I/O les listed at the top of the XSHELL.PL listing. If your Prolog interpreter cannot nd one of these les, this will cause an error. Once you get your knowledge base to load properly (and to load XSHELL.PL and the other les properly), the consultation should begin automatically and the message Introduction should appear on the screen. If the message doesnt appear and xshell fails immediately, you forgot to put an xkb_intro clause in your knowledge base. Next you should be asked about the property or parameter in the rst condition of the rst identication rule of your knowledge base. If instead you get the message that no conclusion could be drawn, one of two things has probably happened: you have left out your identication rules or you have left out your questions. Go back and correct this problem. Once questions start appearing, give answers that will satisfy the conditions for your rst identication rule. If XSHELL skips over any question for one of these conditions, it should be because you put the wrong parameter value in an earlier condition or you didnt provide a question for one of the conditions. Check for these common errors and make the corrections. Eventually, you should be asked all the questions required by the rst identication rule in your knowledge base. Give the required answers to each of these. Then a list of text labels corresponding to the conclusion of your rst identication rule should appear on the screen. If instead XSHELL continues to ask you questions, you may have forgotten to put a list of text labels in your identication rule or you may have forgotten to put the clause
Authors manuscript
312
xkb_textX, X .
Chap. 9
in your knowledge base. To check to see if you gave the correct answers to satisfy the identication rule, break out of XSHELL and use the query
?- listingknown.
This will tell you what answers were recorded for the questions you were asked. You can compare these with your identication rules to try to pin down any problems. Be sure to retract all clauses for the predicate known to erase the working database before restarting your expert system. Suppose XSHELL reports its identication but continues to ask questions even though only one identication was expected. Then you did not put xkb_uniqueno in your knowledge base. You should be able to make any identication rule in your knowledge base succeed. Perhaps one rule is succeeding before you can get to the rule you are trying to make succeed. You may be able to solve the problem by reversing the order of these two rules. Or you may need to add another condition to the earlier rule. The second solution is preferred since then the operation of the system will not depend on the order of the rules in the knowledge base. When rule order is an essential element of the knowledge base, the system becomes more difcult to maintain or to expand. Another method you might try is to add one rule at a time to your knowledge base, verifying that each rule is working properly before you add the next. Put each new rule at the beginning of your knowledge base so you can make it succeed quickly. If you use this strategy, it is particularly important that you wait until all the rules have been added before you replace the short versions of your questions and menus with the nal versions. A single parameter might be used by several identication rules, and you may nd yourself continually rewriting the question or menu for a parameter if you dont leave this step for last. Once you have veried your knowledge base in this simplied version, you can go back and replace your short introductory statement, properties, parameters, questions, menus, and conclusions text with the longer versions you want. You dont have to replace the names of properties and parameters with longer names, but longer, more descriptive names are helpful in documenting your knowledge base. You will want to run your expert system again, trying to get each identication rule to succeed. This will let you see what each of your questions and conclusions looks like. If you use menus for any of your questions, trying to make each identication rule succeed will also point out choices that may have been omitted from menus. These are some of the methods that you can use to get XSHELL running with your knowledge base. Of course, we are assuming that there are no bugs in your XSHELL.PL le itself. You can use the CICHLID.PL and PAINT.PL les to verify that XSHELL is operating properly. You can also use the trace facility in your Prolog interpreter to trace the actual execution during a consultation. Developing a knowledge base for an expert system is difcult the rst time you try it, but it is easier if you approach the job systematically. Dont try to do everything
Authors manuscript
Sec. 9.14.
Bibliographical notes
313
at once. By following the recommendations in this section, you should be able to get small to medium sized systems running quickly. The difcult part is the acquisition and analysis of the knowledge to put into your system; but that problem goes well beyond the scope of this book.
Exercise 9.13.1 In this chapter, we described an expert system that can identify several barnyard animals: horses, cows, goats, pigs, chickens, ducks, crows, dogs, cats, and rats. Construct an XSHELL knowledge base for such a system; call it BARNYARD.PL. Exercise 9.13.2 Add sheep to BARNYARD.PL. Did you have to change any of the rules that were already in BARNYARD.PL to get it to work properly? Why or why not? Exercise 9.13.3 The le CAR.PL in Chapter 2 contains a simple expert system that diagnoses automobile starting problems. Compare CAR.PL with XSHELL.PL, explaining how similar jobs are done in each. Rewrite the CAR expert system using XSHELL.
Authors manuscript
314
Chap. 9
Authors manuscript
Chapter 10
315
Authors manuscript 693 ppid September 9, 1995 Prolog Programming in Depth
316
Chap. 10
inference engine in Prolog, building a new inference engine that can reason about probabilities using this mathematical theory. Attractive though this suggestion may sound, there are at least two good reasons why this approach usually is not taken. The rst reason involves basic assumptions built into the mathematical theory of probabilities. The second is based on observation of human experts. We will expect the probabilities of complex situations to be determined by the probabilities of their component situations. For example, the probability that interest will rise while unemployment falls will be a function of the probability that interest will rise and the probability that unemployment will fall. What is this function? Probability theory gives us an answer, provided interest rates and unemployment are causally independent. But there is reason to think that interest rates and unemployment are not independent. Where two situations are somehow dependent on each other, probability theory cannot tell us how to compute their joint probability from their individual probabilities. Besides this, observation shows that human experts do not normally adjust their condences in a way that ts probability theory. Actual practice suggests a different approach, one that has not yet been fully analyzed. Many different inference engines have been built around these observations, each an attempt to capture the way human experts reason with uncertain information. Because the pattern of condence human experts exhibit does not t the mathematical theory of probability, we will not talk about the probabilities of different situations or outcomes at all. Instead, we will use the term condence factor or certainty factor. The condence factor of a hypothesis will be the measure of our inclination to accept or reject that hypothesis. Our condence in some hypotheses does appear to determine our condence in others, even if no nal agreement has been reached about the correct method for computing this determination. In this chapter, we will build another expert system shell. Unlike XSHELL, this new shell will extend the Prolog inference engine by providing one of many possible ways to reason with condence factors. The user interface and explanatory facilities for this shell will also be different, providing the reader with additional techniques for handling these functions. We will call this expert system shell CONMAN, which is short for Condence Manager. The complete program is in the accompanying listing titled CONMAN.PL.
Authors manuscript
Sec. 10.2.
317
ond, CONMAN can infer a condence factor for the hypothesis from the condence factors of other hypotheses. To infer a condence factor, CONMAN needs rules that say how much condence one should have in the conclusion of the rule when the condition of the rule is satised. The knowledge base builder must supply the condence factors for the rules. This is part of the expert knowledge built into the knowledge base. Condence factors come from two places. The rule itself has a condence factor, and so do its premises or conditions. We may be 100 certain that the sun rises in the east, but if we are unsure whether it is dawn or dusk, we will not be certain whether the sun on the horizon indicates east or west. We will need a way to combine the condence factor for the rule with the condence factor for the condition to get the condence factor for the conclusion. To compute the condence factor for the conclusion of a rule, we will multiply the condence factor for the rule by the condence factor for the condition, then divide by 100. If we are 80 certain that the stock market will go up if interest goes down, and we are 75 sure that interest will go down, then this method says we should be (80 x 75)/100 or 60 sure that the stock market will go up. Some rules will have complex conditions that involve the Boolean operators not, and, or or. We will therefore need methods for computing the condence factor for the negation of a hypothesis, for the conjunction of two hypotheses, and for the disjunction of two hypotheses. We will assume that the condence factor for not H is 100 minus the condence factor for H. If we are 83 condent that it will rain, then we are only 17 condent that it will not rain. Condence factors for conjunctions are trickier. Suppose our condence that we turned off the lights this morning is 90 and our condence that our teenage daughter turned of the radio is 30. What should our condence be that both the lights and the radio were turned off? If the two events are causally independent, probability theory says the combined condence is (30 x 90)/100 or 27. If they are not independent, probability theory says the combined condence can still never be higher than the lesser of the two condences in this case, 30. It is this most optimistic gure that most expert systems using condence or certainty factors accept. And so will we. If H1 has a condence factor of M and H2 has a condence of N, we will take the condence factor for H1 and H2 to be the lesser of M and N. While optimism is the order of the day for and, most expert systems use the most pessimistic gure for or. Suppose we are 25 sure that it will rain today and 30 sure that our teenage son will remember to water the lawn today. What should be our condence that one or the other will happen? The absolute lowest gure would be 25 since we would have this much condence in the rain even if our son werent involved. In fact, our condence that one or the other will happen should be somewhat higher than our condence that either alone will happen. But we will take the conservative approach and say that the condence factor for H1 or H2 is the greater of M and N where M and N are the condence factors for H1 and H2 respectively. Further, we may have more than one rule for a single hypothesis, and each rule may have a different condence factor. What do we do in this case?
Authors manuscript
318
Chap. 10
If a condence factor of 0 means the hypothesis is certainly false and a condence factor of 100 means it is certainly true, then a factor of 50 should mean that the evidence is evenly balanced. Furthermore, any factor below 50 shows a tendency to think the hypothesis is false. When we have more than one rule for a hypothesis giving us more than one condence factor, we could combine the factors below 50 to get the evidence against the hypothesis, combine the factors above 50 to get the evidence for the hypothesis, then compare these to get the total evidence. Some expert systems do something like this. We will adopt a simpler strategy for CONMAN. We will assume that a rule can only give evidence for a hypothesis, never evidence against. Furthermore, we will not take the evidence provided by the different rules as being cumulative. Instead we will think of each rule as making a case for the hypothesis, and we will base our opinion on the best case that can be made. So if three different rules recommend condence factors of M, N, and K for our hypothesis, we will take the greatest of these three as the nal condence factor for the hypothesis. These decisions about how we will represent and use condence factors will guide our design of the inference engine for CONMAN. But before we start writing procedures for the inference engine, we must consider the form we will give to the special rules that will go into a CONMAN knowledge base.
Authors manuscript
Sec. 10.3.
Condence rules
319
Conditions.
The Hypothesis of a condence rule will be a quoted atom in English, showing what CONMAN should display when asking the user questions about the hypothesis or reporting its condence in the hypothesis. The ConfidenceFactor of a condence rule will be a number between 0 and 100, representing the condence we would have in the conclusion if we were 100 condent in the condition. The Prerequisites of a condence rule will be a list of hypotheses that must be conrmed (or disconrmed) before the rule can be applied. If we want a hypothesis to be disconrmed before a rule can be applied, we put the symbol , immediately before it in the list of prerequisites. The Conditions of a condence rule will be a complex list of conditions and atoms representing the relationships between the conditions. If the condition for a rule is a single hypothesis, Conditions will have two members: the hypothesis and the atom yes. If the condition for a rule is that a single hypothesis is false, then Conditions will have two members: the hypothesis and the atom no. For example, in our car repair example we had a rule with the condition that the carburetor jet was not damaged. This would be represented by the list
'The carburetor jet is damaged.',no .
If the condition is a conjunction of two other conditions, then Conditions will have the form
and,Condition-1,Condition-2 .
Of course, Condition-1 and Condition-2 can have any of the forms that Conditions itself can have. That is, conjunctions within conjunctions are permitted. Finally, if the condition for a rule is a disjunction of two other conditions, then Conditions will have the form
or,Condition-1,Condition-2 .
Again, Condition-1 and Condition-2 can have any of the forms that Conditions can have. This gives us four basic ways to formulate a condition. Any condition with any of these four forms can be the second or third member of an and or an or condition. Lets look at a complex example from a knowledge base for medical diagnosis that we will discuss in detail later. The object of the expert system is to diagnose the patients illness and to prescribe for it. We are very condent (85) that if the patient has nasal congestion without either a sore throat or chest congestion, then he is suffering from an allergy. There are no prerequisites for our rule, but it has a complex condition that requires one hypothesis to be conrmed and two others to be disconrmed. Our rule has an empty list of prerequisites and looks like this:
c_rule'The patient has allergic rhinitis.', 85, , and, 'The patient has nasal congestion.',yes , and, 'The patient has a sore throat.',no , 'The patient has chestcongestion.',no .
Authors manuscript
320
Chap. 10
Consider also the following rule for prescribing a medication. We have complete condence that a decongestant should be given to a patient with nasal congestion provided the patient does not also suffer from high blood pressure. As our condence that the patient has high blood pressure increases, our condence that he should be given a decongestant decreases. We dont even consider giving the patient a decongestant unless he has nasal congestion, but our condence in the prescription for a patient with nasal congestion depends only on our condence in his having high blood pressure. So nasal congestion is a prerequisite, and not having high blood pressure is the only condition.
c_rule'Give the patient a decongestant.', 100, 'The patient has nasal congestion.' , 'The patient has high blood pressure.',no .
There are other examples of CONMAN condence rules in the le MDC.PL. This le is a knowledge base for a toy medical diagnostic system. Of course, we are not physicians and the MDC expert system is not medically valid. It is only a demonstration of techniques.
Most of the work of confidence_in will involve option (2) above. There are four clauses to cover this case and one clause for each of the other cases. We will look at the other cases rst, then return to the case of conditions of the form Hypothesis,yes . The empty condition is always conrmed, and the rst clause in the denition of confidence_in is
confidence_in ,100 :- !.
Authors manuscript
Sec. 10.4.
321
We assume that the condence factor for a hypothesis being true and the condence factor for a hypothesis being false will always add up to 100. If we are 95 sure that it will rain, then we are 5 sure that it will not. Of course, we do not usually say things like I am 5 sure that it wont rain. Whenever our condence falls below 50, we dont say that we are sure but rather that we are unsure. Still, the lower our condence that something is true, the higher our condence that it is false. With this assumption, we build our inference engine to compute the condence that something is false by rst computing the condence that it is true.
confidence_in Hypothesis,no ,CF :!, confidence_in Hypothesis,yes ,CF0, CF is 100 - CF0.
We decided earlier that our condence in a conjunction would be equal to our condence in the less likely of the two conjuncts, and that our condence in a disjunction would be equal to our condence in the more likely of the two disjuncts. These decisions are reected in the following clauses.
confidence_in and,Conjunct1,Conjunct2 ,CF :!, confidence_inConjunct1,CF1, confidence_inConjunct2,CF2, minimum CF1,CF2 ,CF. confidence_in or,Disjunct1,Disjunct2 ,CF :!, confidence_inDisjunct1,CF1, confidence_inDisjunct2,CF2, maximum CF1,CF2 ,CF.
The utility predicates maximum 2 and minimum 2 nd, respectively, the largest and smallest numbers in a list, in the range 0 to 100. When we call confidence_in with Hypothesis,yes as the rst argument, the procedure will try to determine the condence factor for the hypothesis using the following four methods. 1. Check to see if the condence factor was determined and remembered earlier. 2. If appropriate, ask the user for the condence factor. 3. Determine the condence factor using the condence rules in the knowledge base. 4. If none of the above produces a result, assign a condence factor of 50, indicating that the weight of evidence for and against the hypothesis is evenly balanced. We have a separate clause for each of these four methods. For the rst, we have the clause:
Authors manuscript
322
Chap. 10
The arguments for our database predicate known 3 represent the hypothesis, the condence factor, and information about the way the condence factor was determined. This last piece of information is used by the explanatory facilities we will examine later; we ignore it here. The second clause asks the user for the condence factor, if a method for doing so has been provided:
confidence_in Hypothesis,yes ,CF :ask_confidenceHypothesis,CF, !, assertknownHypothesis,CF,user.
We will describe the ask_confidence 2 procedure later; it is part of the CONMAN user interface. If CONMAN cannot ask the user for the condence factor, it must compute it using condence rules and the condence factors for other hypotheses that make up the prerequisites and conditions of these rules:
confidence_in Hypothesis,yes ,CF :assertacurrent_hypothesisHypothesis, findallX,evidence_thatHypothesis,X,List, retractcurrent_hypothesis_, findallC,member C,_ ,List,CFList, CFList == , !, maximumCFList,CF, member CF,Explanation ,List, assertknownHypothesis,CF,Explanation. Line 1 Line 2 Line 3
Line 1 leaves a message for the explanatory facility to tell it that confidence_in has begun to investigate a new hypothesis. At certain points, the user will have the opportunity to ask why any given question is being asked. The explanatory facility will use these clauses for current_hypothesis to construct an answer. Line 2 calls evidence_that, which instantiates its second argument to a list containing a condence factor for the hypothesis, plus the prerequisites and conditions needed to infer it with that condence factor. Since many rules can support the same hypothesis, evidence_that yields multiple solutions, and findall collects these into a list. Once evidence_that has done its work, the user will have no opportunity to ask about questions related to this hypothesis, so the clause added earlier to the working database predicate current_hypothesis is retracted. Line 3 gathers all the condence factors in List and returns them as a list of condence factors in the variable CFList. If there is no evidence for the hypothesis, CFList will be empty and the clause will fail. Otherwise, the condence factor for the hypothesis becomes the maximum value in CFList. Information about the conditions that were used to compute the selected condence factor is extracted from List. CONMAN
Authors manuscript
Sec. 10.4.
323
remembers the condence factor and the explanatory information by adding a clause to the working database predicate known. The only way this method for nding a condence factor for a hypothesis can fail is if there are no rules for the hypothesis whose prerequisites are satised. If this happens, the value of CFList will be the empty list. Then the following clause, which always succeeds, will be invoked:
confidence_in Hypothesis,yes ,50 :assertknownHypothesis,50,no_evidence, !.
The hypothesis gets a condence factor of 50 because there was no evidence for it or against it. This tells us how CONMAN determines the condence factor for a hypothesis, except that we havent said how evidence_that works. This crucial predicate is dened by a single clause:
evidence_thatHypothesis, CF, CF1,Prerequisite, Condition :c_ruleHypothesis,CF1,Prerequisite,Condition, confirmPrerequisite, confidence_inCondition,CF2, CF is CF1 * CF2 100.
That is, evidence_that nds a rule for the hypothesis. If the prerequisites for the rule have been conrmed, evidence_that calls confidence_in to get a condence factor for the condition of the rule. This value is multiplied by the condence factor for the rule itself, the result is divided by 100, and this gure is returned by evidence_that together with pertinent information about the content of the rule. Notice that neither confidence_in nor evidence_that is directly recursive. However, each of them sometimes calls the other, and thus they are indirectly recursive. In the knowledge base, we set a condence factor threshold (call it T). We then consider a hypothesis conrmed if its condence factor is higher than T, or disconrmed if its condence factor is lower than 100 - T. The prerequisite for each condence rule is a list of hypotheses to be conrmed or disconrmed. The hypotheses to be disconrmed are preceded in the list by a hyphen. The predicate confirm then works through this list:
confirm .
confirm -,Hypothesis|Rest :- !, knownHypothesis,CF,_, kb_thresholdT, M is 100 - CF, M = T, confirmRest. confirm Hypothesis|Rest :- knownHypothesis,CF,_,
Authors manuscript
324
Chap. 10
Authors manuscript
Sec. 10.6.
325
the user to enter a condence factor either as a word or as a number between 0 and 100.
In cases (2) and (3), each of the subconditions can also have any of these three forms hence the recursion. To explain a condition, explain_conclusion_aux divides the condition into two parts, explains the rst part, and then explains the rest. Each
Authors manuscript
326
Chap. 10
part may be divided up in the same way. Eventually, the part that remains to be explained should be empty. When explain_conclusion_aux passes an empty argument to itself, it should succeed with no further action. This gives us one terminating condition for explain_conclusion_aux:
explain_conclusion_aux :- !.
Besides this terminating condition, we need clauses for each of the three forms a condition can take:
explain_conclusion_aux Hypothesis,_ :!, explain_conclusion_auxHypothesis. explain_conclusion_aux and, Hypothesis,_ ,Rest :explain_conclusion_auxHypothesis, explain_conclusion_auxRest, !. explain_conclusion_aux or, Hypothesis,_ ,Rest :explain_conclusion_auxHypothesis, explain_conclusion_auxRest, !.
The functions of these clauses are obvious. Finally we need procedures for explaining a simple hypothesis. First lets look at the case where the condence factor was given to CONMAN by the user. This will be recorded in a fact for the predicate known. When CONMAN discovers this, it will next look to see if the condence factor for the hypothesis conrms or disconrms the hypothesis. Then it will report that it accepted or rejected the hypothesis because of the response the user gave when asked about the hypothesis.
explain_conclusion_auxHypothesis :knownHypothesis,CF,user, kb_thresholdT, CF = T, !, writeHypothesis, writeln' -', write'From what you told me, I accepted this with ', writeCF, writeln' confidence.', nl. explain_conclusion_auxHypothesis :knownHypothesis,CF,user, !, DisCF is 100 - CF, writeHypothesis, writeln' -', write'From what you told me, I rejected this with ', writeDisCF,
Authors manuscript
Sec. 10.7.
327
The case where the condence factor was set at 50 because no evidence was available is easy:
explain_conclusion_auxHypothesis :knownHypothesis,50,no_evidence, !, writeHypothesis, writeln' -', writeln'Having no evidence, I assumed this was 50-50.', nl.
The last and most complicated case is where a condence rule was used to determine the condence factor. The explanation is stored as a list with three members: the condence factor for the rule that was used, the list of prerequisites for the rule, and the (possibly complex) condition for the rule. So CONMAN must display this information in a readable format. Each prerequisite is represented as something conrmed or disconrmed, and each component hypothesis in the condition is represented as something to conrm or to disconrm. Two auxiliary procedures, list_prerequisites 1 and list_conditions 1, take care of this. Finally, explain_conclusion_aux passes the condition of the rule to itself recursively for further explanation:
explain_conclusion_auxHypothesis :!, knownHypothesis,CF1, CF,Prerequisites,Conditions , writelnHypothesis,write'Accepted with ', writeCF1, writeln' confidence on the basis of the following', write'Rule: ',writelnHypothesis, write' with confidence of ', writeCF, writeln' if', list_prerequisitesPrerequisites, list_conditionsConditions, nl, explain_conclusion_auxConditions.
Eventually, every recursive call to explain_conclusion_aux will terminate with an empty condition or with a hypothesis that got its condence factor from the user or got an arbitrary condence factor of 50 because no other evidence was available. When this happens, explain_conclusion_aux succeeds.
Authors manuscript
328
Chap. 10
knowledge base. Now it will bounce between the goal kb_hypothesisHypothesis and the fail at the end of the clause, making one pass for each hypothesis in the knowledge base. We must tell CONMAN in the knowledge base which are the basic hypotheses it must investigate. We use clauses for the kb_hypothesis 1 predicate to do this. We also determine the order CONMAN explores these hypotheses by the order of the clauses for kb_hypothesis. This order is very important since one hypothesis may be a prerequisite for a rule for another hypothesis. If the prerequisite hypothesis isnt investigated (and possibly conrmed) rst, then the rule will never succeed. Once a basic hypothesis is identied, its condence factor is determined by confidence_in and compared with the threshold condence factor. If it doesnt reach the threshold, conman backtracks and tries a different basic hypothesis. If it reaches the threshold, conman displays it and explains it. Then we hit the fail that causes conman to backtrack and get another basic hypothesis. There may be many more subordinate hypotheses included in conditions for our condence rules than there are basic hypotheses to be explored. When one of these subordinate hypotheses is conrmed, it is neither reported nor explained. The hypotheses that will be most important to the user, the ones that will be reported and explained when conrmed, must be identied in the knowledge base. However, CONMAN will explain how it conrmed or disconrmed a subordinate hypothesis if this hypothesis is a condition in a rule used to conrm some basic hypothesis. When conman can nd no more basic hypotheses to investigate, execution moves to the second clause, which checks that at least one basic hypothesis was conrmed, then reports that no further conclusions can be reached, and nally calls finish_conman to clean up. If no hypotheses were conrmed, the third clause is executed. It prints a slightly different message and calls finish_conman. finish_conman eliminates the working database and asks the user whether to start another consultation.
Authors manuscript
Sec. 10.7.
329
factors in reaching conclusions. As a result, we distinguish the procedures in CONMAN that handle communication with the user from the predicates that make up the CONMAN inference engine.
conman Starts the consultation, calls the procedures making up the inference engine, reports conclusions to the user, and invokes the explanatory facility. conman :- kb_introStatement, writelnStatement,nl, kb_thresholdT, kb_hypothesisHypothesis, confidence_in Hypothesis,yes ,CF, CF = T, write'Conclusion: ', writelnHypothesis, write'Confidence in hypothesis: ', writeCF, writeln'.', explain_conclusionHypothesis, fail. conman :- kb_hypothesisHypothesis, confirm Hypothesis ,!, writeln'No further conclusions.', nl, finish_conman. conman :- writeln'Can draw no conclusions.', nl, finish_conman. finish_conman Ends a consultation and begins another if requested. finish_conman :retractallknown_,_,_, write'Do you want to conduct another consultation?', yes, nl, nl, !, conman. finish_conman.
Authors manuscript
330
Chap. 10
ask_confidence+Hypothesis,-CF Asks the user to express his her confidence in the Hypothesis under consideration. User chooses a descriptive phrase in answer to the request. This phrase is converted to a confidence factor CF between 0 and 100. ask_confidenceHypothesis,CF :kb_can_askHypothesis, writeln'Is the following conjecture true? --', write' ', writelnHypothesis, writeln 'Possible responses: ', ' y yes n no', ' l very likely v very unlikely', ' p probably u unlikely', ' m maybe d don''t know.', ' ? why?' , write' Your response -- ', get_only y,l,p,m,n,v,u,d,? ,Reply, nl, nl, convert_reply_to_confidenceReply,CF, !, Reply == d, ask_confidence_auxReply,Hypothesis,CF. ask_confidence_aux+Char,+Hypothesis,-CF If the user asks for a justification for a request for information, this procedure invokes the explantory facility and then repeats the request for information. ask_confidence_auxChar,_,_ :- Char == ?, !.
ask_confidence_aux_,Hypothesis,CF :explain_question, !, ask_confidenceHypothesis,CF. get_only+List,-Reply An input routine that requires the user to press one of the keys in the List, which the routine returns as Reply. get_onlyList,Reply :getChar,nameValue, Char , memberValue,List,Reply = Value, !.
Authors manuscript
Sec. 10.7.
331
Try again. ',
convert_reply_to_confidence+Char,-CF A table for converting characters to numerical confidence factors. convert_reply_to_confidence?,_. convert_reply_to_confidenced,_. convert_reply_to_confidencen,0. convert_reply_to_confidencev,5. convert_reply_to_confidenceu,25. convert_reply_to_confidencem,60. convert_reply_to_confidencep,80. convert_reply_to_confidencel,90. convert_reply_to_confidencey,100. explain_question Justifies a request to the user for information by reporting hypotheses the information will be used to test. explain_question :current_hypothesisHypothesis, writeln 'This information is needed to test the following hypothesis:', writelnHypothesis, nl, writeln'Do you want further explanation?', explain_question_aux,!. explain_question :writeln'This is a basic hypothesis.', nl, wait. explain_question_aux Inputs the user's indication whether further explanation for a question is desired and, if so, forces explain_question to backtrack and provide further explanation.
Authors manuscript
332
explain_question_aux :-
Chap. 10
explain_question_aux :- nl, nl, fail. explain_conclusion+Hypothesis Where Hypothesis is a conclusion just reported, this routine asks the user whether he she wants to see how the conclusion was derived and, if so, invokes an auxiliary routine that provides the explanation. explain_conclusionHypothesis :writeln'Do you want an explanation?', yes, nl, nl, explain_conclusion_auxHypothesis, wait, !. explain_conclusion_ :- nl, nl. explain_conclusion_aux+Hypothesis Recursively reports all rules and facts used to derive the Hypothesis. explain_conclusion_aux :- !.
explain_conclusion_aux Hypothesis,_ :!, explain_conclusion_auxHypothesis. explain_conclusion_aux and, Hypothesis,_ ,Rest :!, explain_conclusion_auxHypothesis, explain_conclusion_auxRest. explain_conclusion_aux or, Hypothesis,_ ,Rest :!, explain_conclusion_auxHypothesis, explain_conclusion_auxRest. explain_conclusion_auxHypothesis :knownHypothesis,CF,user, kb_thresholdT,CF = T, !, writeHypothesis,writeln' -', write'From what you told me, I accepted this with ', writeCF,writeln' confidence.', nl.
Authors manuscript
Sec. 10.7.
333
explain_conclusion_auxHypothesis :knownHypothesis,CF,user, !, DisCF is 100 - CF, writeHypothesis,writeln' -', write'From what you told me, I rejected this with ', writeDisCF,writeln' confidence.', nl. explain_conclusion_auxHypothesis :knownHypothesis,50,no_evidence, !, writeHypothesis,writeln' -', writeln 'Having no evidence, I assumed this was 50-50.', nl. explain_conclusion_auxHypothesis :!, knownHypothesis,CF1, CF,Prerequisites,Conditions , writelnHypothesis,write'Accepted with ', writeCF1, writeln' confidence on the basis of the following', write'Rule: ',writelnHypothesis, write' with confidence of ', writeCF, writeln' if', list_prerequisitesPrerequisites, list_conditionsConditions, nl, explain_conclusion_auxConditions. list_prerequisites+List Part of the explanatory facilty. Reports whether the hypotheses in the List have been confirmed or disconfirmed. list_prerequisites :- !.
list_prerequisites -,Hypothesis|Rest :!, write' is disconfirmed: ', writelnHypothesis, list_prerequisitesRest. list_prerequisites Hypothesis|Rest :write' is confirmed: ', writelnHypothesis, list_prerequisitesRest.
Authors manuscript
334
Chap. 10
list_conditions+Condition Part of the explanatory facilty. Formats and displays a possibly complex Condition. list_conditions :- !.
list_conditions and,Hypothesis,Rest :list_conditionsHypothesis, list_conditionsRest. list_conditions or,Hypothesis,Rest :writeln' ', list_conditionsHypothesis, writeln' or', list_conditionsRest, writeln' '. list_conditions Hypothesis,yes :write' to confirm: ', writelnHypothesis. list_conditions Hypothesis,no :write' to disconfirm: ', writelnHypothesis. wait Prompts the user to press Return and then waits for a keystroke. wait :- write'Press Return when ready to continue. ', get0_, nl, nl. CONMAN inference engine The CONMAN inference engine computes the confidence in compound goals and decides which of several rules best support a conclusion. It remembers this information for later use by itself, the main conman procedure, and the explanatory facilities. confidence_in+Hypothesis,-CF
Authors manuscript
Sec. 10.7.
335
Computes the confidence factor CF for the possibly complex Hypothesis from the confidence factors for sub-hypotheses. Confidence factors for sub-hypotheses come from the working database, are requested from the user, or are determined by whatever evidence is provided by rules that support each sub-hypothesis.
confidence_in
,100 :- !.
confidence_in Hypothesis,yes ,CF :knownHypothesis,CF,_, !. confidence_in Hypothesis,yes ,CF :ask_confidenceHypothesis,CF, !, assertknownHypothesis,CF,user. confidence_in Hypothesis,yes ,CF :assertacurrent_hypothesisHypothesis, findallX,evidence_thatHypothesis,X,List, findallC,member C,_ ,List,CFList, retractcurrent_hypothesis_, CFList == , !, maximumCFList,CF, member CF,Explanation ,List, assertknownHypothesis,CF,Explanation. confidence_in Hypothesis,yes ,50 :assertknownHypothesis,50,no_evidence, !. confidence_in Hypothesis,no ,CF :!, confidence_in Hypothesis,yes ,CF0, CF is 100 - CF0. confidence_in and,Conjunct1,Conjunct2 ,CF :!, confidence_inConjunct1,CF1, confidence_inConjunct2,CF2, minimum CF1,CF2 ,CF. confidence_in or,Disjunct1,Disjunct2 ,CF :!, confidence_inDisjunct1,CF1, confidence_inDisjunct2,CF2, maximum CF1,CF2 ,CF. evidence_that+Hypothesis,-Evidence
Authors manuscript
336
Chap. 10
evidence_thatHypothesis, CF, CF1,Prerequisite,Condition c_ruleHypothesis,CF1,Prerequisite,Condition, confirmPrerequisite, confidence_inCondition,CF2, CF is CF1 * CF2 100. confirm+Hypothesis Checks to see if the confidence factor for the Hypothesis reaches the threshold set in the knowledge base. confirm .
:-
confirm -,Hypothesis|Rest :!, knownHypothesis,CF,_, kb_thresholdT, M is 100 - CF, M = T, confirmRest. confirm Hypothesis|Rest :knownHypothesis,CF,_, kb_thresholdT,CF = T, !, confirmRest. minimum+Values,-Minimum Returns the smaller value Minimum of the pair of Values. minimum M,K ,M :- M minimum _,M ,M. K, ! .
Authors manuscript
Sec. 10.7.
337
yes :-
maximum+Values,-Maximum Returns the largest value Maximum in a list of Values. maximum ,0 :- !. maximum M ,M :- !. maximum M,K ,M :- M = K, !. maximum M|R ,N :- maximumR,K, maximum K,M ,N. member?X,?Y X is an element of list Y. memberX, X|_ . memberX, _|Z :- memberX,Z. writeln+ListOfAtoms Prints text consisting of a string or a list of atoms, with each atom followed by a new line. writeln :- !.
Authors manuscript
338
Chap. 10
In some implementations, the predicate retractall is used instead of abolish, and the syntax is slightly different. The knowledge base should contain a single clause for each of the two predicates kb_intro 1 and kb_threshold 1. The kb_intro clause contains a list of quoted atoms that CONMAN will print as the introductory message for the expert system. The kb_threshold clause contains the value between 50 and 100 that a condence factor for a hypothesis must reach before the hypothesis is to be considered conrmed. In MDC.PL and PET.PL, the threshold is set at 65 and the introductory messages are simple. Next the knowledge base must provide a set of hypotheses that CONMAN will try to conrm. Each of these will be stored in a clause for the predicate kb_hypothesis. The order of these clauses is very important since they will be investigated in exactly the order they appear in the knowledge base. Look at the hypotheses in the MDC knowledge base. You will notice that there are three diagnostic hypotheses followed by ve prescriptive hypotheses. The MDC system will make all the diagnoses it can before it makes any prescriptions. If some prescription were only made in the case of a single diagnosis, for example if penicillin were only administered if pneumonia were diagnosed, we could put this prescription immediately after the diagnosis in the list of hypotheses. Now come the all-important condence rules. Each clause for c_rule has four arguments: a hypothesis, a condence factor, a list of prerequisites, and a list of conditions. We have already discussed the form of these rules and the way they are used by the CONMAN inference engine. There are several examples in the les MDC.PL and PET.PL. We noticed that a condence rule might have an empty set of prerequisites, but we now see that they may also have an empty list of conditions. The MDC rule for administering an antihistamine has a prerequisite but no conditions.
Authors manuscript
Sec. 10.8.
339
Finally, our knowledge base must specify which hypotheses the user can be asked about directly. These are enumerated in clauses for the predicate kb_can_ask. In the MDC knowledge base, these include hypotheses about the patients symptoms and other items concerning the patients condition and medical history. Notice that no hypothesis shows up in clauses for both kb_hypothesis and kb_can_ask. This is normal. If a user can determine whether or not some hypothesis is true, then he or she does not need the help of the expert system to investigate the hypothesis. The expert system uses information about hypotheses the user is competent to investigate independently (and therefore competent to answer questions about) to arrive at conclusions the user is less competent to investigate independently. Finally, we place the starting query
:- conman.
at the end of the CONMAN knowledge base if the Prolog implementation permits it. This goal will begin a consultation when the knowledge base is loaded. In some Prologs, this does not work as intended, so the distributed versions of the knowledge base simply print a message, Type conman. to start. Most of the advice in the last chapter for building and debugging an XSHELL knowledge base also applies to CONMAN knowledge bases. We will emphasize one particular point here. The hypotheses that show up in a CONMAN knowledge base are represented as complete English sentences. The sentence for a particular hypothesis must have exactly the same form in each of its occurrences. Typing errors will cause the system to perform erratically. Instead of typing each sentence repeatedly, we recommend using a short, distinctive word or syllable for each hypothesis as you build and debug your knowledge base. Later, use the global replace feature of a word processor to replace occurrences of these markers by the appropriate sentence. It is a good idea to keep the version of the knowledge base with the short markers to use if you ever expand or modify your knowledge base. Alternatively, you may wish to modify CONMAN to use the short names internally and maintain a lookup table that contains longer phrases to be used whenever a hypothesis is to be displayed on the screen.
Exercise 10.8.1 Suppose that red spots without nasal or chest congestion indicate measles with 85 condence, but red spots with chest congestion and fever indicate rheumatic fever with 90 condence. Write condence rules for these two facts and add them to the MDC knowledge base. What else do you need to add to MDC to make the two rules work properly? Exercise 10.8.2 Modify CONMAN so the text for hypotheses is stored in clauses for a predicate kb_text 2, allowing the CONMAN knowledge base builder to use short atoms in place of long text messages in CONMAN rules and elsewhere in the knowledge base. Refer to the way kb_text 2 is used in XSHELL.
Authors manuscript
340
Chap. 10
Contains a CONMAN knowledge base. Requires all utility procedures defined in file CONMAN.PL :- clauseconman,_ ; consult'conman.pl'. :::::-
Any previously defined clauses for the predicates KB_INTRO, KB_THRESHOLD, KB_HYPOTHESIS, C_RULE, and KB_CAN_ASK should be removed from the knowledge base before loading the clauses below.
abolishkb_intro 1. abolishkb_threshold 1. abolishkb_hypothesis 1. abolishc_rule 4. abolishkb_can_ask 1.
kb_intro '', 'MDC: A Demonstration Medical Diagnostic System', ' Using Confidence Rules', '' . kb_threshold65. kb_hypothesis'The patient has allergic rhinitis.'. kb_hypothesis'The patient has strep throat.'. kb_hypothesis'The patient has pneumonia.'. kb_hypothesis'Give the patient an antihistamine.'. kb_hypothesis'Give the patient a decongestant.'. kb_hypothesis'Give the patient penicillin.'. kb_hypothesis'Give the patient tetracycline.'. kb_hypothesis'Give the patient erythromycin.'. c_rule'The patient has nasal congestion.', 95, , 'The patient is breathing through the mouth.',yes . c_rule'The patient has a sore throat.', 95, , and, 'The patient is coughing.',yes ,
Authors manuscript
Sec. 10.8.
341
c_rule'The patient has a sore throat.', 90, , 'The inside of the patient''s throat is red.',yes . c_rule'The patient has a sore throat.', 75, , 'The patient is coughing.',yes . c_rule'The patient has chest congestion.', 100, , 'There are rumbling sounds in the chest.',yes . c_rule'The patient has allergic rhinitis.', 85, , and, 'The patient has nasal congestion.',yes , and, 'The patient has a sore throat.',no , 'The patient has chest congestion.',no c_rule'The patient has strep throat.', 80, , and, 'The patient has nasal congestion.',yes , 'The patient has a sore throat.',yes . c_rule'The patient has pneumonia.', 90, , and, 'The patient has chest congestion.',yes , and, 'The patient has nasal congestion.',yes , 'The patient has a sore throat.',no . c_rule'The patient has pneumonia.', 75, , and, 'The patient has chest congestion.',yes , 'The patient has nasal congestion.',yes . c_rule'Give the patient an antihistamine.', 100,
.
Authors manuscript
342
Chap. 10
c_rule'Give the patient a decongestant.', 100, 'The patient has nasal congestion.' , 'The patient has high blood pressure.',no . c_rule'Give the patient penicillin.', 100, 'The patient has pneumonia.' , 'The patient is allergic to penicillin.',no . c_rule'Give 100, 'The 'The and, the patient penicillin.', patient has pneumonia.', patient is allergic to penicillin.' , 'The patient is in critical condition.',yes , 'The patient is allergic to tetracycline.',yes
.
c_rule'Give the patient tetracycline.', 100, 'The patient has pneumonia.', -,'Give the patient penicillin.' , 'The patient is allergic to tetracycline.',no . c_rule'Give the patient erythromycin.', 100, 'The patient has strep throat.' , 'The patient is allergic to erythromycin.',no . kb_can_ask'The patient has nasal congestion.'. kb_can_ask'The patient has chest congestion.'. kb_can_ask'The patient has a sore throat.'. kb_can_ask'The patient has high blood pressure.'. kb_can_ask'The patient is allergic to penicillin.'. kb_can_ask'The patient is allergic to erythromycin.'. kb_can_ask'The patient is allergic to tetracycline.'. kb_can_ask'The patient is breathing through the mouth.'. kb_can_ask'The patient is coughing.'. kb_can_ask'The inside of the patient''s throat is red.'. kb_can_ask'There are rumbling sounds in the chest.'. kb_can_ask'The patient is in critical condition.'. :- write' Type conman. to start.'.
Authors manuscript
Sec. 10.8.
343
Any previously defined clauses for the predicates KB_INTRO, KB_THRESHOLD, KB_HYPOTHESIS, C_RULE, and KB_CAN_ASK should be removed from the knowledge base before loading the clauses below.
abolishkb_intro 1. abolishkb_threshold 1. abolishkb_hypothesis 1. abolishc_rule 4. abolishkb_can_ask 1.
kb_intro '', 'Feeding Your Pet:', 'A Whimsical Knowledge Base for CONMAN', '' . kb_threshold64. kb_hypothesis'Your pet is a carnivore.'. kb_hypothesis'Your pet is a herbivore.'. kb_hypothesis'Feed your pet nerds.'. kb_hypothesis'Feed dog food to your pet.'. kb_hypothesis'Feed your aunt''s fern to your pet.'. kb_hypothesis'My pet is a carnivore. Feed your pet to my pet.'. c_rule'Your pet is a carnivore.', 100, , and, 'Your pet has sharp claws.',yes , 'Your pet has sharp, pointed teeth.',yes c_rule'Your pet is a carnivore.', 85,
.
Authors manuscript
344
Chap. 10
c_rule'Your pet is a herbivore.', 100, , and, 'Your pet has sharp claws.',no , 'Your pet has sharp, pointed teeth.',no c_rule'Your pet is a carnivore.', 85, , 'Your pet has sharp, pointed teeth.',yes .
.
c_rule'Feed your pet nerds.', 100, 'Your pet is a carnivore.' , or, 'Nerds are available at a low price.',yes , 'Dog food is available at a low price.',no . c_rule'Feed dog food to your pet.', 100, 'Your pet is a carnivore.' , and, 'Dog food is available at a low price.',yes , 'Nerds are available at a low price.',no . c_rule'Feed your aunt''s fern to your pet.', 100, 'Your pet is a herbivore.' , 'Your aunt has a fern.',yes . c_rule'My pet is a carnivore. Feed your pet to my pet.', 100, 'Your pet is a herbivore.' , 'Your aunt has a fern.',no . kb_can_ask'Nerds are available at a low price.'. kb_can_ask'Dog food is available at a low price.'. kb_can_ask'Your pet has sharp claws.'. kb_can_ask'Your pet has sharp, pointed teeth.'. kb_can_ask'Your aunt has a fern.'. :- write'Type conman. to start.'.
Authors manuscript
Sec. 10.9.
No condence in condence
345
Authors manuscript
346
Chap. 10
know that penguins dont y. This means we have a rule about birds which we know has exceptions. If we are asked to build a cage for a large bird, we will plan to put a roof on it. If we learn that the bird is a penguin, we change our plans and omit the roof. We do not reason that we are 90 condent that birds y, but we are 100 condent that penguins dont y, and then use these two numbers in deciding whether to provide a roof. We dont even consider leaving off the roof until we learn that the bird is a penguin; then we dont even consider putting a roof on. The rules appear to be activated by the nature of the information in them, not by numbers on a scale. Despite all our complaints, it cannot be denied that there is something attractive about condence factors. Some impressive systems have been built that use them. Furthermore, we may have no choice but to use them to reason about uncertainty if no good alternative can be found. And of course most condence-factor inference engines are more sophisticated than CONMAN. In the next chapter we will look at one system that can reason with uncertain or incomplete information without using condence factors.
Authors manuscript
Chapter 11
Defeasible Prolog
The rst clause says that something ies if it is a bird and we cannot prove that it is a penguin. The second says that Chilly is a bird. With this knowledge base, the query
?- flieschilly.
347
Authors manuscript 693 ppid September 9, 1995 Prolog Programming in Depth
348
Defeasible Prolog
Chap. 11
succeeds. But if we add the fact penguinchilly, the same query will then fail because we are no longer unable to prove that Chilly is a penguin. With more information, Prolog rejects a goal it earlier accepted. People often accept and use rules of thumb like "Birds y," principles that they realize have exceptions. We use these rules, perhaps even unconsciously, unless we have reason to believe we are dealing with a case where the rule does not apply. We do not usually list all of the exceptions as part of the rule, and we realize that there may be many exceptions we dont know about. However, we can sometimes represent such rules in Prolog using negation-as-failure to list the exceptions. We shall see that this method will not work in all cases.
and we might represent the expectation that Fox will win if Wolf doesnt by the rule
will_be_electedfox :+ will_be_electedwolf.
But this doesnt really capture the opinions expressed. First, the original statement was a presumption which might be defeated, but there is no way we can avoid the conclusion that Wolf will win the election once we have the fact about Wolf in our database. Second, there isnt much point in having the rule about Fox at all since its condition can never be satised so long as we have the fact about Wolf. Perhaps our mistake is in the way we have represented the presumption. Instead of translating it into a Prolog fact, perhaps it should be a rule. But what rule? What is the condition for this rule? Maybe it should be
will_be_electedwolf :+ will_be_electedfox.
Authors manuscript
Sec. 11.2.
349
Now the two rules about Wolf and Fox have exactly the same form and the same strength, but our presumption was that Wolf will be elected. Furthermore, the two rules taken together will cause Prolog to loop endlessly because each of them always calls the other when Prolog is given the query
?- will_be_electedX.
Lets add some detail to our example. Suppose our political prognosticator goes on to say, "Of course, Wolf wont win the election if Bull withdraws and throws his support to Fox." How shall we represent this in Prolog? The obvious way to do it is
+ will_be_electedwolf :- withdrawsbull, supportsbull,fox.
But this is also obviously wrong since it is not a well-formed Prolog clause. The built-in predicate + cannot occur in the head of a Prolog clause. Typically, a Prolog interpreter will understand this rule as an attempt to redene the predicate +. Some interpreters will display an error message saying that this is not allowed. What we need is a new way to represent defeasible rules and presumptions and an inference engine that knows how to use them. We also need a negation operator that is different from negation-as-failure so we can represent rules that tell us when something is positively not the case rather than just that we cannot prove that it is the case. These negative rules are needed to tell us when we have an exception to a defeasible rule, but they are desirable in their own right as well. Its easy to represent defeasible rules. We arbitrarily pick a new operator to join the head and the body of the rule. We will use the operator := 2. With this operator, we represent the defeasible rule that birds y as
fliesX := birdX.
We will also introduce the negation operator neg 1. Unlike +, neg can appear in a the head of a rule. Thus, we represent the claim that Wolf will not be elected if Bull withdraws from the election and throws his support to Fox as
neg will_be_electedwolf := withdrawsbull, supportsbull,fox.
Now comes the challenging part: building an inference engine that knows how to use these rules properly. Before we can do this, we must investigate further how reasoning with defeasible rules works. Representing presumptions requires a bit more thought than representing ordinary defeasible rules. A presumption looks something like a defeasible fact. In Prolog the fact
yellowsun.
The same relationship should hold between presumptions and defeasible rules. We will represent the presumption that Wolf will be elected by the defeasible rule
Authors manuscript
350
will_be_electedwolf := true.
Defeasible Prolog
Chap. 11
This allows us to distinguish between presumptions and facts. There is one other kind of statement that is important in understanding our use of defeasible rules. These are statements like "Wolf might not win if there is a poor voter turn-out." How do these might rules work? If there is a poor voter turn-out, does it follow that Wolf does not win? No. The rule is too weak to support this conclusion. But if there is a poor voter turn-out, our presumption that Wolf will win gets defeated. Then the inference engine should conclude neither that Wolf will win nor that Wolf will not win. The sole purpose of these might rules is to interfere with conclusions that we might otherwise draw. They do not themselves allow us to draw any new conclusions. This points out that there are two different ways that a defeasible rule can be defeated. First, we can have an ordinary Prolog rule or another defeasible rule that supports the opposite conclusion. When this happen, we say that the second rule rebuts the rst rule. But thats not what is happening with these rules that use the word might. They do not support the opposite conclusion or, for that matter, any conclusion. They simply point out a situation in which the rules they defeat might not apply. Rather than rebut, they undercut the rules they attack. So a defeasible rule might be either rebutted or undercut. Of course, two defeasible rules with opposite conclusions offer rebuttals for each other. It remains to be seen under what circumstances these rebuttals are successful. The other kind of defeater, the might defeater that does not itself support any conclusion, we will call an undercutting defeater or simply a defeater. Of course, we will need some way to represent defeaters as well as defeasible rules and presumptions. We will use a new operator :^ to connect the head and the body of a defeater. We represent the defeater in our election example as
neg will_be_electedwolf :^ voter_turn_out_is_poor.
We want an inference engine that recognizes defeasible rules, presumptions, defeaters and negations using neg, and knows how to use them. To make the work of the defeasible inference engine easier, we will place some restrictions on the new kinds of rules we are introducing. We will not allow the use of disjunctions (;), cuts (!), or negation-as-failure ( +) in any of these rules. Then we dont have to tell our inference engine what to do when it nds these in a rule. By restricting the use of cuts and negation-as-failure, we restrict procedural control of the search for solutions and make it difcult to adopt a procedural style while using defeasible rules and defeaters. We assume that the procedural parts of our programs will still be written in ordinary Prolog. The use of disjunction in a defeasible reasoning system, on the other hand, raises special problems that we will not consider here.
Authors manuscript
Sec. 11.3.
Strict rules
351
kind are also members of another are nondefeasible or strict rules. Other examples include Gold is a metal and Bats are mammals. We wont go into exactly what it means to say that something is a natural kind, and it isnt quite right to say that bird is part of the denition of the word penguin, but the general idea should be clear enough. Other examples of a strict rules are Squares are rectangles and Marxists are communists. These rules are strict quite literally by denition. Of course, Marxists and communists arent natural kinds (nor are squares and rectangles on most accounts), but thats why the relations between the terms are literally denitions. These are groups that man designed rather than groups found naturally. Some other strict rules involve spatial and temporal regularities, e.g., Every living person born in Atlanta was born in Georgia and Every living person born before 1950 is more than forty years old today. Besides the kinds of rules listed here, most other rules are defeasible. Even strong causal claims like Litmus paper turns red when it is dipped in acid should be treated as defeasible, as should strong regularities like Members of the John Birch Society are conservative. No causal generality can ever be completely conrmed, and of course a group like the John Birch Society might be inltrated by the A.C.L.U. or some other liberal group. We will represent strict rules as ordinary Prolog rules. Whenever the antecedent condition of a strict rule is derivable, so is its consequent. But suppose the condition of a strict rule is only defeasibly derivable? Suppose, for example, that we have evidence that some animal is a bat and we also have evidence that it is a bird, but our evidence for neither claim is conclusive. Then the conditions for the two strict rules
mammalX :- batX. neg mammalX :- birdX.
are both defeasibly derivable in the case of our mystery animal. What should we conclude? If our strict rules are correct, then some of our evidence must be incorrect. But we cant tell whether it is our evidence that the animal is a bat or our evidence that it is a bat that is awed. We do know, though, that the animal cant both be a mammal and not be a mammal. In this situation, we should refrain from drawing any conclusion about whether the animal is a mammal. We may still be in the peculiar situation of concluding at least tentatively that it is both a bat and a bird, but we do a certain amount of damage control by stopping short of the blatant contradiction that it both is and is not a mammal. There are two lessons to be learned from this example. First, we have to distinguish between what is strictly derivable and what is only defeasibly derivable. A conclusion is strictly derivable if it is derivable from facts and strict rules alone. We will say that any conclusion that is strictly derivable is also (at least) defeasibly derivable, but other conclusions that depend on presumptions and defeasible rules are also defeasibly derivable. Second, to limit the damage when we have conicting evidence for competing strict rules, we will allow strict rules to be rebutted by other strict rules in at least those cases where the condition for the rebutted strict rule is only defeasibly derivable.
Authors manuscript
352
Born in U.S.A.
Defeasible Prolog
Chap. 11
Born in Pennsylvania
Hans
Figure 11.1
What about cases where strict rules compete with defeasible rules? Lets consider another example. Pennsylvania Dutch is a dialect of German. Ordinarily, native speakers of Pennsylvania Dutch are born in Pennsylvania. Of course, anyone born in Pennsylvania was born in the United States. But typically, native speakers of a German dialect are not born in the United States. Hans speaks Pennsylvania Dutch. We represent all this information as follows;
native_speakerX,german_dialect :- native_speakerX,pa_dutch. bornX,pennsylvania := native_speakerX,pa_dutch. bornX,usa :- bornX,pennsylvania. neg bornX,usa := native_speakerX,german_dialect. native_speakerhans,pa_dutch.
We can also represent this information in a graph, as we have done in Figure 11.1 using ! for strict rules and for defeasible rules. We use bars through the arrows to show when the negation of the term at the head of the arrow is indicated by the rule. First, we notice that Hans must denitely be a native speaker of a German dialect. Second, we have evidence that Hans was born in Pennsylvania and no evidence to the contrary. So we conclude defeasibly that Hans was in fact born in Pennsylvania. But now we have conicting evidence in the strict rule saying Hans was born in the United States and the defeasible rule saying that he was not. Even though the condition of the strict rule is only defeasibly derivable while the condition of the defeasible rule is strictly derivable, the strict rule is superior. So we conclude defeasibly that Hans was born in the United States. Another way to put this is to strengthen our previous observation: a strict rule can only be defeated if its condition is only defeasibly derivable, and then only by a fact or by another strict rule that rebuts it.
Authors manuscript
Sec. 11.4.
Incompatible conclusions
353
This set of d-Prolog rules and facts certainly captures the incompatibility of being both a Marxist and a capitalist. But consider what would happen if we asked whether we could defeasibly derive that Ping is a capitalist. Since she owns a restaurant, we have evidence that she is a capitalist. But the rule we would use is defeasible; so we must make sure that it is not defeated. We nd the competing rule that says she is not a capitalist if she is a Marxist. This will defeat our rule unless we can show that there is no way to derive that Ping is a Marxist, i.e., that the rule is not satised. But since Ping was born in the Peoples Republic of China, we have evidence that she is a Marxist. Of course, this also depends on a defeasible rule; so we will still be okay if we can show that this rule is defeated. We have a competitor in the strict rule that says that capitalists are not Marxists. So now we need to show is that Ping is a capitalist in order to show that Ping is a capitalist! We are in a loop and have no way to get out. Our solution to this problem is similar to the solution to the problem of using biconditionals discussed in Chapter 6. We will introduce a predicate incompatible 2 which will tell d-Prolog when two clauses that are not complements of each other are nevertheless incompatible. So in our examples, we would use the clauses
incompatiblebirdX,mammalX.
and
Authors manuscript
354
incompatiblemarxistX,capitalistX.
Defeasible Prolog
Chap. 11
When we implement our defeasible logic, we must tell the d-Prolog inference engine how to use this information without looping. We say two clauses Clause1 and Clause2 are contraries in a d-Prolog database if one is the complement of the other or if either of the two clauses
incompatibleClause1,Clause2.
or
incompatibleClause2,Clause1.
is in the database. We say that two rules compete or conict with each other and that they are competitors if their heads are contraries. These denitions are implemented in Prolog as follows:
contraryClause1,Clause2 :- incompatibleClause1,Clause2. contraryClause1,Clause2 :- incompatibleClause2,Clause1. contraryClause1,Clause2 :- compClause1,Clause2. compneg Atom,Atom :- !. compAtom,neg Atom.
We say Rule1 is inferior to Rule2 if Rule2 is superior to Rule1. The importance of this notion to our defeasible logic is that a defeasible rule cannot be rebutted by an inferior defeasible rule. In our example, the Marxist rule is rebutted by the capitalist rule, but the capitalist rule is not rebutted by the Marxist rule. So we conclude that capitalistping is defeasibly derivable. Similarly, a defeasible rule cannot be undercut by an inferior defeater. For example, people normally can walk. But if someone is very young, he or she might not yet walk.
Authors manuscript
Sec. 11.6.
Specicity
355
Capitalist Marxist
Owns restaurant
Born in P.R.C.
Ping
Figure 11.2
But suppose we incorporate into this same database information about animals other than humans. Antelope, for example, can walk within hours of birth. We would not want our defeater for young creatures to undercut the rule for deer.
walksX := deerX. supwalksX := deerX,neg walksX :^ infantX.
So the rule about normal deer locomotion is superior to the defeater about infant locomotion and is not undercut by it. But the rule about human locomotion is not superior to the defeater and is therefore undercut by it.
11.6. SPECIFICITY
Sometimes we can tell that one rule is superior to another without being told. Consider the following example.
fliesX := birdX. neg fliesX := penguinX. birdX :- penguinX. penguinopus.
Is Opus a bird? Denitely. Does Opus y? Apparently, not. An example like this has been around for some time involving a bird named Tweety. The corresponding graph is shown in Figure 11.3. Because of the shape of the graph, this kind of example is often called a Tweety Triangle. Remember that defeasible rules tell us about the normal or typical cases. Typical birds y. But a penguin is a special kind of bird. Because of our strict rule, we immediately know that something is a bird as soon as we know that it is a penguin.
Authors manuscript
356
Flies
Defeasible Prolog
Chap. 11
Bird
Penguin
Opus
Figure 11.3
A Tweety Triangle.
The converse, of course, is not true: many birds are not penguins. So any rule for penguins is more specic than any rule for birds. Penguin rules take into account more information than do bird rules. When one rule is more specic than another, the rst rule is superior to the second. How can we tell that penguin rules are more specic than bird rules? By noticing that the strict rule
birdX :- penguinX.
is in our database. But the connection between birds and penguins might have been less direct and it might have involved defeasible rules as well as strict rules. Here is another example.
conservativeX := affiliateX,chamber_of_commerce. neg conservativeX := theatrical_agentX. busnesspersonX := theatrical_agentX. affiliateX,chamber_of_commerce := businesspersonX. theatrical_agentTaylor.
In this case, the rule for nonconservativism is more specic than and therefore superior to the rule for conservativism. Thats because we have a chain of rules linking the condition for nonconservativism (being a theatrical agent) to the condition for conservativism (being a business person). Put another way, we can defeasibly derive that Taylor is an afliate of the Chamber of Commerce from her activities as a theatrical agent, but we cannot defeasibly derive how she makes her living from her membership in the Chamber of Commerce. It is not enough to establish specicity that we have rules linking the condition of one competing rule to the condition of another. If those links are defeasible, then they must be undefeated or specicity cannot be inferred. As an example of what can go wrong, assume that college students are normally adults and that normally adults are employed, but that college students normally are not employed. Furthermore, employed persons typically are self-supporting while college students typically are not self-supporting. Finally, we stipulate that Jane is a college student who is employed. Does Jane support herself? The rules and facts relevant to this example are:
Authors manuscript
Sec. 11.6.
Specicity
357
Self-supporting
Jane
Figure 11.4
adultX := college_studentX. neg employedX := college_studentX. neg self_supportingX := college_studentX. employedX := adultX. self_supportingX := employedX. college_studentjane. employedjane.
The corresponding graphic representation is shown in Figure 11.4. We have two conicting rules, one for college students and another for employed persons. Is either more specic than the other? Since college students are normally adults and adults are normally employed, it might appear at rst glance that the rule for college students is more specic than the rule for employed persons. However, rules about college students are not really more specic than rules about employed persons because the link from college students to employed persons is itself defeated. Or to put it differently, we cannot defeasibly derive that Jane is employed from her status as a college student because we have a rule that says college students normally are not employed. This rule is more specic than the rule for adults which occurs in the path from college students to employed persons. So neither rule about self-support is more specic, and each rule is rebutted by the other. This points out a crucial fact about our defeasible reasoning. Most of the time, we are using everything we know as the basis for our conclusions. But sometimes, when we are trying to gure out which rules are more specic, we use only the information in the conditions of the rules together with other rules that may link these. When we do this, we ignore any facts or presumptions we may have available and rely only on the conditions of the two rules being compared and the other rules available to us. For example, part of the information we have in the college student example is the fact that Jane is employed. If we are allowed to use this, then we
Authors manuscript
358
Defeasible Prolog
Chap. 11
can infer that Jane is employed from any other information at all. But that certainly doesnt mean that it follows even defeasibly from Janes being a college student that she is employed. In summary, then, at any given time we may be reasoning from part of our information. We will call these different sets of information knowledge bases. Normally we will be reasoning from the entire d-Prolog database. We will call this the root knowledge base. When we test to see whether one rule is more specic than another, we limit ourselves to the clauses in the condition of one of the two rules being compared together with all the strict rules, defeasible rules, and defeaters in the root knowledge base. We leave out any facts or presumptions in the root knowledge base during this part of our reasoning. Remembering that the strict rules, defeasible rules, and defeaters from the root knowledge base get added in, we can think of each condition of a rule as itself determining a knowledge base. It will be important in designing our inference engine to remember that we must always keep track of the knowledge base in use at each stage of reasoning.
Authors manuscript
Sec. 11.6.
Specicity
359
Succeeds if Goal is derivable from the ordinary Prolog facts and rules in the d-Prolog knowledge base.
strict_derroot,Goal : A goal is derivable from the complete d-Prolog knowledge base if and only if it succeeds. !, callGoal. strict_derKB,First,Rest : A conjunction is strictly derivable if and only if both of its conjuncts are strictly derivable. !, strict_derKB,First, strict_derKB,Rest. strict_derKB,Goal : A goal is strictly derivable from a knowledge base other than the complete knowledge base if it is a contained in that knowledge base. conjunctGoal,KB. strict_derKB,Goal : A goal is strictly derivable from a knowledge base other than the complete knowledge base if it is the head of an ordinary Prolog rule in the complete knowledge base whose nonempty condition is strictly derivable from that knowledge base. clauseGoal,Condition, Condition == true, strict_derKB,Condition. def_der+KB,+Goal Succeeds if Goal is defeasibly derivable from the knowledge base KB. def_der_,true :- !. `true' is defeasibly derivable from any knowledge base. def_derKB,First,Rest : A conjunction is defeasibly derivable if and only if both conjuncts are defeasibly derivable. !, def_derKB,First, def_derKB,Rest.
Authors manuscript
360
Defeasible Prolog
Chap. 11
def_der_,Goal :predicate_propertyGoal,built_in, + functorGoal,',',_, A goal with a built-in functor is defeasibly derivable if and only if it succeeds. The test used here is for Quintus Prolog. This test may be different for another version of Prolog. !, Goal. def_derKB,Goal : A goal is defeasibly derivable if it is strictly derivable. strict_derKB,Goal. def_derKB,Goal : A goal is defeasibly derivable if it is the head of an ordinary Prolog rule whose condition is defeasibly derivable and which is not rebutted. clauseGoal,Condition, Condition == true, def_derKB,Condition, not_rebuttedKB,Goal :- Condition. def_derKB,Goal : A goal is defeasibly derivable if it is the head of a defeasible rule whose condition is defeasibly derivable and which is neither rebutted nor undercut. def_ruleKB,Goal := Condition, def_derKB,Condition, not_rebuttedKB,Goal := Condition, not_undercutKB,Goal := Condition. def_derKB,Goal :preemption, If defeater preemption is enabled, then a goal is defeasibly derivable if it is the head of a defeasible rule whose condition is defeasibly derivable, provided every rebutting or undercutting defeater for that rule is itself rebutted by a strict rule or a superior defeasible rule. def_ruleKB,Goal := Condition, + contraryGoal,Contrary1, strict_derKB,Contrary1, def_derKB,Condition, + contraryGoal,Contrary2, clauseContrary2,Condition2, Condition2 == true,
Authors manuscript
Sec. 11.6.
Specicity
361
def_derKB,Condition2, + contraryGoal,Contrary3, def_ruleKB,Contrary3 := Condition3, def_derKB,Condition3, + preemptedKB,Contrary3 := Condition3, + contraryGoal,Contrary4, Contrary4 :^ Condition4, def_derKB,Condition4, + preemptedKB,Contrary4 :^ Condition4. contrary+Clause1,-Clause2 Discovers Clause2 which either is the complement of Clause1 or is incompatible with Clause1. contraryClause1,Clause2 :incompatibleClause1,Clause2. contraryClause1,Clause2 :incompatibleClause2,Clause1. contraryClause1,Clause2 :compClause1,Clause2. comp+Clause1,-Clause2 Succeeds if Clause1 is neg Clause2 or if Clause2 is is neg Clause1. compneg Atom,Atom :!. compAtom,neg Atom. not_rebutted+KB,+Rule Succeeds if + rebutted+KB,Rule succeeds. This predicate is added to introduce a cut in a way that reduces the number of duplicate solutions. not_rebuttedKB,Rule :+ rebuttedKB,Rule, !. rebutted+KB,+Rule Succeeds if the Rule is defeated in the knowledge base KB by a rebutting defeater an ordinary
Authors manuscript
362
Defeasible Prolog
Prolog rule or a defeasible rule to which the Rule is not superior.
Chap. 11
rebuttedKB,Rule : Any rule is rebutted if a contrary of its head is strictly derivable. Rule =.. _,Head,_ , contraryHead,Contrary, strict_derKB,Contrary. rebuttedKB,Rule : Any rule may be rebutted by an ordinary Prolog rule with a contrary head. Rule =.. _,Head,_ , contraryHead,Contrary, clauseContrary,Body, Body == true, def_derKB,Body. rebuttedKB,Head := Body : Defeasible rules may be rebutted by other defeasible rules with contrary heads. contraryHead,Contrary, def_ruleKB,Contrary := Condition, def_derKB,Condition, + sup_ruleHead := Body,Contrary := Condition. not_undercut+KB,+Rule Succeeds if + undercut+KB,Rule succeeds. This predicate is added to introduce a cut in a way that reduces the number of duplicate solutions. not_undercutKB,Rule :+ undercutKB,Rule, !. undercut+KB,+Rule Succeeds if the Rule is defeated in the knowledge base KB by an undercutting defeater. undercutKB,Head := Body : Only defeasible rules may be undercut by pure defeaters. contraryHead,Contrary, Contrary :^ Condition, def_derKB,Condition,
Authors manuscript
Sec. 11.6.
Specicity
363
+ sup_ruleHead := Body,Contrary :^ Body, !. sup_rule+Rule1,+Rule2 Succeeds if the body of Rule2 is defeasibly derivable from the body of Rule1, but the body of Rule1 is not defeasibly derivable from the body of Rule2. The user can also force superiority by adding clauses for the predicate `sup'. sup_ruleRule1,Rule2 :supRule1,Rule2. sup_rule_ := Body1,_ := Body2 :def_derBody1,Body2, + def_derBody2,Body1. sup_rule_ := Body1,_ :^ Body2 :def_derBody1,Body2, + def_derBody2,Body1. conjunct+Clause1,+Clause2 conjunctClause,Clause :- !. conjunctClause,Clause,_ :- !. conjunctClause,_,Rest :conjunctClause,Rest. def_rule+KB,+Rule Succeeds if KB is the entire d-Prolog knowledge base and Rule is any defeasible rule in KB, or if Rule is a defeasible rule in the d-Prolog knowledge base and the body of Rule is not the atom `true'. def_ruleroot,Head := Body :!, Head := Body. def_rule_,Head := Body :Head := Body, Body == true.
Authors manuscript
364
Defeasible Prolog
Chap. 11
preempted+KB,+Rule Succeeds if the Rule is defeated in the knowledge base KB by a superior rebutting defeater an ordinary Prolog rule or a defeasible rule which is superior to the Rule. preemptedKB,Rule : Any rule is preempted if a contrary of its head is strictly derivable. Rule =.. _,Head,_ , contraryHead,Contrary, strict_derKB,Contrary, !. preemptedKB,Rule : Any rule may be preempted by an ordinary Prolog rule with a contrary head. Rule =.. _,Head,_ , contraryHead,Contrary, clauseContrary,Body, Body == true, def_derKB,Body. preemptedKB,Head := Body : Defeasible rules may be preempted by superior defeasible rules with contrary heads. contraryHead,Contrary, def_ruleKB,Contrary := Condition, def_derKB,Condition, sup_ruleContrary := Condition,Head := Body, !. preempt Toggles the preemption of defeaters feature between enabled and disabled. preempt :retractpreemption, !, write'Preemption is disabled.', nl. preempt :assertpreemption, write'Preemption is enabled.', nl. :- abolishpreemption 0. :- consult'dputils.pl'.
Authors manuscript
Sec. 11.7.
365
to succeed if the defeasible inference engine can derive Goal from the database, including defeasible rules, presumptions, defeaters and negations with neg. If the inference engine cannot prove Goal using these resources, we will want the query to fail. Basically, then, the inference engine must dene the predicate @. We will make @ a prex operator so we can express the above query without parentheses as
?- @ Goal.
In fact, we dene four new operators := 2, :^ 2, neg 1, and @ 1 using the built-in predicate op 3 discussed in Chapter 6. File DPROLOG.PL thus begins with a procedure init 0 containing the op declarations, followed immediately by a query invoking it. If the op declarations were not executed at this time, Prolog would be unable to understand the rest of the clauses in the le. (Some implementations may require moving the op declarations outside of init to get them recognized at the proper time.) Remember that a literal may be either strictly or defeasibly derivable either from the complete d-Prolog database (the root knowledge base) or from the literals in the condition of some rule (together with the strict rules, defeasible rules, and defeaters in the root knowledge base). The query
?- @ Goal.
succeeds if Goal is at least defeasibly derivable from the root knowledge base. We need another binary predicate that takes both the knowledge base and the goal as arguments. Thus, we dene @ by
@ Goal :- def_derroot,Goal.
Before dening def_der 2, we will attack the easier task of dening the predicate for strict derivability, strict_der 2. For the root knowledge base, strict derivability is just ordinary derivability in Prolog.
strict_derroot,Goal :- !, callGoal.
We include the cut because we do not want the inference engine to apply any later clauses upon backtracking since these are intended only for knowledge bases other than the root knowledge base. A literal is strictly derivable from a knowledge base KB if it is in KB or if it is the head of a strict rule in the root knowledge base whose condition is strictly derivable from KB. Since conditions may be conjunctions of literals, we will handle this case rst. A conjunction of literals is strictly derivable from KB if each conjunct is strictly derivable from KB.
Authors manuscript
366
strict_derKB,First,Rest :!, strict_derKB,First, strict_derKB,Rest.
Defeasible Prolog
Chap. 11
We put this clause rst and include a cut so the inference engine will not try to apply any of the clauses intended for literals to a complex condition before it has broken the condition into individual literals. For any knowledge base KB other than the root knowledge base, we have two cases left to consider. One is where the literal we are trying to derive is in KB and the other is where it is the head of a strict rule whose condition is strictly derivable from KB. We have a separate clause for each of these situations. Remember that any knowledge base other than the root knowledge base will be identied with the condition of some other rule. So it will be a conjunction of literals. A literal is in a conjunction of literals if it is one of the conjuncts.
strict_derKB,Goal :conjunctGoal,KB. strict_derKB,Goal :clauseGoal,Condition, Condition == true, strict_derKB,Condition.
We eliminate rules in the second clause above whose conditions are the built-in predicate true because these rules are the facts in the root knowledge base. Remember that if we dont do this, then any fact in the root knowledge base will be strictly derivable from any rule condition whatsoever. So, for example, if we know that Opus is both a bird and a penguin, we would be able to show that being a penguin follows from being a bird. Since we certainly dont want to be able to do that, we have the restriction. The auxilliary predicate conjunct 2 is easily dened.
conjunctClause,Clause :- !. conjunctClause,Clause,_ :- !. conjunctClause,_,Rest :- conjunctClause,Rest.
What we have done in dening strict derivability is to call the Prolog interpreter for the root knowledge base and to dene a Prolog interpreter in Prolog for other knowledge bases.
Authors manuscript
Sec. 11.8.
d-Prolog: preliminaries
367
Goal := true.
In using presumptions, an inference engine will eventually contend with the query ?- def_derKB,true. Of course, this query should succeed. So the rst clause in the denition of def_der 2 is simply
def_derKB,true :- !.
As with strict derivability, the inference engine will sometimes need to show that the condition of a rule is defeasibly derivable. To do this, it must break the condition down into its individual literals.
def_derKB,First,Rest :!, def_derKB,First, def_derKB,Rest.
When the inference engine encounters a built-in Prolog predicate, it must evaluate the goal containing that predicate using the built-in denition of the predicate regardless of the knowledge base upon which the derivation is based. The test for a built-in predicate used in the clause below is the correct test in Quintus Prolog. This test will may to be modied for other versions of Prolog.
def_der_,Goal :predicate_propertyGoal,built_in, + functorGoal,',',_, !, Goal.
Of course, a literal is at least defeasibly derivable from a knowledge base if it is strictly derivable from that knowledge base.
def_derKB,Goal :- strict_derKB,Goal.
The last clause before looking at defeasible rules denes how strict rules may be involved in defeasible derivations. Since strict rules cant be undercut, they apply if their conditions are at least defeasibly satised and they are not rebutted.
def_derKB,Goal :clauseGoal,Condition, Condition == true, def_derKB,Condition, not_rebuttedKB,Goal :- Conditional.
We eliminate strict rules with true as their condition since these are facts and cannot be used in deriving a literal from the condition of another rule in testing for specicity. Notice that in the last paragraph, we used a predicate not_rebutted rather than + rebutted. We dene this new predicate simply as
not_rebuttedKB,Rule :+ rebuttedKB,Rule, !.
Authors manuscript
368
Defeasible Prolog
Chap. 11
We do this because in some Prologs, + will succeed more than once, producing identical solutions to a defeasible query. In similar fashion, we use not_undercut below. How can a strict rule be rebutted? It is rebutted if a contrary of its head is strictly derivable (when we know conclusively that the head of the rule is false.) Otherwise, it can only be rebutted by another strict rule.
rebuttedKB,Rule :Rule =.. _,Head,_ , contraryHead,Contrary, strict_derKB,Contrary, !. rebuttedKB,Rule :Rule =.. _,Head,_ , contraryHead,Contrary, clauseContrary,Body, Body == true, def_derKB,Body.
Notice that these two clauses apply to all rules, including defeasible rules. Naturally, any set of circumstances that will rebut a strict rule will also rebut a defeasible rule.
To use this rule to conclude tentatively that Opus ies, we need to establish three things: 1. We conclude at least tentatively that Opus is a bird. 2. Our rule is not rebutted. 3. Our rule is not undercut. An initial attempt at representing this as a general principle in Prolog is simple. First we nd a defeasible rule whose head unies with our goal. Then we apply the three tests listed above.
def_derKB,Goal :def_ruleKB,Goal := Condition, def_derKB,Condition, not_rebuttedKB,Goal := Condition, not_undercutKB,Goal := Condition.
Authors manuscript
Sec. 11.9.
369
We must dene which rules belong in a knowledge base because we only use presumptions when we are reasoning from the root knowledge base. This gives us a simple denition of def_rule 2:
def_ruleroot,Head := Body :!, Head := Body. def_ruleKB,Head := Body :Head := Body, Body == true.
Of course, we need to specify the conditions in which a defeasible rule is rebutted or defeated. Recall from the last section that any rule, strict or defeasible, is rebutted if a contrary of its head is either strictly derivable or is the head of some strict rule that is at least defeasibly satised. But defeasible rules can also be rebutted by other competing, noninferior defeasible rules with contrary heads.
rebuttedKB,Head := Body :contraryHead,Contrary, def_ruleKB,Contrary := Condition, def_derKB,Condition, + sup_ruleHead := Body,Contrary := Condition.
We use a cut because we are never interested in knowing that there are different ways to rebut a rule. If it is rebutted at all, then it cannot be applied. One rule is superior to another if we have a clause for the predicate sup 2 that indicates this, or if the rst rule is more specic than the second. The later situation holds when the body of the second rule is defeasibly derivable from the body of the rst, but at least one literal in the body of the rst rule is not defeasibly derivable from the body of the second.
sup_ruleRule1,Rule2 :supRule1,Rule2. sup_ruleHead1 := Body1,Head2 := Body2 :def_derBody1,Body2, + def_derBody2,Body1.
Defeasible rules are undercut by defeaters in much the same way they are rebutted by other defeasible rules. The only difference is that a defeater does not support an argument for a contrary conclusion.
undercutKB,Head := Body :contraryHead,Contrary, Contrary :^ Condition, def_derKB,Condition, + sup_ruleHead := Body,Contrary :^ Body.
Authors manuscript
370
Defeasible Prolog
Chap. 11
Once again, the cut is used because we only need to know that there is at least one way that the rule is undercut. Since superiority also applies to defeaters, we need another clause for sup_rule 2:
sup_ruleHead1 := Body1,Head2 :^ Body2 :def_derBody1,Body2, + def_derBody2,Body1.
The question, of course, is whether we should conclude that Kim Basinger is a Republican. The intuitively correct answer is that she is not. The structure of the rules is perhaps easier to see in the graph in Figure 11.5. You will probably recognize this as an example of twin Tweety Triangles. When we look at these rules, we see that [3] is superior to [1] (since it is more specic) but not to [2], while [4] is superior to [2] (since it is more specic) but not to [1]. So neither of the two rules supporting the intuitively correct rule that Kim is not a Republican is superior to all competitors. How can we save the situation? Based on examples like this, we can insist that any defeasible rule that is itself rebutted by a superior defeasible rule loses its capacity to rebut any other rule. Then since [1] is rebutted by the superior rule [3] and [2] is rebutted by the superior rule [4], neither [1] nor [2] has the capacity to rebut either [3] or [4]. In this case, we say that the rules [1] and [2] have been preempted as defeaters. Perhaps a defeasible rule should be preempted as a potential defeater if it is either rebutted or undercut by a superior rule. This, however, appears to be too strong as the following modication of our original Tweety Triangle shows. Remember that birds normally y, that penguins normally dont y, and that penguins are birds. Now suppose we develop a genetically altered penguin with large wings and correspondingly large ight muscles. Can these genetically altered penguins
Authors manuscript
Sec. 11.10.
Preemption of defeaters
371
Republican
Rich
Kim
Figure 11.5
y? We should probably take the information that a particular penguin has this genetic alteration as a reason to suspend our usual judgment that it can not y. We are assuming, of course, that the genetic alteration envisioned is not so great that the creatures cease to be penguins. They are still still penguins albeit strange ones. Suppose Folio is one of these genetically altered penguins. (A folio is a kind of opus, isnt it?) In the graphic representation of this example (Figure 11.6), we use a wavy arrow for the defeater. The following is the d-Prolog representation of the same example:
fliesX := birdX neg fliesX := penguinX. fliesX :^ ga_penguinX. birdX :- penguinX. penguinX :- ga_penguinX. ga_penguinfolio.
The important point to note about this example is that we are inclined to withhold judgment about whether Folio can y; we are not inclined to infer even tentatively that he can y. But if the penguin rule were preempted by the undercutting defeater for genetically altered penguins, then it could no longer rebut the bird rule and we could then use the bird rule to infer that apparently Folio ies. Since this runs contrary to our intuitions about this kind of example, we conclude that potential defeaters are not preempted when they are only undercut by superior defeaters. They must actually be rebutted by a superior rule to be preempted. Putting all of this together we get another, rather complicated, clause for the predicate def_der 2:
def_derKB,Goal :1 preemption,
Authors manuscript
372
Flies
Defeasible Prolog
Chap. 11
Bird
Penguin
Folio
Figure 11.6
2 3 4 5
def_ruleGoal := Condition, + contraryGoal,Contrary1, strict_derKB,Contrary1, def_derKB,Condition, + contraryGoal,Contrary2, clauseContrary2,Condition2, Condition2 == true, def_derKB,Condition2, + contraryGoal,Contrary3, def_ruleKB,Contrary3 := Condition3, def_derKB,Condition3, + rebuttedKB,Contrary3 := Condition3, + contraryGoal,Contrary4, Contrary4 :^ Condition4, def_derKB,Condition4, + rebuttedKB,Contrary4 :^ Condition4.
We will examine these conditions one at a time. This clause is considerably more complex than our earlier clauses for applying defeasible rules. If we add it to our inference engine, Prolog will have much more work to do to check to see whether a literal is defeasibly derivable. At the same time, twin Tweety Triangles are relatively unusual. So we give the d-Prolog developer the option of enabling or disabling preemption of defeaters. If it is enabled, the clause preemption will be found in the database and condition [1] of our rule will be satised. Otherwise, condition [1] will fail and Prolog will ignore the rest of the clause. We dene a predicate preempt 0 which toggles between enabling and disabling preemption of defeaters, and by default preemption is disabled when d-Prolog is loaded.
preempt :-
Authors manuscript
Sec. 11.10.
Preemption of defeaters
373
retractallpreemption, !, write'Preemption is disabled.', nl. preempt :assertpreemption, write'Preemption is enabled.', nl. :- retractallpreemption.
Condition [2] nds any available defeasible rule that we might try to apply, condition [3] makes sure that no contrary of the head of the rule (after unifying with the goal) is strictly derivable, and condition [4] checks to see if the rule is satisable. The rest of our conditions check to make sure that the rule is not defeated in one of several ways. Condition [5] checks to see that the rule is not rebutted by any strict rule (which can never be preempted). Conditions [6] and [7] check to see that every defeasible rule or defeater that might otherwise rebut or undercut the rule is preempted. The denition for the auxiliary predicate preempted 2 is just like the denition for rebutted 2 except that in the last clause, the rebutting defeasible rule must be superior to the rule it preempts rather than just not inferior.
preemptedKB,Rule :Rule =.. _,Head,_ , contraryHead,Contrary, strict_derKB,Contrary, !. preemptedKB,Rule :Rule =.. _,Head,_ , contraryHead,Contrary, clauseContrary,Body, Body == true, def_derKB,Body. preemptedKB,Head := Body :contraryHead,Contrary, def_ruleKB,Contrary := Condition, def_derKB,Condition, sup_ruleContrary := Condition,Head := Body, !.
Authors manuscript
374
:- op1100,fx,@@. :- dynamic have_seen_predicate_before 2. :- dynamic dPrologDictionary 2.
Defeasible Prolog
Chap. 11
@@+Goal Tests to see if Goal is strictly or defeasibly derivable from the d-Prolog knowledge base. Goal may not contain an uninstantiated variable. @@ Goal :Goal =.. _|ArgumentList , memberArgument,ArgumentList, varArgument, write'Improper argument for @@.', nl, write'Argument contains an uninstantiated variable.', nl, !, fail. @@ Goal :strict_derroot,Goal, contraryGoal,Contrary, Contrary, !, write'definitely, yes -', nl, write'and definitely, no - contradictory.', nl. @@ Goal :strict_derroot,Goal, !, write'definitely, yes.', nl. @@ Goal :contraryGoal,Contrary, Contrary, !, write'definitely, no.', nl. @@ Goal :def_derroot,Goal, !, write'presumably, yes.', nl. @@ Goal :contraryGoal,Contrary, def_derroot,Contrary,
Authors manuscript
Sec. 11.10.
Preemption of defeaters
375
!, write'presumably, no.', nl. @@ _ :write'can draw no conclusion.', nl. member?X,?Y Succeeds if X is a member of the list Y. memberX, X|_ . memberX, _|Y :- memberX,Y. dlisting+Predicate Lists all ordinary Prolog clauses, defeasible rules, and defeaters in memory which have heads of the form Predicate...Arguments... or of the form neg Predicate...Arguments.... dlistingPredicate :listingPredicate, fail. dlistingPredicate :clauseneg Head,Body, functorHead,Predicate,_, pprintneg Head,' :-',Body, fail. dlistingPredicate :Head := Body, dfunctorHead,Predicate,_, pprintHead,' :=',Body, fail. dlistingPredicate :Head :^ Body, dfunctorHead,Predicate,_, pprintHead,' :^',Body, fail. dlistingPredicate :clauseincompatibleClause1,Clause2,Body, dfunctorClause1,Predicate,_, pprintincompatibleClause1,Clause2,' :-',Body, fail.
Authors manuscript
376
Defeasible Prolog
Chap. 11
dlistingPredicate :clauseincompatibleClause1,Clause2,Body, dfunctorClause2,Predicate,_, pprintincompatibleClause1,Clause2,' :-',Body, fail. dlistingPredicate :clausesupClause1,Clause2,Body, rule_functorClause1,Predicate,_, pprintsupClause1,Clause2,' :-',Body, fail. dlistingPredicate :clausesupClause1,Clause2,Body, rule_functorClause2,Predicate,_, pprintsupClause1,Clause2,' :-',Body, fail. dlisting_. dlist dlists all predicates in the d-Prolog dictionary. dlist :dPrologDictionaryPredicate,_, dlistingPredicate, fail. dlist. dfunctor+Clause,-Predicate,-Arity Returns the d-Prolog Predicate of Clause together with its Arity. dfunctorneg Clause,Predicate,Arity :functorClause,Predicate,Arity, !. dfunctorClause,Predicate,Arity:functorClause,Predicate,Arity. rule_functor+Rule,-Predicate,-Arity Returns the d-Prolog Predicate of the head of the Rule together with its Arity.
Authors manuscript
Sec. 11.10.
Preemption of defeaters
377
rule_functorHead :- _,Predicate,Arity :dfunctorHead,Predicate,Arity, !. rule_functorHead := _,Predicate,Arity :dfunctorHead,Predicate,Arity, !. rule_functorHead :^ _,Predicate,Arity :dfunctorHead,Predicate,Arity, !. pprint+Head,+Operator,+Body A formatting routine for printing ordinary Prolog clauses, defeasible rules, and defeaters. pprintHead,' :-',true :!, writeHead, write'.', nl. pprintHead,Operator,Clause :writeHead, writeOperator, nl, pprintClause. pprintFirst,Rest :!, write' ', writeFirst, write',', nl, pprintRest. pprintClause :write'
rescind+Predicate +Arity Removes all ordinary Prolog clauses, defeasible rules, or defeaters whose heads are of the form Predicate...Arguments... or of the form neg Predicates...Argument.... rescindPredicate Arity :abolishPredicate Arity, functorClause,Predicate,Arity, retractallClause, retractallneg Clause, retractallClause := _, retractallneg Clause := _, retractallClause :^ _, retractallneg Clause :^ _,
Authors manuscript
378
Defeasible Prolog
retractallincompatibleClause,_, retractallincompatible_,Clause, retractallincompatibleneg Clause,_, retractallincompatible_,neg Clause, retractallsupClause := _,_, retractallsup_,Clause := _, retractallsupneg Clause := _,_, retractallsup_,neg Clause := _, retractallsupClause :^ _,_, retractallsup_,Clause :^ _, retractallsupneg Clause :^ _,_, retractallsup_,neg Clause :^ _, retractalldPrologDictionaryPredicate,Arity.
Chap. 11
rescindall Removes from memory the entire d-Prolog knowledge base, provided it was loaded into memory using reload. rescindall :dPrologDictionaryPredicate,Arity, rescindPredicate Arity, fail. rescindall. dload+Filename Consults a file containing a d-Prolog knowledge base. dloadFilename :concat Filename,'.dpl' ,NewFilename, seeNewFilename, repeat, readTerm, execute_termTerm, remember_predicateTerm, add_to_memoryTerm, Term == end_of_file, seen, !. reload+Filename Reconsults a file containing a d-Prolog knowledge base. reloadFilename :concat Filename,'.dpl' ,NewFilename,
Authors manuscript
Sec. 11.10.
Preemption of defeaters
379
seeNewFilename, repeat, readTerm, execute_termTerm, rescind_previous_clausesTerm, add_to_memoryTerm, Term == end_of_file, retractallhave_seen_predicate_before_,_, seen, !. execute_term+Term During a dload or a reload, executes any queries read from a d-Prolog knowledge base. execute_term:- Goal :Goal, !, fail. execute_term_. add_to_memory+Clause Adds clauses to memory during a reload. add_to_memory:- _ :- !. add_to_memoryend_of_file :- !. add_to_memoryTerm :assertzTerm. remember_predicate+Term Adds new predicates to the dPrologDictionary during a dload. remember_predicate:- _ :- !. remember_predicateend_of_term :- !. remember_predicateRule :rule_functorRule,Predicate,Arity, add_to_dictionaryPredicate,Arity, !.
Authors manuscript
380
remember_predicatesupRule1,Rule2 :rule_functorRule1,Predicate1,Arity1, add_to_dictionaryPredicate1,Arity1, rule_functorRule2,Predicate2,Arity2, add_to_dictionaryPredicate2,Arity2, !. remember_predicateincompatibleGoal1,Goal2 :dfunctorGoal1,Predicate1,Arity1, add_to_dictionaryPredicate1,Arity1, dfunctorGoal2,Predicate2,Arity2, add_to_dictionaryPredicate2,Arity2, !. remember_predicateGoal :dfunctorGoal,Predicate,Arity, add_to_dictionaryPredicate,Arity. add_to_dictionary+Predicate,+Arity Adds a clause to dPrologDictionary 2 for Predicate and Arity if one is not already present.
Defeasible Prolog
Chap. 11
add_to_dictionaryPredicate,Arity :+ dPrologDictionaryPredicate,Arity, functorDummyGoal,Predicate,Arity, assertDummyGoal, retractDummyGoal, assertdPrologDictionaryPredicate,Arity. add_to_dictionary_,_. rescind_previous_clauses+Term Removes from memory all ordinary Prolog clauses, defeasible rules, or defeaters that were not loaded during the current invocation of reload and that, from the perspective of the d-Prolog inference engine, are clauses for the same predicate as Term. rescind_previous_clauses:- _ :- !. Query to execute. rescind_previous_clausesend_of_file :- !. rescind_previous_clausesHead :- _ :rescind_previous_clausesHead, !.
Authors manuscript
Sec. 11.10.
Preemption of defeaters
381
rescind_previous_clausesHead := _ :rescind_previous_clausesHead, !. rescind_previous_clausesHead :^ _ :rescind_previous_clausesHead, !. rescind_previous_clausesincompatibleX,Y :rescind_previous_clausesX, rescind_previous_clausesY, !. rescind_previous_clausessupRule1,Rule2 :rescind_previous_clausesRule1, rescind_previous_clausesRule2, !. rescind_previous_clausesClause :dfunctorClause,Predicate,Arity, + have_seen_predicate_beforePredicate,Arity, assertahave_seen_predicate_beforePredicate,Arity, rescindPredicate Arity, remember_predicateClause, !. rescind_previous_clauses_. dictionary Prints a list of all predicates and their arities that occur in a d-Prolog knowledge base loaded into memory using dload or reload. dictionary :dPrologDictionaryPredicate,Arity, writePredicate, write' ', writeArity, nl, fail. dictionary. contradictions Finds all contradictory goals for the d-Prolog knowledge base that succeed, displays them, and stores them as clauses for the predicate contradictory_pair 2.
Authors manuscript
382
contradictions :abolishcontradictory_pair 2, fail.
Defeasible Prolog
Chap. 11
contradictions :dPrologDictionaryPredicate,Arity, functorClause1,Predicate,Arity, clauseClause1,_, contraryClause1,Clause2, contradictions_auxClause1,Clause2, assertcontradictory_pairClause1,Clause2, writeClause1, write' - ', writeClause2, nl, fail. contradictions. contradictions_auxX,Y :+ contradictory_pairX,Y, + contradictory_pairY,X, X, Y, !. whynot+Goal Explains why Goal is not defeasibly derivable. whynotGoal :Goal, nl, write'Why not indeed!', nl, writeGoal, write' is strictly derivable!', nl. whynotGoal :@ Goal, nl, write'Why not indeed!', nl, writeGoal, write' is defeasibly derivable!', nl. whynotGoal :+ initial_evidenceGoal, nl, write'There are no rules in the database for ', nl, writeGoal, nl, write'whose antecedent is defeasibly derivable.', nl, fail. whynotGoal :setofpairRule,Defeater, obstructGoal,Rule,Defeater,
Authors manuscript
Sec. 11.10.
Preemption of defeaters
383
List, say_why_notList. obstructGoal,Goal :- Body,Opposite :clauseGoal,Body, @ Body, contraryGoal,Opposite, Opposite. obstructGoal,Goal :- Body,Opposite :clauseGoal,Body, @ Body, contraryGoal,Opposite, + Opposite, clauseOpposite,Tail, @ Tail. obstructGoal,Goal := Body,Opposite :def_ruleroot,Goal := Body, @ Body, contraryGoal,Opposite, Opposite. obstructGoal,Goal := Body,Opposite :- Tail :def_ruleroot,Goal := Body, @ Body, contraryGoal,Opposite, clauseOpposite,Tail, + Opposite, @ Tail. obstructGoal,Goal := Body,Opposite := Tail :def_ruleroot,Goal := Body, @ Body, contraryGoal,Opposite, def_ruleroot,Opposite := Tail, @ Tail, + sup_ruleGoal := Body,Opposite := Tail. obstructGoal,Goal := Body,Opposite :^ Tail :def_ruleroot,Goal := Body, @ Body, contraryGoal,Opposite, defeaterOpposite :^ Tail, @ Tail, + sup_ruleGoal := Body,Opposite :^ Tail. say_why_not+List displays a list of rules and defeaters for a failed goal.
Authors manuscript
384
say_why_not .
Defeasible Prolog
Chap. 11
say_why_not pairGoal :- Body,Opposite :- Tail|Rest :!, write'The antecedent of the strict rule', nl, nl, pprintGoal,' := ',Body, nl, write'is defeasibly derivable;', nl, write'but the condition of the strict rule', nl, nl, pprintOpposite,' :- ',Tail, nl, write'is also defeasibly derivable.', nl, nl, pause, say_why_notRest. say_why_not pairGoal :- Body,Opposite|Rest :!, write'The antecedent of the strict rule', nl, nl, pprintGoal,' :- ',Body, nl, write'is defeasibly derivable;', nl, write'but the condition of the strict rule', nl, nl, pprintOpposite, nl, write'is also strictly derivable.', nl, nl, pause, say_why_notRest. say_why_not pairGoal := Body,Opposite :- Tail|Rest :!, write'The antecedent of the defeasible rule', nl, nl, pprintGoal,' := ',Body, nl, write'is defeasibly derivable;', nl, write'but the condition of the strict rule', nl, nl, pprintOpposite,' :- ',Tail, nl, write'is also defeasibly derivable.', nl, nl, pause, say_why_notRest. say_why_not pairGoal := Body,Opposite := Tail|Rest :!, write'The antecedent of the defeasible rule', nl, nl, pprintGoal,' := ',Body, nl, write'is defeasibly derivable;', nl, write'but the condition of the defeasible rule', nl, nl, pprintOpposite,' := ',Tail, nl, write'is also defeasibly derivable.', nl, nl, pause, say_why_notRest. say_why_not pairGoal := Body,Opposite :^ Tail|Rest :!, write'The antecedent of the defeasible rule', nl, nl,
Authors manuscript
Sec. 11.10.
Preemption of defeaters
385
pprintGoal,' := ',Body, nl, write'is defeasibly derivable;', nl, write'but the condition of the defeater', nl, nl, pprintOpposite,' :^ ',Tail, nl, write'is also defeasibly derivable.', nl, nl, pause, say_why_notRest. say_why_not pairGoal := Body,Opposite|Rest :!, write'The antecedent of the defeasible rule', nl, nl, pprintGoal,' := ',Body, nl, write'is defeasibly derivable;', nl, write'but', nl, nl, pprintOpposite, nl, write'is strictly derivable.', nl, nl, pause, say_why_notRest. pause :nl,nl, write'Press any key to continue. ', get0_, nl,nl. initial_evidence+Goal Succeeds if there is a rule supporting Goal that is satisfied, i.e., whose body is at least defeasibly derivable. initial_evidenceGoal :clauseGoal,Body, @ Body. initial_evidenceGoal :def_ruleroot,Goal := Body, @ Body. concat+List,-String Concatenates a List of strings into a single String. concatList,String :concat_auxList,
,String.
Authors manuscript
386
concat_aux First|Rest ,Chars,String :nameFirst,List, appendChars,List,NewChars, !, concat_auxRest,NewChars,String.
Defeasible Prolog
Chap. 11
If not, the system tests to see if any contrary of the goal is strictly derivable. If so, the response to the user is
definitely, no.
If neither the goal nor any contrary is strictly derivable, the system checks to see if the goal or any contrary is defeasibly derivable. If so, the response is, respectively,
Authors manuscript
Sec. 11.12.
387
presumably, yes.
or
presumably, no.
We do not have to worry about getting both answers here since the only way that a goal and a contrary of that goal can be both defeasibly derivable is if both are strictly derivable. That case is handled by an earlier clause. Finally, the system may be unable to strictly or defeasibly derive either the goal or any contrary of the goal. Then the response to the user is
can draw no conclusion.
d-Prolog lists all clauses which d-Prolog recognizes as belonging to the predicate pred. So dlisting 1 is the d-Prolog equivalent of the built-in Prolog predicate listing. We need a special utility to list the d-Prolog clauses for a predicate because Prolog will rcognize the clauses
neg p. p := q. neg p :^ r. supp := q,neg p :^ r. incompatiblep,neg s.
as belonging, respectively, to the predicates neg 1, := 2, :^ 2, sup 2, and incompatible 2. Prolog will list none of these clauses in response to the query
?- listingp.
will list these clauses and any others in memory that belong to the predicate p from the perspective of d-Prolog. The denition of dlisting includes many clauses to cover all the possible cases, but there is nothing very difcult to understand about any of these clauses and we will not discuss them in detail. The pridicate dlisting also uses the auxiliary predicate pprint 1 to format rules. Another utility predicate, dlist 0, will perform a listing for all predicates in the d-Prolog dictionary. This dictionary is created by the two utilities dload 1 and reload 1 described in the next section.
Authors manuscript
388
Defeasible Prolog
Chap. 11
only the last defeasible rule in the le will be in memory after the reconsult. (Not all Prolog implementations behave this way, of course, but some do.) We solve this problem by developing our own reconsult utility reload 1 dened in DPUTILS.PL. The proper query to reconsult a d-Prolog le is
?- reloadfilename.
Here filename should not include an extension; reload expects the le to have the extension .DPL for d-Prolog. The utility reads terms from the specied le one by one. First it tries to execute them. This allows the user to include queries in a le which are executed whenever the le is consulted or reconsulted. Next, if the term is a clause and not a query, reload determines the d-Prolog predicate for the term. If it has not seen a term for the predicate before, it stores the predicate and its arity in a clause for the predicate have_seen_predicate_before 2 and it uses the utility predicate rescind 1 (see next section) to remove all d-Prolog clauses for the predicate from memory. It also stores the predicate and and its arity in a clause for the predicate dPrologDictionary. Finally the utility adds the clause to memory. It processes each term in the le in this manner until it encounters an end_of_file marker. Then it removes all clauses for have_seen_predicate_before from memory but leaves the d-Prolog dictionary in memory for later use by other utilities explained below. Consulting a le is a much simpler operation since nothing has to be removed from memory during a consult. But we nevertheless provide a d-Prolog utility dload 1 to consult d-Prolog les. The proper query to consult a d-Prolog le is
Authors manuscript
Sec. 11.14.
389
?- dloadfilename.
Once again, no extension should be included in filename and dload expects the le to have the extension .DPL. The only difference between using dload and consult for d-Prolog les is that dload builds the d-Prolog dictionary used by other d-Prolog utilities. The two utilities dload and reload take longer to consult or to reconsult a le than do the built-in utilities consult and reconsult. This is to be expected since they are doing more work.
where Pred is the name of some d-Prolog predicate loaded into memory using dload or reload, and Arity is the arity of that predicate. The rst utility to use the d-Prolog dictionary is the predicate dictionary 0. d-Prolog lists all predicates and their arities stored in the d-Prolog dictionary in response to the query ?- dictionary.
Of course, we will run into the same complication here that we do with the built-in predicate listing: many clauses which belong to the predicate Pred from the perspective of d-Prolog will belong to the predicates neg, :=, :^, sup, and incompatible from the perspective of Prolog. A predicate rescind 1 is dened in the le DPUTILS.PL which lls this gap. The query
?- rescindPred Arity.
will remove from memory all d-Prolog clauses which belong to the predicate Pred with arity Arity. A user may also want to eliminate all d-Prolog predicates from memory without removing the d-Prolog inference engine itself. The predicate rescindall 0,
Authors manuscript
390
Defeasible Prolog
Chap. 11
dened in DPUTILS.PL, removes all dPrologDictionary clauses and all clauses for predicates recorded in the d-Prolog dictionary. So all d-Prolog clauses loaded into memory using the d-Prolog utility predicates dload and reload can be removed from memory with a single, simple command.
fails. This is, after all, exactly what negation-as-failure means. The clause + Goal says that Goal cannot be proved, not that it is false. In d-Prolog we have implemented a positive negation operator neg. The only way the query
?- neg Goal.
can succeed is if there is a fact or rule in memory whose head unies with neg Goal. But nothing is simpler than to construct a contradictory program in d-Prolog. A simple example is:
p. neg p.
The rst example is contradictory since it says that the same atom is both true and false. The second is contradictory since it says both that two atoms are true and that they cannot both be true. A d-Prolog program containing a contradiction must contain some false statements. In general, a d-Prolog developer will not intend to incorporate contradictions into his or her program. They are more likely to occur as a result of the interactions of several rules. While d-Prolog is expressively more powerful because contradictions can be expressed in it, contradictions are in principle a bad thing. So when we have them, we want to be able to nd them. Once we nd them, we can begin the task of guring out which of our facts or rules is incorrect. This can be a difcult task, but it is a problem in knowledge acquisition rather than a problem in logic programming. Our job is only to provide a tool to nd the contradictions.
Authors manuscript
Sec. 11.17.
391
The d-Prolog utility contradictions 0, dened in DPUTILS.PL, does this for us. In response to the query
?- contradictions
d-Prolog constructs dummy queries from the predicates found in the d-Prolog dictionary. Then it tries to derive strictly both the query and any contrary of the query. If it succeeds, it informs the user. The d-Prolog dictionary is essential to this process since otherwise there would be no way to generate the test queries. As it nds contradictions, d-Prolog displays them to the user and stores them in clauses for the predicate contradictory_pair 2. These clauses are not removed from memory after the contradictions have been identied; they are left in memory for the convenience of the user. It can take a long time to check a large program for contradictions and it is faster to check the clauses for contradictory_pairs than to run the utility again. However, whenever the utility is invoked it removes all clauses for contradictory_pairs from memory before testing for contradictions. Thus any contradictions corrected by the user will not show up again. Notice that besides the contradictory pairs, a great deal more may be displayed when the contradictions utility is invoked. d-Prolog will call every query it can construct from the predicates in the d-Prolog dictionary. Any queries that use write will produce text on the screen when contradictions is used. More importantly, any other input or output operations included in your code will be performed, including writes to les using tell 1 or changes in the contents of memory using assert 1 or retract 1. So great care should be used with the contradictions utility. Still, with care, it can be a valuable tool for testing d-Prolog knowledge bases for consistency.
the utility will respond in one of the following ways. 1. If the query ?- @ Goal succeeds, whynot reports this and stops.
Authors manuscript
392
Defeasible Prolog
Chap. 11
2. If there is no fact that will unify with Goal and no rule supporting Goal that is satisable, whynot reports this and stops. The auxiliary predicate initial_evidence 1 is used here. 3. If there is a strict or defeasible rule supporting Goal which is satisable, whynot displays this rule, nds the competing rule or fact that defeats it, and displays it as well. It takes several clauses to cover all the cases, but the clauses are not difcult to understand once one understands the d-Prolog inference engine.
Authors manuscript
Sec. 11.18.
A suite of examples
393
fliesX:= birdX. neg fliesX:= penguinX. neg fliesX:^ sickX,birdX. fliesX :^ ga_penguinX. fliessuperman := true. birdtweety. birdX:- penguinX. penguinopus. penguinX :- ga_penguinX. ga_penguinfolio. Chinese Restaurant Example - illustrating use of the predicate incompatible 2. incompatiblemarxistX,capitalistX. capitalistX := ownsX,restaurant. marxistX := bornX,prc. ownsping,restaurant. bornping,prc. Pennsylvania Dutch Example - showing superiority of strict rules over defeasible rules. native_speakerX,german_dialect :- native_speakerX,pa_dutch. native_speakerhans,pa_dutch. bornX,pennsylvania := native_speakerX,pa_dutch. bornX,usa :- bornX,pennsylvania. neg bornX,usa := native_speakerX,german_dialect. Twin Tweety Triangles - an example of preemption of defeaters. republicanX := conservativeX. neg republicanX := southernerX.
Authors manuscript
394
conservativeX := southernerX. republicanX := richX. neg republicanX := movie_starX. richX := movie_starX. southernerkim. movie_starkim.
Defeasible Prolog
Chap. 11
The Naked Nautilus - an example of inheritance with exception. has_shellX := molluscX. neg has_shellX := cephalopodX. has_shellX := nautilusX. neg has_shellX := naked_nautilusX. molluscX :- cephalopodX. molluscfred. cephalopodX :- nautilusX. nautilusX :- naked_nautilusX. Nixon Diamond - an example of multiple inheritance with resolution of the conflict forced by specifying superiority of the rule for Republicans. pacifistX := quakerX. neg pacifistX := republicanX. quakernixon. republicannixon. supneg pacifistX := republicanX, pacifistX := quakerX. The Yale Shooting Problem - an example involving temporal persistence. holdsalive,s. holdsloaded,s. holdsF,resultE,S := holdsF,S.
Authors manuscript
Sec. 11.18.
A suite of examples
395
incompatibleholdsalive,S,holdsdead,S. holdsdead,resultshoot,S := holdsloaded,S, holdsalive,S. The Election Example - an extended example of some complexity. neg runsdove := true. backshawk,crow. neg backsX,Y :- backsX,Z, Y == Z. nominateanimals,wolf := true. neg nominateanimals,wolf:= neg backsbull,wolf. nominateanimals,fox:= neg nominateanimals,wolf. neg nominateanimals,fox := involved_in_scandalfox, neg nominateanimals,wolf. nominateanimals,hart:= neg nominateanimals,wolf, neg nominateanimals,fox. nominatebirds,dove:= true. neg nominatebirds,dove :neg runsdove. nominatebirds,crow:= neg nominateanimals,wolf, neg runsdove. neg nominatebirds,crow:= nominateanimals,wolf, neg runsdove. nominatebirds,crane:= neg nominatebirds,dove, neg nominatebirds,crow. electedAnimal:= nominateanimals,Animal, nominatebirds,Bird, backshawk,Bird. electeddove:= nominatebirds,dove, nominateanimals,Animal, neg backsbull,Animal.
Authors manuscript
396
p. neg p. :- examples.
Defeasible Prolog
Chap. 11
Authors manuscript
Sec. 11.20.
Inheritance reasoning
397
yes ?- @ fliesX. X = superman - ; no ?- @@ fliesopus. presumably, no. yes ?- @@ fliesfolio. can draw no conclusion. yes ?-
Notice that when we tell d-Prolog that Tweety and Superman are sick, it no longer concludes that Tweety can y although it continues to conclude that Superman can y. This is because the undercutting defeater only applies to sick birds. Since the system cannot infer that Superman is a bird, it does not use the defeater in Supermans case. Notice also that the system can draw no conclusion about Folio. The rule for genetically altered penguins undercuts the rule for penguins, but the undercut rule can still rebut the rule for birds.
Authors manuscript
398
Mollusc
Defeasible Prolog
Chap. 11
Naked nautilus
Fred
Figure 11.7
nautilus. The following dialog with d-Prolog shows how these disclosures should affect our beliefs about whether Fred has a shell.
?- @@ has_shellfred. presumably, yes. yes ?- assertcephalopodfred. yes ?- @@ has_shellfred. presumably, no. yes. ?- assertnautilusfred. yes ?- @@ has_shellfred. presumably, yes. yes ?- assertnaked_nautilusfred. yes
Authors manuscript
Sec. 11.20.
Inheritance reasoning
399
The Naked Nautilus is an example where we have a linear hierarchy of groups with each group directly inheriting from its immediate supergroup. We can think of properties owing from each group to its subgroups. Multiple inheritance, on the other hand, involves cases where the inheritance relations between the groups and individuals form a nonlinear structure. An example is the Nixon Diamond illustrated graphically in Figure 11.8. Here the individual Nixon inherits from two groups, Republicans and Quakers, and neither group is more specic than the other. Furthermore, members of one group normally have a characteristic that members of the other group normally do not. So Nixon inherits from multiple groups with incompatible characteristics. In general, we can draw no conclusions in a Nixon Diamond since the two inheritance rules rebut each other. But in d-Prolog we can force resolution of the conict by specifying with a clause for the predicate sup which of the two rules should dominate. Notice that resolving the conict in this way does not involve a claim that Republicans are normally Quakers or that Quakers are normally Republicans. Neither rule becomes more specic than the other. Instead, stipulating superiority of the Republican rule simply declares that for purposes of determining whether a person is a pacist, being a Republican counts for more than being a Quaker. For another case where Republicans and Quakers tend to differ in their characteristics, we might specify that being a Quaker is more important or we might leave the issue unresolved.
Pacifist
Republican
Quaker
Nixon
Figure 11.8
Authors manuscript
Defeasible Prolog
Chap. 11
Things tend to remain the same and yet eventually everything changes. We expect to nd our car where we parked it, to nd our home where we left it, to nd our friends hair the same color as the last time we saw it. Yet we also believe that if we abandoned our car it would eventually be moved, that our house will someday be demolished, and that our friends hair will turn gray or even disappear over the course of the years. The tendency things have to stay the same we will call temporal persistence. This is a kind of inertia which sooner or later will be overcome by the forces of change. Temporal persistence is a very important feature of the world, one that we rely upon routinely. Suppose you want a green chair in your bedroom and you have a green chair in your living room. You might try to achieve your goal by moving the chair from the living room to the bedroom. But how can you infer that this action will accomplish your goal? How do you know that after you move the chair it will still be green? Why do you even believe that it will still be a chair? More to the point from the perspective of articial intelligence, how can a machine infer this? It may have a rule that says that after something is moved from location A to location B, it is in location B in the resulting situation. But it seems the system must also have a rule that says that green things remain green and chairs remain chairs when they are moved. And other rules that says chairs remain chairs and things maintain their locations when they are painted. And so on. It would appear that we will need more rules telling us what does not change as the result of some event than we do to tell us what changes. This problem, which is a problem of knowledge representation, was dubbed the Frame Problem by McCarthy and Hayes (1969). Specically, the problem is to represent the information a system needs to make all the necessary inferences about what remains unchanged when some event occurs. The kinds of rules we have mentioned which state that some feature of a situation remains unchanged after some event are called frame axioms. The point that Hayes and McCarthy make is that it looks like we will need lots and lots of frame axioms, probably more than all our other rules combined. A proposal to solve the Frame Problem is to formulate one very general temporal persistence principle that says, essentially, that everything tends to stay the same. Then, unless we can show that something would change as a consequence of some event having occurred, we are justied in inferring that it has not changed. We can formulate such a principle using McDermotts (1982) Situation Calculus together with d-Prolog. In the Situation Calculus situations are indicated by constants or as the situations that result from the occurrence of certain events in other situations. For example, we could indicate an initial situation by the constant s. If some event e1 occurs at s, we would represent the resulting situation by resulte1,s. If in this situation another event e2 were to occur, the resulting situation would be represented by resulte2,resulte1,s. Besides representing situations and the events that produce them, we also want to represent what holds in different situations. In the initial situation s of the last
Authors manuscript
Sec. 11.21.
Temporal persistence
401
paragraph, for example, it may be the case that a particular chair is green and that it is in the living room. We could represent this information by the following clauses:
holdschairc,s. holdsgreenc,s. holdsinc,living_room,s.
Now suppose the event e1 is the event of the chair being moved from the living room to the bedroom. Then we believe that in the resulting situation, the chair is still a chair and is still green, but it is no longer in the living room.
holdschairc,resulte1,s. holdsgreenc,resulte1,s. holdsneg inc,living_room,resulte1,s.
We now have enough machinery to formulate our temporal persistence principle. It will be a defeasible rule containing variables indicating that it applies to all situations, events, and uents (possible states that may or may not hold in a situation):
holdsF,resultE,S := holdsF,S.
Basically, this rule says that if some fact F holds in situation S, then presumably it also holds after event E occurs. This single principle should be the only frame axiom we need since it applies to all uents, all events, and all situations. This solution to the Frame Problem, or something very much like it, was proposed by McDermott (1987). But he later decided it didnt work because of some very simple examples that did not easily submit to this treatment using the nonmonotonic formalisms with which McDermott was familiar. One famous example constructed by Hanks and McDermott (1987) has come to be called the Yale Shooting Problem. We include a version of the YSP in KBASES.DPL which is simplied but which nevertheless captures the problematic features of the original. Our version of the YSP concerns Frankie and Johnnie, lovers of ballad fame. Frankie shot Johnnie for his indelity. We come in late in the story. Frankie enters Johnnies empty room with a loaded gun. She waits until he returns, then points the gun at his head and pulls the trigger. Intuitively, we do not expect Johnnie to survive this story. How do we represent the story in d-Prolog so the system can infer the expected outcome? Like this:
holdsalive,s. holdsloaded,s. holdsF,resultE,S := holdsF,S. incompatibleholdsalive,S,holdsdead,S. holdsdead,resultshoot,S := holdsloaded,S, holdsalive,S.
Authors manuscript
402
Defeasible Prolog
Chap. 11
The rst two clauses say that in the initial situation, s, Johnnie is alive and Frankies gun is loaded. The next is our temporal persistence principle. The fourth says that Johnnie cant be both alive and dead in the same situation. The nal clause is a causal rule that says that normally Johnnies status would be changed from alive to dead if a loaded gun were red at his head. In the brief act of the story that we witness only two events occur: Frankie waits until Johnnie arrives and then she res the gun at him. The crucial query, then, is
?- @@ holdsalive,resultshoot,resultwait,s.
d-Prolog gives the correct answer: presumably, Johnnie is not alive at the end of the story. Notice that it is important in the last clause of the example that holdsalive,S is a condition. Otherwise, this defeasible rule would not be more specic than the temporal persistence principle in the appropriate instance and the two rules would rebut each other. It is reasonable to include this condition since we are trying to represent a causal rule. Causal rules tell us how events tend to change things. So the condition of the rule naturally includes the feature of the situation which will be changed. This is all that is needed for the solution to work. Why did McDermotts nonmonotonic logic and the other nonmonotonic formalisms with which he was familiar fail to resolve the example so simply? Because these systems allow multiple extensions of a knowledge base. Basically, an extension is any completion of the knowledge base that violates a minimum number of the defeasible rules in the knowledge base. In the YSP, there are at least three ways we can complete the story that only violate one defeasible rule. 1. Violate the persistence principle going from the rst situation to the second so the gun is no longer loaded when Johnnie arrives. 2. Violate the causal principle going from the second situation to the last so Johnnie is still alive at the end of the story. 3. Violate the persistence principle going from the second situation to the last so Johnnie is dead at the end of the story. Thus, the story has multiple extensions. In the rst two extensions, Johnnie survives. Why do we prefer the third extension, the only one that d-Prolog supports? Because we have a reason to reject the persistence principle in this case: it conicts with the more specic causal principle.
Authors manuscript
Sec. 11.22.
403
It is presumed that Wolf will represent the Animals in the election, although he cannot get the nomination without Bulls support. If Wolf should for some reason not get the nomination, Fox is expected to get it. But Fox will not be nominated if he is involved in a scandal, even if Wolf is not nominated. And if both Wolf and Fox fail, the nomination is expected to go to Hart.
nominateanimals,wolf := true. neg nominateanimals,wolf:= neg backsbull,wolf. nominateanimals,fox:= neg nominateanimals,wolf. neg nominateanimals,fox := involved_in_scandalfox, neg nominateanimals,wolf. nominateanimals,hart:= neg nominateanimals,wolf, neg nominateanimals,fox.
The situation is murkier for the Birds. Presumably, Dove would get the nomination if she ran. But Crow will apparently get the nomination if, as expected, Dove does not run, and if the Animals do not nominate Wolf. Of course, Dove will not be nominated if she does not run. Furthermore, Crow will not be nominated if Wolf is nominated by the Animals, even if Dove does not run. If for some reason neither Crow nor Dove is nominated by the Birds, then the nomination will apparently go to Crane.
nominatebirds,dove:= true. neg nominatebirds,dove :- neg runsdove. nominatebirds,crow:= neg nominateanimals,wolf, neg runsdove. neg nominatebirds,crow:= nominateanimals,wolf, neg runsdove. nominatebirds,crane:= neg nominatebirds,dove, neg nominatebirds,crow.
We expect that the Animal candidate will win the election if Hawk, a wellknown but discredited Bird, backs the Bird candidate. We might call this the kiss of death rule. Second, we expect Dove to win if she is nominated and if the Animal candidate does not have Bulls support. This is as much as we can predict regarding the actual outcome of the election.
electedAnimal:= nominateanimals,Animal, nominatebirds,Bird, backshawk,Bird. electeddove:=
Authors manuscript
404
nominatebirds,dove, nominateanimals,Animal, neg backsbull,Animal.
Defeasible Prolog
Chap. 11
With this information, d-Prolog can deduce that apparently Wolf will receive the Animal nomination and Crow will receive the Bird nomination. It cannot predict the eventual winner in the election. If we add the information that Bull backs Hart, d-Prolog concludes that apparently Fox will receive the Animal nomination, Crow will receive the Bird nomination, and Fox will be elected. If we add the further information that Fox is involved in a scandal, then d-Prolog concludes that apparently the Animals will nominate hart, the Birds will still nominate Dove, and the election will go to Hart. Here is the corresponding dialog with d-Prolog, including some additional scenarios.
?- @ nominate | ?- @ nominateanimals,A,nominatebirds,B. A = wolf, B = crane | ?- @ electedX. no | ?- retractbackshawk,crow,assertbackshawk,crane. yes | ?- @ electedX. X = wolf | ?- assertbacksbull,hart. yes | ?- @ nominateanimals,A,nominatebirds,B. A = fox, B = crow | ?- @ electedX. X = fox | ?- assertinvolved_in_scandalfox. yes | ?- @ nominateanimals,A,nominatebirds,B. A = hart,
Authors manuscript
Sec. 11.22.
B = crow
405
| ?- @ electedX. X = hart | ?- assertrunsdove. yes | ?- @ nominateanimals,A,nominatebirds,B. A = hart, B = dove | ?- @ electedX. no | ?- retractbacksbull,hart,assertbacksbull,fox. yes | ?- @ nominateanimals,A,nominatebirds,B. A = hart, B = dove | ?- @ electedX. X = dove
Careful examination of the information represented in the example should convince you that these are the correct conclusions. However, it is not a simple task to sort through the information and reach the correct conclusions. This example shows d-Prologs ability to handle complex combinations of interacting rules.
Exercise 11.22.1 Notice that d-Prolog sometimes offers the same solution several times to the query
?- @ nominateParty,Candidate.
Explain this. If one clause is deleted from the denition of the predicate def_der 2, this solution is not repeated. Which clause can we eliminate to accomplish this? Despite the fact that it sometimes produces duplicate queries, we do not want to delete this clause. Why not? Exercise 11.22.2 When we assert clauses indicating that Bull supports Hart, Hawk supports Dove, and Dove is running, d-Prolog deduces that both Fox and Dove are elected President. We can avoid this by using the perdicate runs to list all the candidates that are running and
Authors manuscript
406
Defeasible Prolog
Chap. 11
then adding a clause for the predicate incompatible 2 which has a condition using the predicate runs 1. When done properly, d-Prolog no longer be able to deduce that two different candidates who are running can both be elected. How do we do this? Why do we need to use the runs predicate to make this work?
we get the response no. We conclude that Jones is not a secretary since, if he were, that fact would have been in the database and the query would have succeeded. Consider another example. We want to know all employees who are not secretaries and who make less than $25,000 per year. The appropriate query is
?- salaryX,Y, Y 25000, + positionX,secretary.
If the subgoal + positionX,secretary. succeeds, then X must not be a secretary. But this is only so if the CWA is correct for the predicate position 2. Notice that the variable X is bound by the time the subgoal containing + is called. This is important since otherwise the subgoal will only succeed if no one in the company is a secretary. Negation-as-failure is only appropriate where the CWA is justied. But when the CWA is justied, we can use + in Prolog to invoke it. This would seem to present a problem for d-Prolog. The CWA is often very useful, but the d-Prolog inference engine will not interpret + correctly. The solution to this problem is simple, and in fact d-Prolog gives us additional control over when we do and when we do not make the Closed World Assumption. In our example, we invoke the CWA for the predicate position 2 by including in our database the presumption
neg positionX,Y := true.
The presumption, then, is that nobody has any position in the company. Of course, presumptions are always overriden by facts. They are also overriden by any competing defeasible rule whose body is something other than true. So the presumption that nobody has any position in the company is overriden by any positive evidence whatsoever that some individual holds some position in the company. We can then rephrase our earlier query about employees making less than $25,000 who are not secretaries as
?- @ salaryX,Y, Y 25000, neg positionX,secretary.
In some cases, we may not be justied in making the CWA. Then we will not add the corresponding presumption. Suppose, for example, the senior partner in
Authors manuscript
Sec. 11.24.
BIBLIOGRAPHICAL NOTES
407
our ctional company has entered data in the database concerning whether the other employees like chocolates. Her reason for doing this is to help her keep track of what to give the other employees for Christmas. If they like chocolates, thats what she gives them. If they dont like chocolates, she gives them something else. If she doesnt know, she tries to work chocolates into the conversation and nd out. But she would not assume that someone does not like chocolates just because the database doesnt include the information that they do like chocolates. Thats just not the kind of information one can assume would be included. The partner will have to enter an explicit fact to show that a particular employee does not like chocolate. The Closed World Assumption example in KBASES.DPL includes information about positions, salaries, and attitudes toward chocolates for all the people working in the small dental clinic Dewey, Pullem, and Howe. The CWA is made only for the predicate position.
Exercise 11.23.1 (Project) Write a set of Prolog rules, defeasible rules, and defeaters for a predicate prescribe 2 that captures the following principles for prescribing medication. 1. Normally, prescribe a medication for a diagnosed illness if the medication is effective in treating that illness. 2. Normally, dont prescribe a medication for a diagnosed illness if there are counterindications (allergies, etc.). 3. Normally, prescribe a medication for a diagnosed illness even if the medication is counter-indicated if the medication is effective in treating the illness and the patients condition is critical (the patient will die if not treated). 4. Never prescribe a counter-indicated medication for an illness if there is another medication that is effective in treating the illness and is not counter-indicated. Exercise 11.23.2 (Project) Rewrite the CONMAN knowledge base in the le MDC.PL as a set of Prolog rules, defeasible rules, and defeaters for the predicate diagnosis 1. Use the prescription rules from the preceding exercise as part of your knowledge base.
Authors manuscript
408
Defeasible Prolog
Chap. 11
Authors manuscript
Chapter 12
409
Authors manuscript 693 ppid September 9, 1995 Prolog Programming in Depth
410
Chap. 12
use a subset of English as a command language. But presentday researchers are a long way from a complete understanding of how language works. In what follows we will explore the linguistic theory that underlies NLP and show how it can be applied in practical Prolog programs. First we present a template system that can be implemented even by programmers who have studied no linguistics. Then we present more advanced techniques that illustrate current research issues. You should be aware that, in this chapter, many shortcuts have been taken because there is not room to explain the topic fully. For a more comprehensive treatment of natural language processing in Prolog, see Covington (1994).
Authors manuscript
Sec. 12.3.
Exercise 12.2.1
Tokenization
411
Classify each of the following facts about English as phonological, morphological, syntactic, semantic, or pragmatic: 1. The sentence Can you close the door? is normally understood as a polite request even though it is literally a question. 2. The sound /h/ never occurs at the end of a syllable. 3. The plural noun trousers has no singular form. 4. The verb run has a large number of different meanings. 5. The verb denounce is normally followed by a noun phrase. Exercise 12.2.2 Compare the morphology of a foreign language that you are acquainted with to the morphology of English. Does the morphology of the other language convey kinds of information that English morphology does not?
12.3. TOKENIZATION
The rst step in designing any NLP system is to write down some examples of sentences it should process. Our goal in this chapter will be to build a system that can carry on conversations like that shown in Figure 12.1. The computer will accept information from the user and use it to answer questions. For simplicity, we have chosen trivial subject matter, but our techniques will work equally well with almost any Prolog knowledge base. The rst problem that we face is TOKENIZATION. We want to represent an utterance as a list of words, but the user types it as a string of characters. The string must therefore be broken up into words. Additionally, we will discard punctuation marks, convert all upper case letters to lower case, and then convert the words into Prolog atoms. Thus, How are you today? will become how,are,you,today . This is done by the recursive procedure shown in TOKENIZE.PL (Figure 12.2). To understand how it works, rst consider the following procedure, which copies a list one element at a time:
copy_list , . copy_list X|Y , X|Z :- copy_listY,Z.
This is an inefcient way to copy a list; unication could duplicate the whole list in a single step. But copy_list can be modied so that it looks at the list and stops copying at a particular point. This is how grab_word (in TOKENIZE.PL) works: it stops copying when it reaches a blank. Then it returns two results: the word that it found, and the list of characters remaining in the input string. Thus, successive calls to grab_word extract successive words in a string. This tokenizer makes no attempt to recognize or classify words. It is a generic tokenizer designed for NLP systems that deal with word recognition and morphology elsewhere. In practice, most systems can be made more efcient by combining part of the morphological component with the tokenizer, to avoid the waste of effort
Authors manuscript
412
Cathy is a child. Fido is a big brown dog. Cathy likes animals. Does Cathy like a dog? Not as far as I know. Dogs are animals. Does Cathy like Fido? Yes. Does Fido like Cathy? Not as far as I know. Big dogs chase cats. Felix is a cat. Is Fido big? Yes. Does Fido chase cats? Yes. Does Felix chase Fido? Not as far as I know.
Chap. 12
Design goal: a possible conversation with our system. The computer will reason solely from the information given to it in human language.
Figure 12.1
that comes from collapsing lists of characters into atoms, then taking them apart again later for morphological analysis. We have chosen not to do this so that we will not have to modify the tokenizer as we rene the syntactic and semantic parts of the system.
Exercise 12.3.1 Get TOKENIZE.PL working on your computer. Demonstrate that it correctly handles queries such as this:
?- tokenize"This is a test.",What. What = this,is,a,test
Exercise 12.3.2 What does TOKENIZE.PL presently do if words are separated by multiple blanks this")? Modify it to handle multiple blanks correctly. Call your version ("like TOKENIZ1.PL. Exercise 12.3.3 Further modify TOKENIZ1.PL so that punctuation marks are not discarded, but instead are treated as onecharacter tokens. Call this version TOKENIZ2.PL.
Authors manuscript
Sec. 12.4.
Template Systems
413
File TOKENIZE.PL tokenize+String,-Result Breaks String a list of ASCII code into a list of atoms, discarding punctuation and converting all letters to lower case. tokenize , :- !.
grab_word+String,-Chars,-Rest Splits String into first token Chars and remainder Rest. grab_word 32|Tail , grab_word , , . ,Tail :- !. stop upon hitting a blank stop at end of list skip punctuation marks
grab_word Char|Tail1 , NewChar|Tail2 ,Rest :grab_wordTail1,Tail2,Rest, lower_caseChar,NewChar. if within a word, keep going
punctuation_mark+Char Succeeds if Char is a punctuation mark. punctuation_markChar punctuation_markChar punctuation_markChar punctuation_markChar ::::Char = 47. Char = 58, Char = Char = 91, Char = Char = 123.
64. 96.
lower_case+Char,-NewChar Translates any ASCII code to lower case. lower_caseChar,NewChar :Char = 65, Char = 90, !, NewChar is Char+32. lower_caseChar,Char. Add 32 to code of upper-case letter
Figure 12.2
Authors manuscript
414
animalX :- dogX.
Chap. 12
One way to perform these translations is to match tokenized sentences to TEMPLATES, or stored sentence patterns, each of which is accompanied by a translation schema. Thus, X,is,a,Y will translate into the fact YX regardless of what X and Y may be. In practice, Prologs does not allow variable functors, so we must use =.. to build the translation. The rules will look like this:
process X,is,a,Y :- Fact =.. process is,X,a,Y :- Query =.. Y,X , assertFact. Y,X , callQuery.
Some analysis of words is unavoidable. We want to distinguish Dogs sleep from Fido sleeps, since their translations have different structures: the rst is sleepX :- dogX and the second is sleepfido. To do this we must distinguish singulars from plurals, and perhaps distinguish names from common nouns. Program TEMPLATE.PL (Figure 12.3) contains a templatematching system that can carry on simple conversations; Figure 12.4 shows an example of what it can do. By adding more templates, we could make it accept a greater variety of sentence types, and it could obviously benet from a more thorough treatment of morphology. The present version naively assumes that all nouns ending in s are plural and all verbs ending in s are singular. Template systems are powerful enough for a wide range of NLP applications. They are especially appropriate for creating Englishbased command languages in which the vocabulary may be large but the repertoire of sentence structures is small. Moreover, template systems can be implemented without performing extensive analysis of the human language that they are to accept. Thus, they are especially suitable for implementation by programmers who have no training in linguistics. A template system can be made more versatile by discarding unnecessary words in advance. Give me the names of people in accounting with salaries above $30,000 can be turned into names in accounting, salaries above $30,000 by the tokenizer. A more sophisticated approach, called KEYWORD ANALYSIS, scans the sentence for important words and uses them as clues to determine which other words are important. Keyword analysis is widely used in commercial NLP systems. Another approach is to combine template matching with limited parsing capability, as is done in the program PARRY (Parkinson et al. 1977), which has over 2000 templates. But no template system, even with keyword analysis, is powerful enough to cover all the sentence structures of a human language.
Exercise 12.4.1 Get TEMPLATE.PL working and modify it so that it knows that children is the plural of child (so that you can say girls are children and Cathy is a girl and have it infer that Cathy is a child).
Authors manuscript
Sec. 12.4.
Template Systems
415
File TEMPLATE.PL Simple natural language understander using templates Uses READSTR.PL Chapter 5 and TOKENIZE.PL Chapter 12. :- ensure_loaded'readstr.pl'. :- ensure_loaded'tokenize.pl'. Use reconsult if necessary
process+Wordlist translates Wordlist into Prolog and asserts it if it is a statement or queries it if it is a question. Note that this procedure assumes that whenever a word ends in S, the S is an affix either noun plural or verb singular. process X,is,a,Y :!, Fact =.. Y,X , noteFact. process X,is,an,Y :!, process X,is,a,Y . process is,X,a,Y :!, Query =.. Y,X , checkQuery. process is,X,an,Y :!, process is,X,a,Y . process X,are,Y :!, remove_sX,X1, remove_sY,Y1, Head =.. Y1,Z , Tail =.. X1,Z , noteHead :- Tail. fido,is,a,dog = dogfido.
is,fido,a,dog
?-dogfido.
pages.)
Figure 12.3
Authors manuscript
416
Chap. 12
process does,X,Y :!, Query =.. Y,X , checkQuery. process X,Y :+ remove_sX,_, remove_sY,Y1, !, Fact =.. Y1,X , noteFact. process X,Y :remove_sX,X1, + remove_sY,_, !, Head =.. Y,Z , Tail =.. X1,Z , noteHead :- Tail.
does,fido,sleep
?-sleepfido.
fido,sleeps
sleepfido.
dogs,sleep
sleepX :- dogX.
remove_s+Word,-NewWord removes final S from Word, or fails if Word does not end in S. remove_sX,X1 :nameX,XList, remove_s_listXList,X1List, nameX1,X1List. remove_s_list"s", .
Authors manuscript
Sec. 12.4.
Template Systems
417
check+Query Try Query. Report whether it succeeded. checkQuery :- write'Trying query: ?- ', writeQuery, Un-comment these lines nl, to see the translations callQuery, !, write'Yes.', nl. check_ :write'Not as far as I know.', nl.
note+Fact Asserts Fact and prints acknowledgement. noteFact : write'Adding to knowledge base: ', writeFact, Un-comment these lines nl, to see the translations assertaFact, write'OK', nl.
do_one_sentence Accept and process one sentence. do_one_sentence :- write' ', read_strS, tokenizeS,T, processT.
start Main procedure. start :- write'TEMPLATE.PL at your service.',nl, write'Terminate by pressing Break.',nl, repeat, do_one_sentence, fail.
Authors manuscript
418
TEMPLATE.PL at your service. Terminate by pressing Break. Fido is a dog. OK Dogs are animals. OK Animals eat. OK Does Fido eat? Yes. Does Fido sleep? Not as far as I know.
Chap. 12
Figure 12.4
Exercise 12.4.2
(project)
Use template technology to make your computer process operating system commands in English. Instead of constructing a Prolog query from the input, construct a command and pass it to the operating system. For example, What les do I have? would translate as dir in DOS or ls in UNIX. Most Prologs have a builtin predicate that passes strings or atoms to the operating system as commands.
Authors manuscript
Sec. 12.5.
Generative Grammars
419
From any even number you can get another even number by adding or subtracting 2. With just two rules, this procedure describes an innite number of numbers, because the second rule can apply recursively to its own output. You can show that 1,988 is even by applying the rst rule once and then applying the second rule 994 times. A procedure that generates the sentences of a human language is called a GENERATIVE GRAMMAR. What kinds of rules should a generative grammar contain? Chomskys initial conclusion was that two kinds of rules are sufcient. Most sentence structures can be generated satisfactorily by CONTEXTFREE PHRASE STRUCTURE RULES (PSrules for short), which we will discuss next; rules of another type, called TRANSFORMATIONS, are needed to generate structures that we will discuss later. The following is a small grammar consisting of context free phrase structure rules. Here we are using the notation commonly used in linguistics; the notation used in Prolog programs is slightly different. (1) (2) (3) (4) (5) (6) (7) (8) (9) (10) (11) (12) (13) (14) S ! NP VP NP ! D N VP ! V NP VP ! V S D ! the D!a N ! dog N ! cat N ! boy N ! girl V ! chased V ! saw V ! said V ! believed
(etc.)
The rst rule says, A sentence (S) can consist of a noun phrase (NP) followed by a verb phrase (VP). That is, it EXPANDS S into NP and VP. The other rules work the same way; D stands for determiner, N for noun, and V for verb. In many cases there are alternative expansions of the same symbol; rules 3 and 4, for example, both specify expansions of VP. The words introduced by rules 6 to 14 are called TERMINAL SYMBOLS because they have no further expansions. Figure 12.5 shows one of the sentences generated by this grammar, together with a tree diagram that shows how the rules generate it. The diagram divides the sentence into phrases called CONSTITUENTS, each comprising the expansion of one symbol. This grammar generates an innite number of structures. Rule 1 expands S into NP VP, and rule 4 expands VP into V S. Each of these rules can apply to the output of the other, and the resulting loop can generate arbitrarily long sentences (Figure 12.6). Thus the grammar is a nite description of an innite set. Our grammar does generate some nonsensical sentences, such as The boy chased the cat saw the girl. We could block generation of these sentences by using more sophisticated rules. Alternatively, we could say that they are syntactically
Authors manuscript
420
S
PPPPP
Chap. 12
the
dog
chased
the
cat
A sentence generated by our phrase structure grammar, with a tree diagram showing the rules used to generate it.
Figure 12.5
PPPP
NP VP ,@
PPPPP , @
D N V S
PPPP
dog
Figure 12.6
Authors manuscript
Sec. 12.6.
A Simple Parser
421
acceptable but semantically illformed; that is, their structures are legitimate but no coherent meanings can be assigned to them because of the particular words used.
Exercise 12.5.1 Using the generative grammar given in the text of this section, draw tree diagrams of the following sentences: A dog chased the boy. The cat believed the dog saw the girl. Exercise 12.5.2 Which of these sentences are generated by the grammar given in the text of this section? The boy saw the boy. A boy chased. The dog slept. The cat said the cat said the cat said the cat. The the cat said the dog chased the boy. Exercise 12.5.3 Give an example of a recursive syntactic structure in English other than those mentioned in the text. Exercise 12.5.4 Write a generative procedure (in English or any appropriate notation) that generates the following innite set of strings: ab aabb aaabbb aaaabbbb aaaaabbbbb . . . Exercise 12.5.5 Does the human brain generate sentences by the same process as the grammar described in this section? Or does it use some other process? What, in your opinion, is the relation between the generative grammar (or a more comprehensive one like it) and the brain of a person who speaks English?
Authors manuscript
422
(1) (2) (3) . . . (5) . . .
Chap. 12
Rule 1 calls rules 2 and 3; rule 2 calls rule 5 and another rule much like it; and so on. The process continues until all the words in the sentence have been recognized. This kind of parser can handle multiple expansions of the same symbol, because if one alternative fails, it backtracks and tries another. It can handle recursion because the rules can call themselves recursively. The only thing that it cannot handle is LEFT RECURSION, as in the PSrule A!AB because the procedure for parsing A would call itself before doing anything else, and an endless loop would result. Fortunately, any PSgrammar that uses left recursion can be transformed into an equivalent grammar that does not. To perform recursivedescent parsing, we need to keep track of two things: the list of words that we are starting with, and the list of words that will be left over after a particular constituent has been removed. Call these X and Z respectively. We will render S ! NP VP into Prolog as:
sentenceX,Z :- noun_phraseX,Y, verb_phraseY,Z.
In English: To remove a sentence from X leaving Z, rst remove a noun phrase from X leaving Y, then remove a verb phrase from Y leaving Z. The rules for terminal symbols refer directly to the elements of the list:
noun dog|Z ,Z.
To ask whether The cat saw the dog is a sentence, we execute the query:
?- sentence the,cat,saw,the,dog , .
The second argument is so that no words will be left over at the end. This is important; we want to distinguish The cat saw the dog, which is grammatical, from The cat saw the dog the the, which is not, and if we try to parse The mouse ate the cheese, we do not want to stop with The mouse ate. File PARSER1.PL (Figure 12.7) shows a complete parser constructed in this way. As shown, it merely tests whether a list is generated by the grammar, without constructing a representation of the structure found. However, note that it is reversible it can generate as well as parse. The query
?- sentenceX, .
means generate a sentence, and you can backtrack to get as many different sentences as you wish. In fact, you can even issue a query such as
Authors manuscript
Sec. 12.6.
A Simple Parser
423
File PARSER1.PL Simple parser using Prolog rules sentenceX,Z :- noun_phraseX,Y, verb_phraseY,Z. noun_phraseX,Z :- determinerX,Y, nounY,Z. verb_phraseX,Z :- verbX,Y, noun_phraseY,Z. verb_phraseX,Z :- verbX,Y, sentenceY,Z. determiner the|Z ,Z. determiner a|Z ,Z. noun noun noun noun verb verb verb verb dog|Z ,Z. cat|Z ,Z. boy|Z ,Z. girl|Z ,Z. chased|Z ,Z. saw|Z ,Z. said|Z ,Z. believed|Z ,Z.
Figure 12.7
A parser that uses Prolog rules to determine whether a list of atoms is generated by the grammar.
Authors manuscript
424
?- sentence W,X,Y,Z,cat|T , .
Chap. 12
to get a sentence whose fth word is cat a feature that can be useful for solving puzzles and breaking codes.
Exercise 12.6.1 Get PARSER1.PL working on your computer, and use it to generate all possible sentences whose third word is believed. What do you get? Exercise 12.6.2 Using your solution to Exercise 12.5.4, implement a parser that will test whether a list consists of a number of as followed by the same number of bs (such as a,a,a,b,b,b ). Exercise 12.6.3 (for discussion)
Consider the work done by a recursivedescent parser using the rules a!bc a!bd a!be a!fg when the input string is f,g,e . What operations are needlessly repeated during the parsing? Suggest a way to reduce the duplication of effort.
although its internal implementation may be different (see Appendix B). In grammar rule notation, non-terminal symbols are written in lists:
noun -dog .
The list can contain more than one element, representing words that occur in immediate succession:
verb -gives,up .
This rule treats gives up as a single verb. The list can even be empty, indicating an element that can be left out:
determiner -.
Authors manuscript
Sec. 12.7.
425
This rule says that the grammar can act as if a determiner is present even if it doesnt actually nd one. What about queries? A query to the rule such as
sentence -noun_phrase, verb_phrase.
In fact, if you use listing, you will nd that grammar rules are actually translated into ordinary Prolog clauses when they are loaded into memory. Remember that grammar rule notation is merely a notational device. It adds no computing power to Prolog; every program written with grammar rules has an exact equivalent written without them. PARSER2.PL (Figure 12.8) is a parser written in grammar rule notation. Here are some examples of its operation:
?- sentence the,dog,chased,the,cat , . yes ?- sentence the,dog,the,cat , . no ?- sentence the,dog,believed,the,boy,saw,the,cat , yes ?- sentence A,B,C,D,cat|E , . A=the,B=dog,C=chased,D=the,E= A=the,B=dog,C=chased,D=a,E= A=the,B=dog,C=saw,D=the,E= etc.
.
Like Prolog goals, grammar rules can use semicolons to mean or:
noun -dog ; cat ; boy ; girl .
Grammar rules can even include Prolog goals, in curly brackets, interspersed among the constituents to be parsed:
sentence -noun_phrase, verb_phrase, write'Sentence found', nl .
Grammar rules are executed in the same way as ordinary clauses; in fact, they are clauses in which some of the arguments are supplied automatically. Thus it may even make sense to embed a cut in a grammar rule:
sentence -does , ! , noun_phrase, verb_phrase.
This rule parses a question that begins with does. The cut says that if does has been found, no other rule for sentence should be tried. Most importantly, nonterminal symbols in grammar rules can take arguments. Thus
Authors manuscript
426
Chap. 12
File PARSER2.PL A parser using grammar rule notation sentence -noun_phrase, verb_phrase. determiner, noun. verb, noun_phrase. verb, sentence. the . a .
noun_phrase -verb_phrase -verb_phrase -determiner -determiner -noun noun noun noun verb verb verb verb --------dog . cat . boy . girl .
Figure 12.8
Authors manuscript
Sec. 12.8.
Grammatical Features
noun_phraseN, verb_phraseN.
427
sentenceN --
is equivalent to
sentenceN,X,Z :- noun_phraseN,X,Y, verb_phraseN,Y,Z.
although some Prologs may put N after the automatically supplied arguments instead of before them. A grammar in which nonterminal symbols take arguments is called a denite clause grammar (DCG); it is more powerful than a context-free phrase-structure grammar. The arguments undergo instantiation just as in ordinary clauses; they can even appear in embedded Prolog goals. Grammar rule notation is often called DCG notation.
Exercise 12.7.1 Get PARSER2.PL running on your computer. Show how to use it to: Parse the sentence The girl saw the cat. Test whether The girl saw the elephant is generated by the grammar rules. Generate a sentence with 8 words (if possible). Exercise 12.7.2 Reimplement your parser for ab, aabb, aaabbb... using grammar rule notation.
Authors manuscript
428
plural_determiner singular_noun plural_noun singular_verb plural_verb -----; the .
Chap. 12
dog ; cat ; boy ; girl . dogs ; cats ; boys ; girls . chases ; sees ; says ; believes . chase ; see ; say ; believe .
This grammar works correctly but is obviously very redundant. Most rules are duplicated, and one of them the rule for verb phrases has actually split into four parts because the verb need not agree with its object. Imagine how the rules would proliferate a language whose constituents agree not only in number but also in gender and case. AGREEMNT.PL (Figure 12.9) shows a much better approach. Number is treated as an argument (or, as linguists call it, a FEATURE) of certain non-terminal symbols (Figure 12.10). The rst rule says that a sentence consists of a noun phrase with some number feature, followed by a verb phrase with the same number:
sentence -noun_phraseN, verb_phraseN.
N is instantiated to singular or plural when a word that can be identied as singular or plural is parsed. Thus, when looking for nounN, we could use the rule nounplural -dogs .
which, if successful, will instantiate N to plural. The rule for verb phrases uses an anonymous variable to show that the number of the object does not matter:
verb_phraseN -verbN, noun_phrase_.
Anonymous variables are an ideal computational mechanism to handle features that are NEUTRALIZED (disregarded) at particular points in the grammar. Notice that we cant leave out the argument altogether, because noun_phrase (without arguments) will not unify with noun_phrasesingular or noun_phraseplural. Here are some examples of queries answered by AGREEMNT.PL:
?- sentence the,dog,chases,cats , . yes ?- sentence the,dog,chase,cats , . no ?- noun_phraseX, the,dogs , . X=plural ?- noun_phraseX, a,dog , . X=singular ?- noun_phraseX, a,dogs , . no
Note in particular that you need not parse an entire sentence; you can tell the computer to parse a noun phrase or something else.
Authors manuscript
Sec. 12.8.
Grammatical Features
429
File AGREEMNT.PL Illustration of grammatical agreement features The argument N is the number of the subject and main verb. It is instantiated to 'singular' or 'plural' as the parse progresses. noun_phraseN, verb_phraseN. determinerN, nounN. verbN, noun_phrase_. verbN, sentence. a . the . .
sentence --
dog ; cat ; boy ; girl . dogs ; cats ; boys ; girls . chases ; sees ; says ; believes . chase ; see ; say ; believe .
Figure 12.9
D singular
cc
NP singular
PPPPP
N singular
V singular
H H HH
D plural
VP singular
cc
NP plural
N plural cats
dog
chases
the
Number features on nonterminal symbols. Some constituents, such as adverbs and prepositional phrases, are not marked for number.
Figure 12.10
Authors manuscript
430
Exercise 12.8.1
Chap. 12
Get AGREEMNT.PL working and add the determiners one, several, three, every, all, and some.
12.9. MORPHOLOGY
AGREEMNT.PL is still redundant in one respect: it lists both singular and plural forms of every word. Most English noun plurals can be generated by adding s to the singular. Likewise, almost all third person singular verbs are formed by adding s to the plural (unmarked) form. In MORPH.PL (Figure 12.11), we implement these morphological rules and at the same time allow them to have exceptions. The trick is to use Prolog goals embedded in grammar rules. A rule such as
nounN -X , morphverbN,X .
means: Parse a noun with number feature N by instantiating X to the next word in the input list, then checking that morphverbN,X succeeds. Here morph is an ordinary Prolog predicate that handles morphology. The clauses for morph comprise both rules and facts. The facts include all the singular nouns as well as irregular plurals:
morphnounsingular,dog. morphnounsingular,cat.
. . .
morphnounplural,children.
The rules compute additional wordforms from the ones listed in the facts. They use remove_s, a predicate dened originally in TEMPLATE.PL; remove_sX,Y succeeds if X is an atom ending in s and Y is the same atom without the s. This provides a way to form regular plural nouns from singulars:
morphnounplural,X :remove_sX,Y, morphnounsingular,Y.
Authors manuscript
Sec. 12.9.
Morphology
431
File MORPH.PL Parser with morphological analysis sentence -noun_phraseN, verb_phraseN. determinerN, nounN. verbN, noun_phrase_. verbN, sentence. a . the . . . .
morphnounN,X morphverbN,X
morph-Type,+Word succeeds if Word is a word-form of the specified type. morphnounsingular,dog. morphnounsingular,cat. morphnounsingular,boy. morphnounsingular,girl. morphnounsingular,child. Singular nouns
Figure 12.11
Authors manuscript
432
Chap. 12
morphnounplural,children. morphnounplural,X :remove_sX,Y, morphnounsingular,Y. morphverbplural,chase. morphverbplural,see. morphverbplural,say. morphverbplural,believe. morphverbsingular,X :remove_sX,Y, morphverbplural,Y.
Plural verbs
remove_s+X,-X1 lifted from TEMPLATE.PL removes final S from X giving X1, or fails if X does not end in S. remove_sX,X1 :nameX,XList, remove_s_listXList,X1List, nameX1,X1List. remove_s_list"s", .
Authors manuscript
Sec. 12.10.
433
can represent the parse tree in Figure 12.5 above. (It is indented purely for readability; a few Prologs have a pretty-print utility that can print any structure this way.) To build these structures, we rewrite the grammar rules as in this example:
sentencesentenceX,Y -noun_phraseX, verb_phraseY.
That is: Instantiate the argument of sentence to sentenceX,Y if you can parse a noun phrase, instantiating its argument to X, and then parse a verb phrase, instantiating its argument to Y. The complete parser is shown in STRUCTUR.PL (Figure 12.12). A query to it looks like this:
?- sentenceX, the,dog,chased,the,cat , .
and X becomes instantiated to a structure representing the parse tree. Grammars of this kind, with clause heads such as sentencesentenceX,Y, are obviously somewhat redundant; there are more concise ways to build parsers that simply output the parse tree. Behind the redundancy, however, lies some hidden
Authors manuscript
434
Chap. 12
File STRUCTUR.PL Parser like PARSER2.PL, but building a parse tree while parsing sentencesentenceX,Y -noun_phraseX, verb_phraseY. noun_phrasenoun_phraseX,Y -determinerX, nounY. verb_phraseverb_phraseX,Y -verbX, noun_phraseY. verb_phraseverb_phraseX,Y -verbX, sentenceY. determinerdeterminerthe -the . determinerdeterminera -a . nounnoundog -nounnouncat -nounnounboy -nounnoungirl -dog . cat . boy . girl .
Figure 12.12
power; the grammar can build a structure that is not the parse tree but is computed in some way while the parsing is going on. Instead of building up a phrasestructure representation of each constituent, we can build a semantic representation. But before we begin doing so, we have one more syntactic issue to tackle.
Exercise 12.10.1 Get STRUCTUR.PL working and show how to use it to obtain the parse tree of the sentence The girl believed the dog chased the cat. Exercise 12.10.2 What is the effect of the following query to STRUCTUR.PL? Explain.
?- sentencesentencenoun_phrase_,verb_phraseverb_,sentence_,What, .
Authors manuscript
Sec. 12.11.
Unbounded Movements
435
This rule passes the saved WHword list X unchanged to noun_phrase, which may or may not use it; then noun_phrase instantiates Z to the new WHword list and passes it back to this rule. Crucially, one of the rules can pull a noun phrase out of the WHword list rather than the input string:
noun_phrase X|Tail ,Tail,noun_phraseX -.
Authors manuscript
436
Chap. 12
S ` !!! ```````` ! NP VP S
PPPPP
N V NP VP
PPPP
P V S HHH H NP VP cc N V
NP
Who (did)
Max
think
believed
Sharon
saw
Cathy
A transformational grammar generates a WHword in place and then moves it to the beginning of the sentence. Another transformation inserts did.
Figure 12.13
Authors manuscript
Sec. 12.12.
Semantic Interpretation
437
The other noun phrase rules accept a noun phrase from the input string without altering the WHword list. WHPARSER.PL (Figure 12.14) is a parser that uses this technique. For the sentence who,did,the,boy,believe,saw,the,girl it produces the parse tree
sentence noun_phrase determinerthe, nounboy , verb_phrase verbbelieved, sentence noun_phrasewho, verb_phrase verbsaw, noun_phrase determinerthe, noungirl
with the WHword who in the position of the missing NP, where it would have appeared if the movement transformation had not taken place. Each WHquestion contains only one preposed WHword, so we dont really need a list in which to store it. The usefulness of lists shows up with relative clauses, which can have WHwords introducing multiply nested constructions: The man whom the dog which you saw belonged to claimed it. In parsing such sentences, the WHword list might acquire two or even three members. However, sentences that require more than two items on the list are invariably confusing to human hearers. The human brain apparently cannot maintain a deep pushdown stack while parsing sentences.
Exercise 12.11.1 Why does WHPARSER.PL accept Who did the boy believe the girl saw? but reject Who did the boy believe the girl saw the cat? That is, how does WHPARSER.PL guarantee that if the sentence begins with who, an NP must be missing somewhere within it?
Authors manuscript
438
Chap. 12
File WHPARSER.PL Parser that handles WH-questions as well as statements. For simplicity, morphology is neglected. Each phrase that can contain a WH-word has 3 arguments: 1 List of WH-words found before starting to parse this constituent; 2 List of WH-words still available after parsing this constituent; 3 Structure built while parsing this constituent as in STRUCTUR.PL.
Sentence that does not begin with a WH-word, but may be embedded in a sentence that does
sentenceX,Z,sentenceNP,VP -wh_wordW, Sentence begins with WH-word. did , Put the WH-word on the list, noun_phrase W|X ,Y,NP, absorb "did," and continue. verb_phraseY,Z,VP.
noun_phraseX,X,noun_phraseD,N -determinerD, Ordinary NP that does nounN. not use saved WH-word noun_phrase X|Tail ,Tail,noun_phraseX - Missing NP supplied by picking a stored WH-word off the list verb_phraseX,Z,verb_phraseV,NP -verbV, noun_phraseX,Z,NP. .
Figure 12.14
Authors manuscript
Sec. 12.12.
Semantic Interpretation
439
verb_phraseX,Z,verb_phraseV,S -verbV, sentenceX,Z,S. determinerdeterminera -determinerdeterminerthe -nounnoundog nounnouncat nounnounboy nounnoungirl ----dog . cat . boy . girl . a . the .
Two forms of every verb: "The boy saw the cat" vs. "Did the boy see the cat?" verbverbchased verbverbsaw verbverbsaid verbverbbelieved wh_wordwho -wh_wordwhat -----chased ; chase . saw ; see . said ; say . believed ; believe .
who . what .
Sample queries test1 :- sentence , ,Structure, who,did,the,boy,believe,the,girl,saw , writeStructure, nl. test2 :- sentence , ,Structure, who,did,the,boy,believe,saw,the,girl , writeStructure, nl.
,
,
Authors manuscript
440
Rule: sentence ! noun phrase verb phrase sentence ! noun phrase copula verb phrase sentence ! noun phrase copula adj phrase sentence ! aux verb noun phrase verb phrase sentence ! copula noun phrase noun phrase sentence ! copula noun phrase adj phrase verb phrase ! verb noun phrase adj phrase ! adjective noun phrase ! determiner noun group noun group ! adjective noun group noun group ! common noun noun group ! proper noun
Chap. 12
Figure 12.15
grammar rule notation, and Figure 12.16 shows some of the structures that these rules generate. For simplicity, we completely neglect morphology. However, we introduce several new types of sentences, including yesno questions and sentences that have the copula is or are rather than a main verb. We compute the meaning of each sentence in two stages. First, the parser constructs a representation of the meaning of the sentence; then other procedures convert this representation into one or more Prolog rules, facts, or queries. The representations constructed by the parser have the form
statementEntityList,Predicate
or
questionEntityList,Predicate
where EntityList is a list of the people, places, or things that the sentence is about, and Predicate is the central assertion made by the sentence. The principal functor, statement or question, of course identies the type of sentence. The items in EntityList represent meanings of noun phrases. We assume that every noun phrase refers to a subset of the things that exist in the world specically, to the thing or things that meet particular conditions. We therefore represent entities as structures of the form:
entityVariable,Determiner,Conditions
Here Variable is a unique variable that identies the entity; if the entity has a name, the variable is instantiated to that name. Determiner is the determiner that introduces the noun phrase; in this subset of English, the only determiners are a and null. Finally, Conditions is a Prolog goal specifying conditions that the entity must meet. Here are a few examples:
Authors manuscript
Sec. 12.12.
Semantic Interpretation
441
sentence hhhhhh verb phrase noun phrase ```` hhhh h noun group noun phrase determiner verb XXX ```` noun group noun group adjective determiner XXX common noun adjective
noun group
h copulahhhhh noun phrase noun phrase X X XXnoun group X XXnoun group X determiner determiner
sentence proper noun X Kermit is a common noun frog
hhhhhhhh hhh aux verb noun phrase verb phrase X h XXnoun group verbhhhh phrase X determiner noun X XXnoun group X proper noun determiner
sentence common noun does X Kermit chase X cats
Figure 12.16
Authors manuscript
442
Chap. 12
File NLU.PL A working natural language understander Preliminaries :- write'Loading program. Please wait...',nl,nl. :- ensure_loaded'tokenize.pl'. :- ensure_loaded'readstr.pl'. Use reconsult if necessary.
Define the ampersand & as a compound goal constructor with narrower scope lower precedence than the comma. :- op950,xfy,&. GoalA & GoalB :callGoalA, callGoalB. syntax of &
semantics of &
sentence --
noun_phrase, verb_phrase.
sentence -sentence --
sentencestatement NewSubj ,Pred -noun_phraseSubj, copulaCop, noun_phraseComp ; adj_phraseComp, change_a_to_nullSubj,NewSubj , NewSubj = entityS,_,_ , Comp = entityS,_,Pred .
A Prolog program that understands a small subset of English. (Continued on following pages.)
Figure 12.17
Authors manuscript
Sec. 12.12.
Semantic Interpretation
443
sentence --
sentence -sentence --
sentencequestion NewSubj ,Pred -copulaCop, noun_phraseSubj, noun_phraseComp ; adj_phraseComp, change_a_to_nullSubj,NewSubj , NewSubj = entityS,_,_ , Comp = entityS,_,Pred .
change_a_to_null+Entity,-NewEntity Special rule to change determiner 'a' to 'null'. Invoked when parsing sentences with copulas so that "A dog is an animal" will mean "Dogs are animals." change_a_to_nullentityV,a,C,entityV,null,C :- !. change_a_to_nullX,X. if it didn't match the above
verb_phrase --
verb, noun_phrase.
verb_phraseverb_phrase Subj,Obj ,Pred -verbV, noun_phraseObj, Subj = entitySu,_,_ , Obj = entityOb,_,_ , Pred =.. V,Su,Ob .
adj_phrase --
adjective.
Authors manuscript
444
noun_phrase --
Chap. 12
determiner, noun_group.
noun_group --
adjective, noun_group.
noun_group --
common_noun.
noun_group --
proper_noun.
noun_groupentityN,_,true -proper_nounN.
Vocabulary copulabe aux_verbdo determinera determinernull verbchase verbsee verblike adjectivegreen adjectivebrown adjectivebig adjectivelittle -----------is ; are . do ; does . a ; an . . chase ; chases . see ; sees . like ; likes . green . brown . big . little .
Authors manuscript
Sec. 12.12.
Semantic Interpretation
445
common_noundog common_nouncat common_nounfrog common_nounboy common_noungirl common_nounperson common_nounchild common_nounanimal proper_nouncathy proper_nounfido proper_nounfelix proper_nounkermit
-------------
dog ; dogs . cat ; cats . frog ; frogs . boy ; boys . girl ; girls . person ; people . child ; children . animal ; animals . cathy . fido . felix . kermit .
Procedure to drive the parser parse+List,-Structure parses List as a sentence, creating Structure. parseList,Structure :sentenceStructure,List, , !. Commit to this structure, even if there are untried alternatives, because we are going to modify the knowledge base. parseList,'PARSE FAILED'. if the above rule failed
Translation into Prolog rules make_rule+EntityList,+Pred,-Rule rearranges EntityList and Pred to make a Prolog-like rule, which may be ill-formed with a compound left side. make_ruleEntityList,Pred,Pred :- Conds :combine_conditionsEntityList,Conds.
Authors manuscript
446
Chap. 12
combine_conditionsEntityList,Result combines the conditions of all the entities in EntityList to make a single compound goal. combine_conditions entity_,_,Cond,Rest1|Rest , Cond & RestConds :combine_conditions Rest1|Rest ,RestConds. combine_conditions entity_,_,Cond ,Cond. Processing of statements dummy_item-X Creates a unique dummy individual a structure of the form dummyN where N is a unique number. dummy_itemdummyN :retractdummy_countN, NewN is N+1, assertadummy_countNewN, !. dummy_count0. substitute_dummies+Det,+Elist,-NewElist Substitutes dummies for all the entities in Elist whose determiners match Det and whose identifying variables are not already instantiated. If Det is uninstantiated, it is taken as matching all determiners, not just the first one found. substitute_dummiesDet, Head|Tail , NewHead|NewTail :!, substitute_oneDet,Head,NewHead, substitute_dummiesDet,Tail,NewTail. substitute_dummies_, , .
Authors manuscript
Sec. 12.12.
Semantic Interpretation
447
substitute_one_,E,E. for those that didn't match the above assert_ruleRule Adds Rule to the knowledge base. If the left side is compound, multiple rules with simple left sides are created. assert_ruleC1 & C2 :- Premises :!, Rule = C1 :- Premises, message'Adding to knowledge base:', messageRule, assertRule, assert_ruleC2 :- Premises. assert_ruleRule : Did not match the above message'Adding to knowledge base:', messageRule, assertRule. Processing of questions move_conditions_into_predicate+Det,+E,+P,-NewE,-NewP E and P are original entity-list and predicate, respectively. The procedure searches E for entities whose determiner matches Det, and transfers their conditions into P. Results are NewE and NewP. move_conditions_into_predicateDet, E1|E2 ,P, E1|NewE2 ,NewP :E1 = entity_,Det,_, !, No change needed in this one move_conditions_into_predicateDet,E2,P,NewE2,NewP. move_conditions_into_predicateDet, E1|E2 ,P, NewE1|NewE2 ,Conds & NewP :E1 = entityV,Det,Conds, !, NewE1 = entityV,Det,true, move_conditions_into_predicateDet,E2,P,NewE2,NewP. move_conditions_into_predicate_, ,P, ,P.
Authors manuscript
448
Chap. 12
query_rule+Rule Tests whether Rule expresses a valid generalization. This procedure always succeeds. query_ruleConclusion :- Premises :message'Testing generalization:', messagefor_allPremises,Conclusion, for_allPremises,Conclusion, !, write'Yes.',nl. query_rule_ : Above clause did not succeed write'No.',nl.
for_all+GoalA,+GoalB Succeeds if: 1 All instantiations that satisfy GoalA also satisfy GoalB, 2 There is at least one such instantiation. for_allGoalA,GoalB :+ callGoalA, + callGoalB, callGoalA, !.
User interface message+Msg Prints Msg only if message_flagtrue. messageX :message_flagtrue, !, writeX,nl. message_. message_flagtrue. Change to false to suppress messages
Authors manuscript
Sec. 12.12.
Semantic Interpretation
449
process+Structure Interprets and acts upon a sentence. Structure is the output of the parser. process'PARSE FAILED' :write'I do not understand.', nl. processstatementE,P :substitute_dummiesa,E,NewE, make_ruleNewE,P,Rule, assert_ruleRule, substitute_dummies_,NewE,_. processquestionE,P :move_conditions_into_predicatea,E,P,NewE,NewP, make_ruleNewE,NewP,Rule, query_ruleRule. main_loop Top-level loop to interact with user. main_loop :repeat, message' ', message'Enter a sentence:', read_strString,nl, tokenizeString,Words, message'Parsing:', parseWords,Structure, messageStructure, processStructure, fail.
start Procedure to start the program. start :write'NATURAL LANGUAGE UNDERSTANDER',nl, write'Copyright 1987, 1994 Michael A. Covington',nl, nl, write'Type sentences. Terminate by hitting Break.',nl, main_loop.
Authors manuscript
450
a frog dogs Fido = = =
entityX,a,frogX entityY,null,dogY entityfido,null,true
Chap. 12
Here true serves as an emptycondition a goal that always succeeds and thus can be inserted where no other goal is needed. X and Y stand for unique uninstantiated variables. The sentence Do dogs chase a cat? is thus represented as:
question entityX,null,dogX,entityY,a,catX ,chaseX,Y
Predicates and conditions can be compound. To form compound goals, we use the ampersand, dened as an operator synonymous with the comma but with lower precedence, exactly as in Chapter 6. Some examples of compounding follow: a big green frog little Cathy Big dogs chase little cats = = =
entityX,a,bigX & greenX & frogX entitycathy,null,littlecathy & true statement entityX,null,bigX & dogX, entityY,null,littleX & catX , chaseX,Y
Notice that true occurs redundantly in the translation of little Cathy. This is because the translation of Cathy is entitycathy,null,true, and when little is added, none of the existing structure is removed. We could write more complex rules to remove redundancies such as this, but there is little point in doing so, since the extra occurrence of true does not affect the conditions under which the compound goal succeeds.
Exercise 12.12.1 What representation would NLU.PL use for each of the following phrases or sentences? 1. Cathy chases a green frog. 2. Kermit is an animal. 3. Is Cathy a cat?
The arguments contain the same information as the rule itself; thats why the end result is a structure showing how the rules generate the sentences. But in NLU.PL, we dont want to build a parse tree. Instead, we want to build a semantic representation. And the rst rule in the grammar is therefore this:
Authors manuscript
Sec. 12.13.
Constructing Representations
451
Paraphrasing this in English: To parse a sentence, parse a noun phrase and unify its representation with Subj, and then parse a verb phrase and unify its representation with verb_phrase Subj|Tail ,Pred. To see how this works, note that the representation of a verb phrase is like that of a sentence except that most of the information about the subject is uninstantiated. For instance: a dog = =
entityX,a,dogX
chases a cat
To combine these into a sentence, we unify Subj rst with entityX,a,dogX and then with entityY,YDet,YConds. This sets up the following instantiations:
Y = X Y1 = a Y2 = dogX Subj = entityX,a,dogX Tail = entityZ,a,catZ Pred = chaseY,Z = chaseX,Z
That is: This is a statement about two entities, X, which is a dog, and Z, which is a cat. The fact being stated is chaseX,Z. The parser works this way throughout; it builds representations of small units and combines them to represent larger units. If the sentence has a copula (is or are) rather than a verb, then its predicate comes from the noun phrase or adjective phrase that follows the copula. To parse Fido is a dog we rst represent the constituents as follows: Fido a dog = =
entityfido,null,true entityX,a,dogX
Upon nding the copula, the parser unies X with fido and moves the conditions of a dog into the predicate, creating the structure:
statement entityfido,null,true ,dogfido
This accounts for the fact that a dog is understood, in this context, as a description of Fido, not as another entity to which Fido is related in some way. The semantic representations must next be converted into Prolog facts, rules, or queries. This is done by the procedure process and various other procedures that it calls. Consider rst the simple statement:
Authors manuscript
452
Children like animals.
Chap. 12
This is easily done: put the predicate of the sentence on the left and the conditions of all the entities on the right. That is done by the procedures combine_conditions and make_rule. The same procedure works for sentences with names in them. The sentence Cathy likes Fido.
statement entitycathy,null,true,entityfido,null,true , likecathy,fido
becomes
likecathy,fido :- true & true.
which is an ordinary Prolog query. Consider, however, what happens if the question is Does Cathy like dogs? or a similar generalization. This could mean either Does Cathy like all of the dogs in the knowledge base? or Is there a rule from which we can deduce that Cathy likes all dogs? To handle such questions we must make an ad hoc extension to the Prolog inference engine. Recall that in Chapter 6 we dened a predicate for_allGoal1,Goal2, which succeeds if all instantiations that satisfy Goal1 also satisfy Goal2 (and there is at least one such instantiation). Thus, the query
?- for_alldogX,likecathy,X.
Authors manuscript
Sec. 12.14.
Dummy Entities
453
enables us to ask whether Cathy likes all of the dogs in the knowledge base. But if, without naming any dogs, we have simply asserted, Cathy likes dogs, the query will fail. We can get more natural behavior by postulating dummy entities, whose names will be dummy0, dummy1, dummy2, and so on. Whenever we assert a generalization, we will also assert that there is a dummy entity to which the generalization applies. Thus, if the user types Children like big animals, we will assert not only
likeX,Y :- childX & bigY & animalY.
but also
childdummy0. animaldummy1. bigdummy1.
That is, if we say that children like big animals, we will assert that there exists at least one child and at least one big animal. As a result, if we later ask Do children like big animals? or even Do children like animals? we will get an afrmative answer. Dummy entities also provide a way to deal with the determiner a. When we say Cathy likes a dog, we are asserting that there is a dog and that Cathy likes it:
dogdummy2. likecathy,dummy2.
Similarly, when we translate A dog chases a cat, we will assert that there is a dummy dog and a dummy cat and that the dog chases the cat:
dogdummy3. catdummy4. chasedummy3,dummy4.
Dummy entities are inserted into statements by the procedure substitute_dummies, which creates dummies for all the entities with a particular determiner. As an example, consider how we process the statement: Dogs chase a little cat.
statement entityX,null,dogX,entityY,a,littleY & catY , chaseX,Y
First, substitute_dummies searches for entities whose determiner is a. It instantiates their identifying variables to unique values and asserts their conditions. Thus, in this step, we instantiate Y to dummy5, make the assertions
littledummy5. catdummy5.
Authors manuscript
454
Chap. 12
In effect, we have changed the sentence to Dogs chase dummy5 and asserted that dummy5 is a little cat.1 Next we turn the representation into a Prolog rule in the normal manner, and assert it:
chaseX,dummy5 :- dogX & true.
Finally, we need to ensure that there is some individual to which this generalization applies, so we make another pass through the representation, this time picking up all the entities that remain. X is still uninstantiated, so we can instantiate it to dummy6 and assert:
dogdummy6.
This gives the expected behavior with queries that use for_all. In fact, we use for_all to process all questions, not just those that involve generalizations. If we ask, Is Fido a dog? the query that we generate is
?- for_alltrue,dogfido.
which is trivially equivalent to ?- dogfido. The actual process of translating a question into Prolog is simple. Given a question such as Does a dog chase Felix?
question entityX,a,dogX,entityfelix,null,true , chaseX,felix
the rst step is to move into the predicate the conditions of all the entities whose determiner is a. (These are the ones that we want to have implicit existential rather than universal quantiers in Prolog.) The result is:
question entityX,a,true,entityfelix,null,true , chaseX,felix & dogX
Next we transform the representation into a Prolog rule using the same procedure as if it were a statement:
chaseX,felix & dogX :- true.
But instead of adding the rule to the knowledge base, we pass it to the procedure query_rule, which transforms it into a query that uses for_all:
?- for_alltrue,chaseX,felix & dogX.
Authors manuscript
Sec. 12.15.
Exercise 12.14.1
Bibliographical Notes
(small project)
455
Get NLU.PL running and modify it to handle sentences with the determiner every, such as Every dog chases every cat and Does every dog chase a cat? Exercise 12.14.2 (small project)
Modify NLU.PL so that it can answer WHquestions such as Who saw the cat? Exercise 12.14.3 (project)
Using techniques from NLU.PL, construct a practical natural language interface for a database that is available to you.
Authors manuscript
456
Chap. 12
Authors manuscript
Appendix A
This appendix is a summary of the 1995 ISO standard for the Prolog language, ISO/IEC 13211-1:1995 (Prolog: Part 1, General core). As this is written (September 1995), standard-conforming Prolog implementations are just beginning to appear.1 Section A.9 summarizes the August 1995 proposal for implementing modules (Prolog: Part 2, Modules Working Draft 8.1, ISO/IEC JTC1 SC22 WG17 N142); this is not yet an ofcial standard and is subject to change. The information given here is only a sketch; anyone needing denitive details is urged to consult the ISO documents themselves. The ISO standard does not include denite clause grammars (DCGs), nor the Edinburgh lehandling predicates (see, seen, tell, told, etc.). Implementors are, however, free to keep these for compatibility, and nothing that conicts with them has been introduced. The standard does not presume that you are using the ASCII character set. The numeric codes for characters can be whatever your computer uses.
1 A draft of this appendix was circulated by Internet; I want to thank Jan Burse, Jo Calder, Klaus Daessler, Markus Fromherz, Fergus Henderson, Andreas Kagedal, Koen de Bosschere, Paul Holmes Higgin and especially Roger Scowen for pointing out errors and making helpful suggestions.
457
Authors manuscript 693 ppid September 9, 1995 Prolog Programming in Depth
Appx. A
Whitespace (layout text) consists of blanks, endofline marks, and comments. Implementations commonly treat tabs and formfeeds as equivalent to blanks. You can put whitespace before or after any term, operator, bracket, or argument separator, as long as you do not break up an atom or number and do not separate a functor from the opening parenthesis that introduces its argument list. Thus fa,b,c can be written f a , b , c , but there cannot be whitespace between f and . Whitespace is sometimes required, e.g., between two graphic tokens. For example, * * is two occurrences of the atom *, but ** is one atom. Also, whitespace is required after the period that marks the end of a term. There are two types of comments. One type begins with * and ends with * ; the other type begins with and ends at the end of the line. Comments can be of zero length (e.g., ** ). It is not possible to nest comments of the same type (for example, * * * is a complete, valid comment). But a comment of one type can occur in a comment of the other type ( * thus * ). STYLE NOTE: Because nesting is not permitted, we recommend using for ordinary comments and using * * only to comment out sections of code.
A.1.2. Variables
A variable name begins with a capital letter or the underscore mark (_) and consists of letters, digits, and/or underscores. A single underscore mark denotes an anonymous variable.
provided it does not begin with * and is not a period followed by whitespace. Such atoms are called GRAPHIC TOKENS. The special atoms and (see section A.1.6 below).
A series of arbitrary characters in single quotes. Within single quotes, a single quote is written double (e.g., 'don''t panic'). A backslash at the very end of the line denotes continuation to the next line, so that
Authors manuscript
Sec. A.1.
'this is an atom'
Syntax of Terms
459
is equivalent to 'this is an atom' (that is, the line break is ignored). Note however that when used this way, the backslash must be at the physical end of the line, not followed by blanks or comments. (In practice, some implementations are going to have to permit blanks because it is hard or impossible to get rid of them.)2 Another use of the backslash within a quoted atom is to denote special characters, as follows:
a b f n r t v x23 23 ' " `
alert character (usually the beep code, ASCII 7) backspace character formfeed character newline character or code (implementation dependent) return without newline (horizontal) tab character vertical tab character (if any) character whose code is hexadecimal 23 (using any number of hex digits) character whose code is octal 23 (using any number of octal digits) backslash single quote double quote backquote
The last two of these will never be needed in a quoted atom. They are used in other types of strings that take these same escape sequences, but are delimited by double quotes or backquotes.
A.1.4. Numbers
Integers are written in any of the following ways: As a series of decimal digits, e.g., 012345; As a series of octal digits preceded by 0o, e.g., 0o567; As a series of hexadecimal digits preceded by 0x, e.g., 0x89ABC; As a series of binary digits preceded by 0b, e.g., 0b10110101; As a character preceded by 0', e.g., 0'a, which denotes the numeric code for the character a. (The character is written exactly as if it were in single quotes; that is, if it is a single quote it must be written twice, and an escape sequence such as n is treated as a single character.)
2A
line break written as such cannot be part of the atom; for example,
Authors manuscript
460
Appx. A
Floatingpoint numbers are written only in decimal. They consist of at least one digit, then (optionally) a decimal point and more digits, then (optionally) E, an optional plus or minus, and still more digits. For example:
234 2.34 2.34E5 2.34E+5 2.34E-10
Note that .234 and 2. are not valid numbers. A minus sign can be written before any number to make it negative (e.g., -3.4). Notice that this minus sign is part of the number itself; hence -3.4 is a number, not an expression.
A.1.6. Structures
The ordinary way to write a structure is to write the functor, an opening parenthesis, a series of terms separated by commas, and a closing parenthesis: fa,b,c. We call this FUNCTOR NOTATION, and it can be used even with functors that are normally written as operators (e.g., 2+2 = +2,2).
Authors manuscript
Sec. A.1.
Syntax of Terms
461
TABLE A.1
atom lengthAtom,Integer
must be instantiated.)
sub atomAtom,NB,L,NA,Sub Succeeds if Atom can be broken into three pieces consisting of NB, L, and NA characters respectively, where L is the length of substring Sub. Here Atom must be instantiated;
the other arguments enjoy full interchangeability of unknowns and give multiple solutions upon backtracking.
char codeChar,Code
Relates a character (i.e., a onecharacter atom) to its numeric code (ASCII, or whatever the computer uses). (Either Char or Code, or both, must be instantiated.)
atom charsAtom,Chars
Interconverts atoms with lists of the characters that represent them, e.g., atom_charsabc, a,b,c . (Either Atom or Chars, or both, must be instantiated.)
atom codesAtom,String Like atom_chars, but uses a list of numeric codes, i.e., a string. number charsNum,Chars
Interconverts numbers with lists of the characters that represent them, e.g., number_chars23.4, '2','3','.','4' . (Either Num or Chars, or both, must be instantiated.)
number codesNum,String Like number_chars, but uses a list of numeric codes, i.e., a string.
These predicates raise error conditions if an argument is the wrong type. Note that name 2 is not included in the standard.
Authors manuscript
Appx. A
Specier
xfx fx xfy xfy xfy fy xfx xfy yfx yfx xfx xfy fy
= =..
Lists are dened as rightwardnested structures using the functor . (which is not an inx operator). For example,
a a, b a, b | c = = = .a, .a, .b,
There can be only one | in a list, and no commas after it. Curly brackets have a special syntax that is used in implementing denite clause grammars, but can also be used for other purposes. Any term enclosed in is treated as the argument of the special functor :
one = one
and likewise for any number of terms. The standard does not include denite clause grammars, but does include this syntactic hook for implementing them. You are, of course, free to use curly brackets for any other purpose.
A.1.7. Operators
The predened operators of ISO Prolog are shown in Table A.2. The meanings of the operators will be explained elsewhere in this appendix as they come up; : is to be used in the module system (Part 2 of the standard, not yet ofcial). Some operators, such as ?- and -- , are not given a meaning in the standard, but are preserved for compatibility reasons.
Authors manuscript
Sec. A.2.
Program Structure
463
The SPECIFIER of an operator, such as xfy, gives both its CLASS (inx, prex, or postx) and its ASSOCIATIVITY. Associativity determines what happens if there are two inx operators of equal priority on either side of an argument. For example, in 2+3+4, 3 could be an argument of either the rst or the second +, and the associativity yfx species that the grouping on the left should be formed rst, treating 2+3+4 as equivalent to 2+3+4. The Prolog system parses an expression by attaching operators to their arguments, starting with the operators of the lowest priority, thus:
2 + 3 * 4 =:= X 2 + *3,4 =:= X +2,*3,4 =:= X =:=+2,*3,4,X
(original expression) (after attaching *, priority 400) (after attaching +, priority 500) (after attaching =:=, priority 700)
Terms that are not operators are considered to have priority 0. The same atom can be an operator in more than one class (such as the inx and prex minus signs). To avoid the need for unlimited lookahead when parsing, the same atom cannot be both an inx operator and a postx operator.
A.1.8. Commas
The comma has three functions: it separates arguments of functors, it separates elements of lists, and it is an inx operator of priority 1000. Thus a,b (without a functor in front) is a structure, equivalent to ','a,b.
A.1.9. Parentheses
Parentheses are allowed around any term. The effect of parentheses is to override any grouping that may otherwise be imposed by operator priorities. Operators enclosed in parentheses do not function as operators; thus 2+2 is a syntax error.
A.2.2. Directives
The standard denes the following set of directives (declarations):
:- dynamicPred Arity.
The specied predicate is to be dynamic (modiable at run time). (See also section A.9.) This directive can also be written
Authors manuscript
464
to declare more than one predicate at once.
:- multifilePred Arity.
Appx. A
The specied predicate can contain clauses loaded from more than one le. (The multifile declaration must appear in each of the les, and if the predicate is declared dynamic in any of the les, it must be declared dynamic in all of them.) This directive can also be written :- multifile Pred Arity,Pred Arity... . or :- multifilePred Arity,Pred Arity.... to declare more than one predicate at once.
:- discontiguousPred Arity.
The clauses of the specied predicate are not necessarily together in the le. (If this declaration is not given, the clauses of each predicate are required to be contiguous.) This directive can also be written :- discontiguous Pred Arity,Pred Arity... . or :- discontiguousPred Arity,Pred Arity.... to declare more than one predicate at once.
:- opPriority,Associativity,Atom.
The atom is to be treated syntactically as an operator with the specied priority and associativity (e.g., xfy). CAUTION: An op directive in the program le affects the syntax while the program is being loaded; the standard does not require that its effect persist after the loading is complete. Traditionally, an op declaration permanently changes the syntax used by the Prolog system (until the end of the session), thus affecting all further reads, writes, and consults; the standard permits but does not require this behavior. See also section A.9. However, op can also be called as a builtin predicate while the program is running, thereby determining how read and write will behave at run time. Any operator except the comma can be deprived of its operator status by declaring it to have priority 0 (in which case its class and associativity have no effect, but must still be declared as valid values).
:- char conversionChar1,Char2.
This species that if character conversion is enabled (see Flags, Section A.5), all occurrences of Char1 that are not in quotes should be read as Char2. Note that, to avoid painting yourself into a corner, you should normally put the arguments of char_conversion in quotes so that they wont be subject to conversion. The situation with char_conversion is analogous to op the standard does not require its effect to persist after the program nishes loading. However, you can also call char_conversion as a builtin predicate at execution time, to determine how characters will be converted at run time.
:- set prolog flagFlag,Value.
Authors manuscript
Sec. A.3.
Control Structures
465
it is up to the implementor whether the effect persists after the program nishes loading, but you can also call set_prolog_flag as a builtin predicate at execution time.
:- initializationGoal.
This species that as soon as the program is loaded, the goal Goal is to be executed. There can be more than one initialization directive, in which case all of the goals in all of them are to be executed, in an order that is up to the implementor.
:- includeFile.
Species that another le is to be read at this point exactly as if its contents were in the main le. (Apparently, a predicate split across two les using include does not require a multifile declaration, since the loading is all done at once.)
:- ensure loadedFile.
Species that in addition to the main le, the specied le is to be loaded. If there are multiple ensure_loaded directives referring to the same le, it is only loaded once. Note that directives are not queries the standard does not say you can embed arbitrary queries in your program, nor that you can execute directives as queries at run time (except for op, char_conversion, and set_prolog_flag, which are, explicitly, also builtin predicates). Traditionally, directives have been treated as a kind of query, but the standard, with advancing compiler technology in mind, does not require them to be.
A.3.2. Cuts
The cut (!) works in the traditional way. When executed, it succeeds and throws away all backtrack points between itself and its CUTPARENT. Normally, the cutparent is the query that caused execution to enter the current clause. However, if the cut is in an environment that is OPAQUE TO CUTS, the cutparent is the beginning of that environment. Examples of environments that are opaque to cuts are: The argument of the negation operator ( +). The argument of call, which can of course be a compound goal, such as callthis,!,that. The lefthand argument of - (see below).
Authors manuscript
466
Appx. A
The goals that are arguments of once, catch, findall, bagof, and setof (and, in general, any other goals that are arguments of predicates).
Authors manuscript
Sec. A.4.
Error Handling
467
A.3.5.
repeat
The predicate repeat works in the traditional way, i.e., whenever backtracking reaches it, execution proceeds forward again through the same clauses as if another alternative had been found.
A.3.6.
once
The query onceGoal nds exactly one solution to Goal. callGoal,! and is opaque to cuts.
It is equivalent to
A.3.7. Negation
The negation predicate is written + and is opaque to cuts. That is, + Goal is like callGoal except that its success or failure is the opposite. Note that extra parentheses are required around compound goals (e.g., + this, that).
and throw
The control structures catch and throw are provided for handling errors and other explicitly programmed exceptions. They make it possible to jump out of multiple levels of procedure calls in a single step. The query catchGoal1,Arg,Goal2 is like callGoal1 except that if, at any stage during the execution of Goal1, there is a call to throwArg, then execution will immediately jump back to the catch and proceed to Goal2. Here Arg can be a variable or only partly instantiated; the only requirement is that the Arg in the catch must match the one in the throw. Thus, Arg can include information to tell catch what happened. In catch, Goal1 and Goal2 are opaque to cuts.
An argument was uninstantiated in a place where uninstantiated arguments are not permitted.
Authors manuscript
468
type errorType,Term
Appx. A
An argument should have been of type Type (atom, body (of clause), callable (goal), character, compound (= structure), evaluable (arithmetic expression), integer, list, number, etc., as the case may be), but Term is what was actually found.
domain errorDomain,Term Like type_error, except that a DOMAIN is a set of possible values, rather than a basic data type. Examples are character_code_list and stream_or_alias. Again, Term is the argument that caused the error. existence errorObjType,Term
Something does not exist that is necessary for what the program is trying to do, such as reading from a nonexistent le. Here, again, Term is the argument that caused the error.
permission errorOperation,ObjType,Term
The program attempted something that is not permissible (such as repositioning a nonrepositionable le). Term and ObjType are as in the previous example, and Operation is access clause, create, input, modify, or the like. Reading past end of le gets a permission errorinput,past end of stream,Term.
representation errorError
An implementationdened limit has been violated, for example by trying to handle 'ab' as a single character. Values of Error is character, character code, in character code, max arity, max integer, or min integer.
evaluation errorError
An arithmetic error has occurred. Types are float overflow, int overflow, underflow, zero divisor, and undefined.
resource errorResource
The system has run out of some resource (such as memory or disk space).
syntax errorMessage
The system has attempted to read a term that violates Prolog syntax. This can occur during program loading, or at run time (executing read or read_term).
system error
This is the catchall category for other implementationdependent errors. For further details see the latest ISO documents.
A.5. FLAGS
A FLAG is a parameter of the implementation that the program may need to know about. Programs can obtain and, where applicable, change ags by using the builtin predicates current_prolog_flagFlag,Value and set_prolog_flagFlag,Value. Table A.3 lists the ags dened in the standard. Any specic implementation is likely to have many more.
Authors manuscript
Sec. A.5.
Flags
469
TABLE A.3
True if integer arithmetic gives erroneous results outside a particular range (as when you add 32767 + 1 on a 16bit computer and get ,32768). False if the range of available integers is unlimited (as with Lisp bignums). max integer (an integer) The greatest integer on which arithmetic works correctly. Dened only if bounded is true. min integer (an integer) The least integer on which arithmetic works correctly. Dened only if bounded is true. integer rounding function (down or toward zero) The direction in which negative numbers are rounded by and rem. char conversion (on or off) Controls whether character conversion is enabled. Can be set by the program. debug (on or off) Controls whether the debugger is in use (if so, various predicates may behave nonstandardly). Can be set by the program. max arity (an integer or unbounded) The maximum permissible arity for functors. unknown (error, fail, or warning) Controls what happens if an undened predicate is called. Can be set by the program. double quotes (chars, codes, or atom) Determines how strings delimited by double quotes ("like this") are interpreted upon input: as lists of characters, lists of codes, or atoms. The standard species no default, but most implementors are expected to choose codes for Edinburgh compatibility.
Authors manuscript
Appx. A
N + N N - N N * N N N I I I rem I I mod I N ** N -N absN atanN ceilingN cosN expN sqrtN signN floatN float_fractional_partX float_integer_partX floorX logN sinN truncateX roundX I J I J I J I J I
Addition Subtraction Multiplication Floatingpoint division Integer division Remainder Modulo Exponentiation (result is oatingpoint) Sign reversal Absolute value Arctangent (in radians) Smallest integer not smaller than N Cosine (argument in radians) Natural antilogarithm, eN Square root Sign (-1, 0, or 1 for negative, zero, or positive N) Convert to oatingpoint Fractional part of X (negative if X is negative) Integer part of X (negative if X is negative) Largest integer not greater than X Natural logarithm, loge N Sine (argument in radians) Integer equal to the integer part of X Integer nearest to X Bitshift I rightward J bits Bitshift I leftward J bits Bitwise and function Bitwise or function Bitwise complement (reverse all bits of I)
Here I and J denote integers, X denotes oatingpoint numbers, and N denotes numbers of either type.
Authors manuscript
Sec. A.7.
471
The arithmetic system of the ISO standard is based on other ISO standards for computer arithmetic; see the standard itself for full details. The Prolog standard requires all arithmetical operations to give computationally reasonable results or raise error conditions.
Notice that each inputoutput operation can name a STREAM (an open le) and can give an OPTION LIST. To take the defaults, the option lists can be empty, and in some cases even omitted:
test :- open' usr mcovingt myfile.txt',write,MyStream, write_termMyStream,'Hello, world', , closeMyStream. ,
atom);
Mode is read, write, or append; Stream is a variable that will be instantiated to an implementationdependent handle; Options is an option list, possibly empty.
Authors manuscript
472
Appx. A
characters arranged into lines; a binary le contains any data whatsoever, and is read byte by byte.
repositiontrue or repositionfalse (the default). A repositionable stream (e.g., a disk le) is one in which it is possible to skip forward or backward to specic positions. aliasAtom to give the stream a name. For example, if you specify the option aliasaccounts_receivable, you can write accounts_receivable as the Stream argument of subsequent operations on this stream.
A specication of what to do upon repeated attempts to read past end of le: eof_actionerror to raise an error condition; eof_actioneof_code to make each attempt return the same code that the rst one did (e.g., -1 or end_of_file); or eof_actionreset, to examine the le again and see if it is now possible to read past what used to be the end (e.g., because of data written by another concurrent process). Somewhat surprisingly, the standard species no default for this option. Implementors are free to add other options.
Authors manuscript
Sec. A.7.
473
positionP, where P is an implementationdependent term giving the current position of the stream; end_of_streamE, where E is at, past, or no, to indicate whether reading has
just reached end of le, has gone past it, or has not reached it.
eof_actionA, where A is as in the options for open. repositionB, where B is true or false to indicate repositionability. typeT, where T is text or binary.
The import of this is that it lets you write your own user interface for the Prolog system (or any Prologlike query processor). You can accept a query, store a list that gives the names of its variables, and then eventually print out the names alongside the values. There are also two less elaborate options. The option singletonsList gives you a list, in the same format, of just the variables that occurred only once in the term useful if youre reading Prolog clauses and want to detect misspelled variable names. And variablesList gives you a list of just the variables, without their names (such as _0001,_0002 ).
Authors manuscript
474
Appx. A
TABLE A.5
get charStream,Char
Reads a character (as a onecharacter atom). Returns end of file at end of le.
get charChar
Returns the next character waiting to be read, without removing it from the input stream. Returns end of file at end of le.
peek charCode
presumably faster.)
put charChar
Returns the code of the next character waiting to be read, without removing it from the input stream. Returns -1 at end of le.
peek codeCode
Authors manuscript
Sec. A.7.
475
TABLE A.6
get byteStream,Code
Returns the numeric value of the next byte waiting to be read, without removing it from the input stream. Returns -1 at end of le.
peek byteCode
TABLE A.7
read termStream,Term,Options Reads a term from Stream using options in list. read termTerm,Options
Authors manuscript
Appx. A
write termStream,Term,Options
The signicance of this is that '$VAR'terms are often used to replace variables when there is a need to instantiate all the variables in a term. By printing the term out with this option, its variables can be made to look like variables again.
Authors manuscript
Sec. A.8.
477
TABLE A.9
current inputStream Unies Stream with the handle of the current input stream. current outputStream Unies Stream with the handle of the current output stream. set inputStream
True if the stream is at or past end of le (i.e., the last character or byte has been read). (A read or read_term does not consume any of the whitespace following the term that it has read, so after reading the last term on a le, the le will not necessarily be at end of stream.)
at end of stream
position).
nlStream
Alters the set of operators during execution. See sections A.1.7, A.2.2.
current opPriority,Specifier,Term
Determines the operator denitions that are currently in effect. Any of the arguments, or none, can be instantiated. Gives multiple solutions upon backtracking as appropriate.
char conversionChar1,Char2
Alters the set of character conversions during execution. See sections A.2.2, A.5.
current char conversionChar1,Char2 True if char_conversionChar1,Char2 is in effect (see sections A.2.2, A.5). Either
Authors manuscript
Appx. A
Succeeds by unifying Arg1 with Arg2 in the normal manner (i.e., the same way as when arguments are matched in procedure calls). Results are undened if you try to unify a term with another term that contains it (e.g., X = fX, or fX,gX = fY,Y). (Commonly, such a situation produces cyclic pointers that cause endless loops when another procedure later tries to follow them.)
unify with occurs checkArg1,Arg2 Succeeds by unifying Arg1 with Arg2, but explicitly checks whether this will
attempt to unify any term with a term that contains it, and if so, fails:
?- unify_with_occurs_checkX,fX. no
This version of unication is often assumed in work on the theory of logic programming.
Arg1 = Arg2
Succeeds if the two arguments cannot be unied (using the normal unication process).
A.8.2. Comparison
(See also the arithmetic comparision predicates
Arg1 == Arg2 = = =:= in section A.6.)
Succeeds if Arg1 and Arg2 are the same term. Does not unify them and does not attempt to instantiate variables in them.
Arg1 == Arg2
Succeeds if Arg1 and Arg2 are not the same term. Does not unify them and does not attempt to instantiate variables in them.
Arg1 @ Arg2
Succeeds if Arg1 precedes Arg2 in alphabetical order. All variables precede all oatingpoint numbers, which precede all integers, which precede all atoms, which precede all structures. Within terms of the same type, the alphabetical order is the collating sequence used by the computer, and shorter terms precede longer ones.
Arg1 @= Arg2
Succeeds if Arg1 @ Arg2 or Arg1 == Arg2. Does not perform unication or instantiate variables.
Arg1 @ Arg2
Authors manuscript
Sec. A.8.
479
).
Succeeds if Arg is an integer. Note that this tests its data type, not its value. Thus integer3 succeeds but integer3.0 fails.
floatArg
Succeeds if Arg is a oatingpoint number. Thus float3.3 succeeds but float3 fails.
Authors manuscript
480
Both N and Term must be instantiated.
Term =.. List
Appx. A
Succeeds if List is a list consisting of the functor and all arguments of Term, in order. Term or List, or both, must be at least partly instantiated.
?- fa,b =.. What. What = f,a,b ?- What =.. f,a,b What = fa,b copy termTerm1,Term2 Makes a copy of Term1 replacing all occurrences of each variable with a fresh variable (like changing fA,B,A to fW,Z,W). Then unies that copy with Term2. ?- copy_termfA,B,A,What. A = _0001 B = _0002 What = f_0003,_0004,_0003
we get:
?- clausegreenWhat,Body. What = _0001, Body = moldy_0001 What = kermit, Body = true ;
current predicateFunctor Arity Succeeds if Functor Arity gives the functor and arity of a currently dened
Authors manuscript
Sec. A.8.
481
Gives multiple solutions upon backtracking. Note that current_predicateFunctor Arity succeeds even if all the clauses of the predicate have been retracted (or if the predicate was declared dynamic but no clauses were ever asserted), but not if the predicate has been abolished.
assertaClause Adds Clause at the beginning of the clauses for its predicate. If there are no
clauses for that predicate, the predicate is created and declared to be dynamic. If the predicate already has some clauses and is static, an error condition is raised.
assertzClause Like asserta, but adds the clause at the end of the other clauses for its predicate. NOTE: assert (without a or z) is not included in the standard. retractClause
Removes from the knowledge base a dynamic clause that matches Clause (which must be at least partly instantiated). Gives multiple solutions upon backtracking. Note that the fact greenkermit could be retracted by any of the following queries:
?- retractgreenkermit. ?- retractgreenkermit :- true. ?- retractgreen_ :- _.
Completely wipes out the dynamic predicate designated by Functor Arity, as if it had never existed. Its dynamic declaration is forgotten, too, and current_predicate no longer recognizes it. This is a more powerful move than simply retracting all the clauses, which would leave the dynamic declaration in place and leave current_predicate still aware of the predicate.
Finds each solution to Goal; instantiates variables to Term to the values that they have in that solution; and adds that instantiation of Term to List. Thus, given
greenkermit. greencrabgrass.
Authors manuscript
482
?- findallfX,greenX,L. L = fkermit,fcrabgrass
Appx. A
This is the simplest way to get a list of the solutions to a query. The solutions found by findall are given in the order in which the normal searchingand backtracking process nds them.
bagofTerm,Goal,List Like findallTerm,Goal,List except for its treatment of the FREE VARIABLES of Goal (those that do not occur in Term). Whereas findall would try all possible values of all variables, bagof will
pick the rst set of values for the free variables that succeeds, and use only that set of values when nding the solutions in List. Then, if you ask for an alternative solution to bagof, youll get the results of trying another set of values for the free variables. An example:
parentmichael,cathy. parentmelody,cathy. parentgreg,stephanie. parentcrystal,stephanie. ?- findallWho,parentWho,Child,L. L = michael,melody,greg,crystal ?- bagofWho,parentWho,Child,L. L = michael,melody ; L = greg,crystal Child is free variable with Child = cathy with Child = stephanie
If in place of Goal you write Term^Goal, any variables that occur in Term will not be considered free variables. Thus:
?- bagofWho,Child^parentWho,Child,L. L = michael,melody,greg,crystal
Exits from the Prolog system (or from a compiled program), passing the integer N to the operating system as a return code. (The signicance of the return code depends on the operating system. For example, in MSDOS and UNIX, return code 0 is the usual way of indicating normal termination.)
Authors manuscript
Sec. A.9.
Modules
483
Some import, metapredicate, and other directives The predicates themselves :- end_module name . The four arguments of module are: The name of the module.
Authors manuscript
484
:- modulemy_list_stuff, last 2,reverse 2 , :- begin_modulemy_list_stuff. last E ,E. last _|E ,Last :- lastE,Last. reverseList1,List2 :- reverse_auxList1, reverse_aux H|T ,Stack,Result :reverse_aux ,Result,Result. :- end_modulemy_list_stuff.
Appx. A
,List2.
Figure A.1
Example of a module.
A list of ordinary predicates to be EXPORTED (made callable from other modules that import this one). A list of metapredicates to be exported (well return to this point shortly). A list of predicates to be made ACCESSIBLE from other modules (i.e., callable only by prexing the name of this module and a colon to their names). If you write module_part instead of module, you can give just part of the denition of a module; later you can add to it with another module_part with the same module name. In a module, op and dynamic work the same way as if you arent using the module system, except that they have scope only over one module. The other declarations work as follows:
:- importModule.
All the predicates that exported by Module are to be imported into (and hence usable in) the current module. (Used in module body.)
:- importModule, Pred Arity,Pred Arity... .
A.9.4. Metapredicates
A METAPREDICATE is a predicate that needs to know what module it is called from. Examples include abolish, asserta, assertz, clause, current predicate, and retract, all of which manipulate the predicates in the module that they are called from (not the module they are dened in); and bagof, setof, findall, catch, call,
Authors manuscript
Sec. A.9.
Modules
485
and once, all of which take goals as arguments and need to be able to execute them in the module they are called from. A metapredicate declaration looks like this:
:- metapredicatexyz+,?,-,:.
That is: xyz has four arguments. The rst will always be instantiated, the second may or may not be instantiated, the third will always be uninstantiated, and the fourth needs the calling modules name prexed to it when it is called. Thus, if module mymod calls predicate xyz, the fourth argument of xyz will arrive with mymod: prexed to it. Recall that : is an inx operator.
would work from any module, even though reverse_aux is not exported by the module that denes it.
Succeeds if its argument is the name of any currently existing module. Arguments need not be instantiated.
current visibleModule,Pred Arity Succeeds if Pred Arity describes a predicate that is dened in Module and is
visible (callable) from the module in which this query is taking place. Arguments need not be instantiated.
current accessibleModule,Pred Arity Same as current_visible except that it picks up predicates that are accessible
but not exported, i.e., predicates that can only be called by prexing them with the module name and a colon.
abolish moduleModule Like abolish but unloads a complete module in a single step.
Authors manuscript
486
Appx. A
Authors manuscript
Appendix B
B.1. INTRODUCTION
In this appendix, we briey note some differences between implementations that affect portability of Prolog programs. We make no attempt to be complete; all we want to do is alert you to some sources of difculty that you might otherwise overlook.1 We deal only with implementations that are, at least at rst sight, compatible with Edinburgh Prolog. There are many other Prologlike languages (some of them even called Prolog) that are outside our purview. All the discrepancies noted here will diminish or disappear as implementors adopt the emerging ISO standard. Much of the information in this appendix is based on tests performed with ALS Prolog 1.2, Arity Prolog versions 4.0 and 6.1.9, Cogent Prolog 2.0, LPA Prolog 3.1, and Expert Systems Ltd. (ESL) Public Domain Prolog2 version 2.35 (all for MSDOS); LPA Prolog 2.3 for Windows (considerably newer than 3.1 for DOS); and Quintus Prolog 2.5.1 and 3.1.4 and SWIProlog 1.6.14 (for UNIX). These are intended only as samples, to give you some idea of the diversity that exists. We deliberately chose older versions of these products, rather than the latest, because the oldest versions are the least compatible with each other.
1 We
487
Authors manuscript 693 ppid September 9, 1995 Prolog Programming in Depth
488
Appx. B
B.2. WHICH PREDICATES ARE BUILT IN? B.2.1. Failure as the symptom
Notoriously, in most implementations, Prolog queries simply fail if they involve a call to a nonexistent predicate, or a predicate with an argument of the wrong type. Quintus Prolog complains about nonexistent predicates, but most other Prologs do not. Normally, then, you will attack portability problems by using the debugger to nd out which query is failing that ought to be succeeding. When porting a program, be sure to test it thoroughly so that all the calls to builtin predicates are exercised.
Many of the library predicates are worth studying as examples of good (and sometimes remarkably creative) Prolog code.
and retractall
To retract all the clauses of f 2, use abolishf 2 in Arity, LPA, ESL, Quintus, and the ISO standard, but abolishf,2 in ALS and SWI, and either one in Cogent Prolog.
Authors manuscript
Sec. B.3.
489
Note that retractall is not part of the ISO standard and is not supported by Arity or ALS. Further, abolish and retractall are not equivalent: abolish wipes out all memory of a predicate (including its dynamic declaration if any), while retractall merely removes all the clauses.
B.3.2.
name
: numeric arguments
The rst argument of name can be a number, as in name2.5,"2.5", in Arity, LPA, and Quintus Prolog, but not in Cogent, ALS, or ESL (which give name'2.5',"2.5"). In SWIProlog, the rst argument can be an integer but not a oatingpoint number. The ISO standard does not include name.
B.3.3.
functor
: numeric arguments
The behavior of functor 3 differs among implementations in the following way: the rst two arguments can be a number, as in functor2.3,2.3,0, in Arity, LPA, Quintus, and ISO Prolog, but not in Cogent, ALS, ESL, or SWI Prolog.
B.3.4.
op
Most Prologs today use the operator priorities specied by the ISO standard (e.g., :- is 1200), but older Prologs used a different set in which the highest priority was 256. Before declaring operators, check the actual priorities of the operators in the implementation that you are using. Not all Prologs support current_op (ALS and Cogent dont), and in those that have it, its behavior is not well standardized (for example, in ESL Prolog2, it retrieves not only the operators, but also all the other atoms in the symbol table, giving them priority 0). The ISO standard says that there cannot be an inx operator and a postx operator with the same name so that the Prolog reader will not have to backtrack but not all Prologs enforce this restriction.
B.3.5.
findall setof
, and bagof
We found some variation in the semantics of findall, setof, and bagof. Specically:
findall is not implemented in ALS Prolog or ESL Prolog2.
ALS Prolog 1.2 crashes (!) if the goal argument of bagof or setof contains a cut. In the other Prologs, cuts in this position are permitted and bagof and setof are opaque to cuts. In Cogent Prolog and older versions of LPA Prolog, if you use ^ to indicate a free variable in the goal, the lefthand argument of ^ must be the variable itself. The other Prologs accept any term there and recognize as free all of the variables that occur in it.
Authors manuscript
490 B.3.6.
listing
Appx. B
In many Prologs, listing (with no arguments) dumps more information than just the knowledge base loaded from your program. For example, in Quintus Prolog, it also dumps some facts about le paths, and in ESL Prolog2, it dumps a number of builtin predicates.
Variable goals written without call are transparent to cuts in ALS, LPA, and Cogent Prolog and opaque in the others. The lefthand argument of - is opaque to cuts in ALS, LPA, and Cogent, and transparent in the others; Quintus Prolog does not allow cuts there at all. Disjunction is transparent to cuts in all the Prologs that we tried (thank goodness), but OKeefe (1990:277) indicates that there may be Prologs in which it is not.
Authors manuscript
Sec. B.4.
Control Constructs
491
However, the ISO standard says nothing about tail recursion (nor indexing nor any other memory management issue), and, indeed, in our tests, neither of these examples was tail recursive in ESL Public Domain Prolog2 (which admittedly was designed for free distribution to students and made no pretensions to be an optimizing implementation). In several implementations that include both an interpreter and a compiler, the compiler performs more thorough tailrecursion optimization than does the interpreter.
Authors manuscript
492
Appx. B
at the beginning of the program in order to select DEC10compatible syntax. Otherwise the syntax is slightly different: does not introduce comments, and strings delimited by double quotes ("like this") are not equivalent to lists of codes. Because ESL is no longer in business, updates are not immediately forthcoming, but the implementor (Tony Dodd) hopes to be able to release updates later.
B.5.2. Comments
Some early Prologs did not recognize as a comment delimiter, but we know of no presentday implementations with this limitation. Some Prologs allow nesting of *...* comments, but most do not. SWI and ALS Prolog allow nesting and take * * * * to be a valid comment with another comment inside it. But in the other Prologs that we tried, and in the ISO standard, the comment begins with the rst * and ends with the rst * , regardless of what has intervened. This means you cannot use * * to comment out any Prolog code that has comments delimited with * * within it.
B.5.3. Whitespace
Arity Prolog 4.0 does not allow an operator to appear immediately before a left parenthesis; if it does, it loses its operator status. For example, if you write 2+3-4, Arity Prolog will think the rst + is an ordinary functor with the left parenthesis introducing its argument list, and will report a syntax error; you should write 2 + 3 + 4 instead. As far as we know, no Prolog allows whitespace between an ordinary functor and its argument list; fa cannot be written f a. The ISO standard and all the Prologs that we have tested allow whitespace to appear within an empty list (e.g., in place of ), but discussions on Usenet indicate that there may be Prologs that do not do so.
B.5.4. Backslashes
The ISO standard gives backslashes a special meaning, so that if you want backslashes in le names (e.g., 'c: prolog myfile') you have to write them double ('c: prolog myfile'). The Prologs that we have worked with, however, treat backslashes as ordinary characters.
Authors manuscript
Sec. B.6.
Arithmetic
493
B.5.5. Directives
Quintus Prolog and the ISO standard require dynamic and multifile declarations (see Chapter 2). SWI Prolog requires multifile and accepts dynamic but does not require it. Other Prologs reject these declarations as syntax errors. In Quintus and SWI Prolog, dynamic and multifile are prex operators, so that you can write
:- dynamic mypred 2.
But the ISO standard does not specify this; instead, ISO Prolog will require (and Quintus and SWI already accept) ordinary functor notation:
:- dynamicmypred 2.
B.5.6.
consult
and reconsult
The behavior of consult and reconsult varies quite a bit between implementations, and these predicates are not included in the ISO standard; the method for loading a program is left up to the implementor. We have not attempted to track down all the variation. In older Prologs, consulting a le twice will result in two copies of it in memory, while reconsulting will throw away the previous copy when loading the new one. In Quintus Prolog, however, consult is equivalent to reconsult, and compile causes the program to be compiled rather than interpreted.
Authors manuscript
494
Appx. B
But the query should succeed, because Y is X = Y is 2+3, and indeed it does in Quintus Prolog 3.1. The problem in Quintus Prolog 2.5 was that before looking at the instantiation of X, the Prolog system has already converted Y is X into an operation that simply copies a number. What you had to do instead is this:
?- X = 2+3, callY is X. X = 2+3 Y = 5
That way, the Prolog system does not try to do anything with is until the entire argument of call has been constructed.
Authors manuscript
Sec. B.7.
495
and get0
In most Prologs, get and get0 return -1 at end of le. In Arity Prolog, they simply fail at end of le. In most Prologs, get0 reads every byte of the input le. In ALS Prolog, get0 skips all bytes that contain code 0, and converts the sequence 13, 10 (Return, Linefeed) to simply 10. See the discussion of get_byte in Chapter 5.
Authors manuscript
Appx. B
~a Print an atom (without quotes). ~nc Print an integer by taking it as an ASCII code and printing the corresponding
8. ~nR Same, but uses capital A, B, C: : : for digits greater than 9. ~ns Print an (Edinburghstyle) string as a series of characters. Only the rst n characters are printed. If n is omitted, the whole string is printed. NOTE: format'~s', "abcde" is correct; format'~s',"abcde" is incorrect syntax (because "abcde" is a list of integers and is taken to be the whole list of arguments to be printed).
n
Here n stands for any integer, and is optional. If you write * in place of n, the next element in the list of values will be used as n. See the manual for further details.
The rst argument is either an atom or a string; the second argument is a list of values to be printed. Table B.1 lists some of the format speciers that you can use in Quintus Prolog 2.5.1.
is translated as noun dog|X ,X. However, this raises a problem if there is something in the rule with a side effect, such as a cut:
Authors manuscript
Sec. B.8.
noun --
497
As written, this rule should perform the cut before looking for dog, but its usual translation is
noun dog|X ,X :- !.
which does these two things in the wrong order. Of the Prologs that we tried, only Arity and Cogent make no attempt to solve this problem. The translations of noun -- !, dog in the other Prologs are:
nounX,Y nounX,Y nounX,Y nounX,Y ::::!, !, !, !, 'C'dog,X,Y. '$C'dog,X,Y. '$char'dog,X,Y. X = dog|Y .
The ALS solution is the most elegant. The others rely on a builtin predicate 'C' 3 or equivalent, dened as:
'C' X|Y ,X,Y.
Quintus uses 'C' to deal with all terminal nodes, but SWI uses '$char' only where the rule introduces both terminal nodes and nonterminals or Prolog goals.
means Parse hasnt as a verbaux, and put not at the beginning of the input string, and translates to:
verbauxA,B :- 'C'A,hasnt,B, 'C'B,not,C.
Of the Prologs we tried, only Quintus, SWI, and the freeware DCG translator written by R. A. OKeefe handled this rule correctly.
B.8.3.
phrase
To parse a sentence in Clocksin and Mellish (1984) Prolog, youd use a query like this:
?- phrases, the,dog,barks , .
Of the Prologs that we tested, only Quintus and LPA still support phrase.
Authors manuscript
498
Appx. B
Authors manuscript
Bibliography
Abelson, H., and Sussman, G. J. (1985) Structure and interpretation of computer programs. Cambridge, Massachusetts: M.I.T. Press. Abramowitz, M., and Stegun, I. A. (1964) Handbook of mathematical functions with formulas, graphs, and mathematical tables. (National Bureau of Standards Applied Mathematics Series, 55.) Washington: U.S. Government Printing Ofce. Adams, J. B. (1976) A probability model of medical reasoning and the MYCIN model. Mathematical biosciences 32:177186. Aikins, J. S.; Kunz, J. C.; and Shortliffe, E. H. (1983) PUFF: an expert system for interpretation of pulmonary function data. Computers and biomedical research 16:199208. AtKaci, H. (1991) Warrens abstract machine: a tutorial reconstruction. Cambridge, Massachusetts: MIT Press. Allen, J. F. (1987) Natural language understanding. Menlo Park, California: Benjamin Cummings. Boizumault, P. (1993) The implementation of Prolog. Princeton, N.J.: Princeton University Press. Bol, R. H. (1991) An analysis of loop checking mechanisms for logic programs. Theoretical computer science 86:3579. Bowen, K. A. (1991) Prolog and expert systems. New York: McGrawHill.
499
Authors manuscript 693 ppid September 9, 1995 Prolog Programming in Depth
500
Appx. B
Buchanan, B. G. (1986) Expert systems: working systems and the research literature. Expert systems 3.3251. Campbell, J. A., ed. (1984) Implementations of Prolog. Chichester: Ellis Horwood. Charniak, E., and McDermott, D. (1985) Introduction to articial intelligence. Reading, Mass.: AddisonWesley. Chomsky, N. (1975) Syntactic structures. The Hague: Mouton. Clocksin, W. F., and Mellish, C. S. (1984) Programming in Prolog. Berlin: SpringerVerlag. Second edition.
Covington, Michael A. (1989) A numerical equation solver in Prolog. Computer Language 6.10 (October), 4551. Covington, Michael A. (1994) Natural language processing for Prolog programmers. Englewood Cliffs, N.J.: PrenticeHall. Dahl, V., and SaintDizier, P., eds. (1985) Natural language understanding and Prolog programming. Amsterdam: NorthHolland. Dahl, V., and SaintDizier, P., eds. (1985) Natural language understanding and Prolog programming, II. Amsterdam: NorthHolland. Dahl, V., and McCord, M. C. (1983) Treating coordination in logic grammars. American Journal of Computational Linguistics 9:6991. Duda, R.; Hart, P. E.; Nilsson, N. J.; Barrett, P.; Gaschnig, J. G.; Konolige, K.; Reboh, R.; and Slocum, J. (1978) Development of the PROSPECTOR consultation system for mineral exploration. Research report, Stanford Research Institute. Fromkin, V., and Rodman, R. (1993) An introduction to language. 5th edition. Ft. Worth, Texas: Harcourt Brace Jovanovich. Gabbay, D.; Hogger, C.; and Robinson, A. (eds.) (1994) Handbook of logic for articial intelligence and logic programming, Vol. III. Oxford: Oxford University Press. Ginsberg, M. L. (1987) Readings in nonmonotonic reasoning. Los Altos, Calif.: Morgan Kaufmann. Grishman, R. (1986) Computational linguistics: an introduction. Cambridge: Cambridge University Press. Grosz, B. J.; Sparck Jones, K.; and Webber, B. L., eds. (1986) Readings in natural language processing. Los Altos, California: Morgan Kaufmann. Hamming, R. W. (1971) Introduction to applied numerical analysis. New York: McGraw Hill. Hoare, C. A. R. (1962) Quicksort. Computer journal 5:1015. Hodgson, J. P. E., ed. (1995) Prolog: Part 2, Modules Working Draft 8.1 (ISO/IEC JTC1 SC22 WG17 N142). Teddington, England: National Physical Laboratory (for ISO). Hogger, C. J. (1984) Introduction to logic programming. London: Academic Press. Jackson, P. (1986) Introduction to expert systems. Reading, Mass.: AddisonWesley.
Authors manuscript
Sec. B.8.
501
Kain, Richard Y. (1989) Computer architecture, vol. 1. Englewood Cliffs, N.J.: Prentice Hall. Karickhoff, S. W.; Carreira, L. A.; Vellino, A. N.; Nute, D. E.; and McDaniel, V. K. (1991) Predicting chemical reactivity by computer. Environmental Toxicology and Chemistry 10:14051416. Kluzniak, F., and Szpakowicz, S. (1985) Prolog for programmers. London: Academic Press. Knuth, D. E. (1973) The art of computer programming, vol. 3: Sorting and searching. Reading, Massachusetts: AddisonWesley. Lindsay, R. K.; Buchanan, B. G.; Feigenbaum, E. A.; and Lederberg, J. (1980) Applications of articial intelligence for organic chemistry: the DENDRAL project. New York: McGrawHill. Luger, G. F. (1989) Articial intelligence and the design of expert systems. Redwood City, Calif.: Benjamin/Cummings. Maier, D., and Warren, D. S. (1988) Computing with logic: logic programming with Prolog. Menlo Park, California: BenjaminCummings. Mamdani, E. H., and Gaines, B. R., eds. (1981) Fuzzy reasoning and its applications. London: Academic Press. Marcus, C. (1986) Prolog programming. Reading, Massachusetts: PrenticeHall. Matsumoto, Y.; Tanaka, H.; and Kiyono, M. (1986) BUP: a bottomup parsing system for natural languages. In van Caneghem and Warren (1986), 262275. Merritt, D. (1989) Building expert systems in Prolog. New York: SpringerVerlag. Newmeyer, F. J. (1983) Grammatical theory: its limits and its possibilities. Chicago: University of Chicago Press. Newmeyer, F. J. (1986) Linguistic theory in America. 2nd edition. Orlando: Academic Press. Nute, D. (1992) Basic defeasible logic. In L. Farinas del Cerro and M. Penttonen (eds.) Intensional logics for programming, 125154. Oxford: Oxford University Press. Nute, D. (1994) A decidable quantied defeasible logic. In D. Prawitz, B. Skyrms, and D. Westerstahl (eds.) Logic, methodology and philosophy of science IX, 263284. New York: Elsevier. OConnor, D. E. (1984) Using expert systems to manage change and complexity in manufacturing. In W. Reitman, ed., Articial intelligence applications for business: proceedings of the NPU Symposium, May, 1983, 149157. Norwood, N.J.: Ablex. OKeefe, R. A. (1990) The craft of Prolog. Cambridge, Massachusetts: MIT Press. Parkinson, R. C.; Colby, K. M.; and Faught, W. S. (1977) Conversational language comprehension using integrated patternmatching and parsing. Articial Intelligence 9:111134. Reprinted in Grosz et al. (1986), 551562. Patil, R. S.; Szolovits, P.; and Schwartz, W. B. (1981) Modeling knowledge of the patient in acidbase and electrolyte disorders. In P. Szolovits, ed., Articial
Authors manuscript
502
Appx. B
intelligence in medicine, 191226. (AAAS Selected Symposium 51.) Boulder, Colorado: Westview Press. Pereira, F. C. N. (1981) Extraposition grammars. American Journal of Computational Linguistics 7:243256. Pereira, F. C. N., and Shieber, S. M. (1987) Prolog and naturallanguage analysis. (CSLI Lecture Notes, 10.) Stanford: Center for the Study of Language and Information (distributed by University of Chicago Press). Pereira, F. C. N., and Warren, D. H. D. (1980) Denite clause grammars for language analysis a survey of the formalism and a comparison with augmented transition networks. Articial Intelligence 13:231278. Reprinted in Grosz et al. (1986), 101124. Press, W. H.; Flannery, B. P.; Teukolsky, S. A.; and Vetterling, W. T. (1986) Numerical recipes: the art of scientic computing. Cambridge: Cambridge University Press. Richer, M. H. (1986) An evaluation of expert system development tools. Expert systems 3:167183. Scowen, R., ed. (1995) Prolog part 1, general core. (ISO/IEC 13211-1:1995.) Geneva: International Organization for Standardization (ISO). Sells, P. (1985) Lectures on contemporary grammatical theories. (CSLI Lecture Notes, 3.) Stanford: Center for the Study of Language and Information (distributed by University of Chicago Press). Shieber, S. M. (1986) An introduction to unicationbased approaches to grammar. (CSLI Lecture Notes, 4.) Stanford: Center for the Study of Language and Information (distributed by University of Chicago Press). Shoham, Y. (1994) Articial intelligence techniques in Prolog. San Francisco: Morgan Kaufmann. Shortliffe, E. H. (1976) Computerbased medical consultation: MYCIN. New York: Elsevier. Slocum, J. (1985) A survey of machine translation: its history, current status, and future prospects. Computational Linguistics 11:117. Smith, D. E.; Genesereth, M. R.; and Ginsberg, M. L. (1986) Controlling recursive inference. Articial Intelligence 30:343389. Steele, G. L. (1978) RABBIT: a compiler for SCHEME. MIT Articial Intelligence Technical Report 474. Sterling, L., and Shapiro, E. (1994) The art of Prolog. Second edition. Cambridge, Massachusetts: M.I.T. Press. Walden, J. (1986) File formats for popular PC software: a programmers reference. New York: Wiley. Warren, D. H. D. (1986) Optimizing tail recursion in Prolog. In van Caneghem and Warren (1986), 7790. Warren, D. H. D., and Pereira, F. C. N. (1982) An efcient easily adaptable system for interpreting natural language queries. American Journal of Computational Linguistics 8:110122.
Authors manuscript
Sec. B.8.
503
Wirth, N. (1986) Algorithms and data structures. Englewood Cliffs, N.J.: PrenticeHall.
Authors manuscript