1 Introduction CIS335, Assignment 4 Advanced Prolog Programming Geraint Wiggins November 12, 2004 This practical is the second self-assessed exercise for MSc students on the Prolog module. It is intended to reinforce the information taught in the lectures, and to give practice in relatively advanced Prolog programming. Unless otherwise stated, the exercises should be answered first away from the computer, and then the answers checked by running them. Feel free to ask your tutor, me, or your fellow students if you get stuck; if you are a student who is secure in Prolog, remember that explaining to others can be a very good way of reaching a better understanding yourself. To save you pointless typing, example code for the exercises which require it may be found in on my web site. 2 Debugging 2.1 Tracing Use the trace command to follow and compare the execution of the following queries, to predicates whose definitions are given in the examples file. Remember that reverse/2 and reverse/3 are distinct predicates. % call to naive reverse?- reverse( [a,b,c], Answer ) % call to accumulating reverse?- reverse( [a,b,c], [], Answer ). Remember that you will need to see the source code as you use the debugger. Concentrate particularly on the two different approaches to reversing lists, and make sure you understand how the accumulator argument works in the second query.? is the help command when in trace mode. 1
2.2 Debugging Use on-paper analysis and the debugger to solve the following problem. I have implemented some prolog predicates according to the following specification. Most of them seem to work, but one or more is/are apparently faulty. Work out, from the specification and supplied code, which of the predicates is/are incorrect, then set a spy point on it/them, and use the debugger to work out what is wrong. It seems fairly likely that at least the quicksort/2 predicate is faulty. Specification This is a specification for a piece of Prolog code which interfaces between two other programs. In other words, it converts the output format of one (a planning program, for storage of shop goods) into the input format of the other (an inventory database system). The output format (ie the input to our program) is a list of terms of the form identifier( reference number, size ) where identifier is one of box, lid, bottle, tin, bag, the referencenumber is an integer, and the size is small, medium, or large. The order of the list is the order in which the planning program has chosen to store the goods. The input format (ie the output of our program) is a list of terms of the form reference numbersize/type, which are sorted by reference number. Type is the same value as the identifier in the output format; size is 1, 2, or 3. A predicate is supplied which will give you some sample output (sample output/1); use it in a query to pass that output to your conversion routine, which will give a result like this when the program works correctly:?- sample output(x),conversion(x,y). X = [box(1,large),lid(1,large),bottle(3,small),bottle(12,sma ll),tin(5,small),lid(5,small),bag(20,large),bag(8,small),bag (6,medium)], Y = [1-box/3,1-lid/3,3-bottle/1,5-tin/1,5-lid/1,6-bag/2,8-ba g/1,12-bottle/1,20-bag/3] 3 Cut 3.1 Coloured cuts Decide which of these predicates contain red or green cuts. If you are unsure, check, by removing the cut and seeing if the logical behaviour is the same (especially when backtracking). % which is the maximum of two numbers? max( X, Y, X ) :- X > Y,!. max( X, Y, Y ) :- X =< Y. 2
% is a value a or not? test( a ). a only( X, yes ) :-!,test( X ). a only( X, no ) :- \+ test( X ). % is a value a or not? (version 2) test( a ) :-!. a only( X, yes ) :- test( X ). a only( X, no ) :-!, \+ test( X ). 3.2 Committing to solutions How many solutions will the following programs/queries yield on exhaustive backtracking (ie using ; until we get no)? In what order will they appear? % commit 1 p(c).?- p(x). % commit 2 p(b) :-!. p(c).?- p(x). % commit 3?- p(x),!. 3
% commit 4 q(b).?- p(x),!,q(x). 4 Operators Refer to the SWIProlog manual and look up how to use the predicates op/3 and current op/3 to define and check Prolog operators. Use op/3 to define an operator ++ so that the following goals are true. Note that you will redefine ++ for each example, so don t expect all of them to work at once. You will need to use current op/3 to look up the details of the existing operators, like + and =. % op1?- 1 + 2++ = ( 1 + ( 2 ++ )). % op2?- 1 + 2++ = (( 1 + 2 ) ++). % op3?- a - ++ b = ( a - ( ++ b )). % op4?- ++ a - b = (++ ( a - b )). % op5?- a ++ - 3 = ( a ++ ( - 3 )). % op6?- a ++ b ++ c = ( a ++ ( b ++ c )). % op7?- a ++ b ++ c = (( a ++ b ) ++ c )). % op8?- a ++ ++ c = ( a ++ ( ++ c )). % op9?- a ++ ++ c = (( a ++ ) ++ c ). 4
5 Meta-programming Using the meta-programming facilities of Prolog, we can write a simple Prolog interpreter. We use the built-in predicate clause to give us access to the database look it up in the manual if you are not sure how it works. The basic interpreter (usually called solve/1) looks like this. It deals only with conjunctions in clauses and disjunctions between (but not within) clauses. solve( true ). solve( Goal ) :- \+ Goal = (, ), clause( Goal, Body ), solve( Body ). solve(( Goal1, Goal2 )) :- solve( Goal1 ), solve( Goal2 ). Use this basic program (in the examples file) to explore the operation of the append/3 program in the examples file. Compare the trace of solve calling append with that of a direct call at the prompt, and verify that the effect is the same. If you wish to try any other predicates with solve/1 remember that you must declare them as dynamic before you can do so. Remember also that if you wish to use conjunctions in your call to solve/1 you must put them in () otherwise they will be mistaken as calls to solve/2 or solve/3 because of the comma operator. Having understood the operation of the program, add a further two clauses so that the interpreter can solve disjunctive goals, to achieve the following behaviour:?- solve(( append( [a,b], [c], D ); append( [x], [y,z], D ))). D = [a,b,c]? ; D = [x,y,z]? ; no Hint: consider how conjunction is implemented, by taking the meta-level comma and replacing it by an object-level one. You will need to make minor modifications to some of the other clauses too, because ;/2 is defined as a built-in, static (not dynamic) predicate. Finally, rewrite the clause that deals with looking up the program clause (clause 2 of solve/1) as follows. It is often (though not always) the case that executing goals with fewer uninstantiated variables first can lead to a more efficient execution. Here s an example: 5
q(b).?- p(x),q(a). In this case, q(a) always fails, and because it is determinate that is, there is no choice to be made in its arguments it might as well be executed first. The examples file contains a predicate called conjunction to list/2 which will be helpful here. Use it to unpack the clause body returned to solve/1 by clause/2 into a list, and then write a predicate which will split the list into two lists, one of ground conjuncts and one of non-ground conjuncts (use the meta-predicate ground/1 to test this). You may like to look at the split/4 predicate, above, to get some hints on how to do the splitting up. Put the two lists back together again, the ground ones at the front, and convert them back into a goal using conjunction to list backwards. Then pass that new goal to solve/1. Now use the debugger to investigate the behaviour of the following query:?- solve(( p( X ), p( Y ), append( [x,y], [X], [x,y,d] ))). When you run this, it should fail. Work out why, using the normal Prolog left-to-right computation rule. Then compare the usual behaviour with that of the solve version, above. You should find that your new computation rule never bothers with p(y), because the other two goals can never succeed together. 6