CS 360: Programming Languages Lecture 10: Logic Programming with Prolog Geoffrey Mainland Drexel University
Section 1 Administrivia
Midterm Tuesday Midterm is Tuesday, February 14! Study guide is on the web site. You will have 80 minutes for the exam. No Prolog on the midterm. You may bring a single, hand-written, two-sided, 8.5 11in sheet of notes to the exam. This sheet must have your name on it, and it will be collected with your exam.
Section 2 Reconsidering the amb Language
Solving a logic puzzle with amb Now that we know how the implementation of amb works, how is the following program actually executed? (define (multiple-dwelling) (let ((baker (amb 1 2 3 4 5)) (cooper (amb 1 2 3 4 5)) (fletcher (amb 1 2 3 4 5)) (miller (amb 1 2 3 4 5)) (smith (amb 1 2 3 4 5))) (require (distinct? (list baker cooper fletcher miller smith))) (require (not (= baker 5))) (require (not (= cooper 1))) (require (not (= fletcher 5))) (require (not (= fletcher 1))) (require (> miller cooper)) (require (not (= (abs (- smith fletcher)) 1))) (require (not (= (abs (- fletcher cooper)) 1))) (list (list 'baker baker) (list 'cooper cooper) (list 'fletcher fletcher) (list 'miller miller) (list 'smith smith))))
Section 3 Prolog
Prolog In the previous lecture, we saw a language and interpreter that automated search. Prolog is the most prevalent language that implements automatic search. We called the amb language nondeterministic, but the evaluator itself was actually deterministic it always evaluates the same program in the same way via a systematic search. Prolog also implements systematic search. Becoming proficient in Prolog requires that you really understand how Prolog performs search it s not a magic bullet. See the Online programming language resources link under Resources on the course home page for Prolog tutorials and references.
Prolog Syntax A variable is a sequence of letters and digits that begins with an uppercase letter. Examples: X, Ys. An atom is a sequence of letters and digits that begins with a lowercase letter. Examples: bart, lisa. A single underscore, _, represents an anonymous variable and means I don t care what this value is.
Facts about the Simpsons father(grampa,homer). father(homer,bart). father(homer,lisa). father(homer,maggie). mother(marge,bart). mother(marge,lisa). mother(marge,maggie).?- father(homer,x). % X = bart % yes?- mother(x,bart). % X = marge % yes
Using facts about the Simpsons father(grampa,homer). father(homer,bart). father(homer,lisa). father(homer,maggie). mother(marge,bart). mother(marge,lisa). mother(marge,maggie). parent(x,y) :- father(x,y). parent(x,y) :- mother(x,y).?- parent(x,bart). % X = homer? ; % X = marge % yes The program above says that parent(x,y) is a fact if father(x,y) is a fact. How else can parent(x,y) be derived as a fact? You can read :- as =, or right-to-left implication. Entering ; into the interpreter after a query has been answered tells the interpreter to give us the next answer to the query.
Clauses father(grampa,homer). father(homer,bart). father(homer,lisa). father(homer,maggie). mother(marge,bart). mother(marge,lisa). mother(marge,maggie). parent(x,y) :- father(x,y). parent(x,y) :- mother(x,y). Prolog is based on Horn clauses. A clause has a head and a body. The above program has 9 clauses. The first 7 have no body. A clause without a body is also called a fact, as we have seen. The last two clauses have the same head, parent(x,y). For the head of a clause to hold, all of the facts in its body must hold. Facts in the body of a clause are separated by a comma, which can be read as and. In Prolog, we express programs using relations, not functions.
An exercise Let s try to write a predicate mated(x,y) that holds when X and Y are both parents of the same person. Remember, the body of a clause can contain multiple facts.
How Prolog performs search parent(marge,bart). ancestor(x,y) :- parent(x,y). ancestor(x,y) :- parent(z,y), ancestor(x,z). We can trace the execution of a query using the trace command in gnuprolog (tracing can be turned off with notrace). Let s see how Prolog executes the query ancestor(x,bart)..
How Prolog performs search parent(marge,bart). ancestor(x,y) :- parent(x,y). ancestor(x,y) :- parent(z,y), ancestor(x,z). Let s see how Prolog executes the query ancestor(x,bart)... ancestor(_16,bart) parent(_16,bart) parent(_85,bart), ancestor(_16,_85) parent(marge,bart) Success! {_16 = marge} parent(marge,bart) {_85 = marge} parent(_16,marge) Fail! parent(_134,marge), ancestor(_16,_134) parent(_134,marge) Fail!
How Prolog performs search How would search progress this time given the query ancestor(x,bart).? Note the minor change in our program. parent(marge,bart). ancestor(x,y) :- parent(x,y). ancestor(x,y) :- ancestor(x,z), parent(z,y). For reference, here is the old path through the search space. ancestor(_16,bart) parent(_16,bart) parent(_85,bart), ancestor(_16,_85) parent(marge,bart) Success! {_16 = marge} parent(marge,bart) {_85 = marge} parent(_16,marge) Fail! parent(_134,marge), ancestor(_16,_134) parent(_134,marge) Fail!
Writing programs in Prolog Prolog programs are relations, not functions. By convention, the result is the last argument in a relation. Think of each clause in a definition as a different case. Try to simplify code by matching only the specific case you are interested in.
Lists in Prolog In Prolog... [X,Y] is a list with two elements. [H T] is a list whose head is H and whose tail is T. We can also write, for example, [0 [1,2,3]]. This is the list [0,1,2,3]. As an exercise, let s write member, append, and reverse in Prolog.
Unification in Prolog Consider the member function we just wrote. We used the variable X twice in a pattern this expressed the constraint that these two values must be equal. Prolog uses unification to instantiate variables, i.e., to determine what values variables have. The expression s = t attempts to unify the expressions s and t. Unification takes two terms, s and t, and attempts to find a substitution for the variables in s and t that make s and t equal. The underscore variable, _, is special: it does not represent the same term everywhere it is used.
Unification in Prolog: examples bart = bart bart = lisa bart = X f(a, X) = f(y, b) f(x) = g(x) f(x) = f(a, b) f(a, g(x)) = f(y, b) f(a, g(x)) = f(y, g(b)) Unification is an extremely important algorithm. In particular, it is used for type inference in many languages, e.g., ML, Haskell, F#.
Arithmetic in Prolog Recall that = means terms should be unified. To test for term equality, use ==. For term inequality, use \=. Les-than-or-equal is =<. What do you think X = 2 + 3 does? How about X == 2 + 3? How about 3 + 2 == 2 + 3? To force evaluation of arithmetic expressions, use is. For example, X is 2 + 3. What do you think X =< 2 does? Variables are only instantiated via unification.
Arithmetic in Prolog: gcd gcd(u,0,u). gcd(u,v,w) :- V \= 0, R is U mod V, gcd(v,r,w).
Implementing ; in the amb Language How could we implement ; in our amb interpreter? That is, how can we get the next solution? (define (ambeval exp env succeed fail) ((analyze exp) env succeed fail)) Calling ambeval: (ambeval <exp> the-global-environment (lambda (value fail) value) (lambda () 'failed))
Running programs backwards In Scheme, how would you write a program that takes a list xs, the result, zs, of appending that list to some (unknown) list ys, and computes zs? That is, I want (appendee '(1 2 3) '(1 2 3 4)) to return the result '(4). How could we solve this problem in Prolog using a program we already wrote? This is one advantage of expressing programs as relations instead of functions.
The power of declarative programming? Prolog is often termed a declarative language because programmers declare what a program is supposed to do, but not how it is supposed to be done. That is, the Prolog implementation has to search for a solution. The original idea was that programmers could write a declarative specification for a program, and the language would find an implementation for that specification. Let s see an example... sort(x,y) :- permute(x,y), sorted(y). insert(x,ys,[x YS]). insert(x,[y YS],[Y ZS]) :- insert(x,ys,zs). permute([],[]). permute([x XS],YS) :- permute(xs,zs), insert(x,zs,ys). sorted([]). sorted([_]). sorted([xa,xb XS]) :- XA =< XB, sorted([xb XS]).
The power of declarative programming? How efficient is the sort that we just specified declaratively? How declarative is Prolog? Can the programmer really write a fully declarative specification? Recall our ancestor predicate. How declarative is a language when order matters so much? parent(marge,bart). ancestor(x,y) :- ancestor(x,z), parent(z,y). ancestor(x,y) :- parent(x,y).
Cut Consider the following program for using association lists: assoc(x,[[x,y] _],Y). assoc(x,[_ YS],Z) :- assoc(x,ys,z). lookup(x,y) :- assoc(x,[[break,statement], [double,type],[int,type], [return,statement]],y),!. What do you expect lookup(break,x). to do? How about lookup(x,statement).? The cut prunes all further sibling nodes in the search tree. If Prolog encounters a cut node while backtracking, search continues with the grandparent node.
Cut: another example Consider the following program: a(x, Y) :- b(x),!, c(y). b(1). b(2). b(3). c(1). c(2). c(3). If we ask?- a(q, R)., what should we expect?
Cut: a third example Consider the following slightly different program: a(x) :- b(x),!, c(x). b(1). b(2). b(3). c(2). If we ask?- a(x)., what should we expect? What about?- a(2).?
Prolog vs. amb What are some of the big differences between Prolog and the amb language? amb is a general-purpose language with built-in support for search. Prolog programs must be expressed as Horn clauses. Prolog implements pattern matching and unification.
Prolog: Summary Prolog programs are expressed as a sequence of clauses. Prolog predicates are relations, not functions. A Prolog implementation searches for a solution to a query systematically: clauses are processed top-to-bottom, and the terms in the body of a clause are processed left-to-right.