1.7 Classifying problems
2.1.7 Failing usefully
As it had been already stressed, backtracking is initiated when some grounded predicate fails. However, there are situation when backtracking is forced by deliberately using an ”always false” atom calledfail/0. This is illustrated by program2_2_fail.pl: /*1*/ top:- /*2a*/ who_are_your_friends_1. /*2b*/ % who_are_your_friends_2. /*2c*/ % who_are_your_friends_3. /*3*/ friend("Mark"). /*4*/ friend("Jack"). /*5*/ friend("Andrew").
% For ’who_are_your_friends_1’ there is no backtracking, % just one solution (the first one from top) is given: /*6*/ who_are_your_friends_1:-
/*7*/ friend(Who),
/*8*/ write("Friend: "),write(Who), nl.
% For ’who_are_your_friends_2’, ’fail’ generates backtracking,
% all friends are displayed but eventually the program fails with a ’No’:
/*9*/ who_are_your_friends_2:- /*10*/ friend(Who),
/*11*/ write("Friend: "),write(Who),nl, /*12*/ fail.
% For ’who_are_your_friends_3’, ’fail’ generates backtracking, % but when no backtracking can be performed any more, the second % definition of ’who_are_your_friends_3’i invoked
% and the program end with a Y’es’ /*13*/ who_are_your_friends_3:- /*14*/ friend(Who),
/*15*/ write("Friend: "),write(Who), nl, /*16*/ fail.
/*17*/ who_are_your_friends_3:-
/*18*/ write("Those are all my friends."),nl.
The messages are:
Message forwho_are_your_friends_1: Friend: Mark. Yes.
After clicking twice ”more” in the Main Window from Figure 2 (to enforce other solutions), the message is:
Friend: Jack Yes.
Friend: Andrew Yes.
Message forwho_are_your_friends_2:
Friend: Mark Friend: Jack Friend: Andrew No.
Message forwho_are_your_friends_3:
Friend: Mark Friend: Jack Friend: Andrew
Those are all my friends. Yes.
shows the impossibility of writing declarative programs that work without some procedural crutches. It is worth remembering that fail/0 is functioning like any predicate which is always false, e.g. 2 is 3.
2.1.8
Recursive definitions
Arecursive predicate definition is given by:
1) arule with thehead being the defined predicate and thebodycontaining this very predicate with different argument structure;
2) afact, most often the grounded predicate.
The conciseness, declarativity and power ofProlog (andCLP as well) is largely due to the widespread usage of recursive definitions of predicates. As example may serve the list definition. Lists are basicProlog data structures. They are n-tuplesof elements, beginning with a left-hand square bracket and closing with a right-hand square bracket:
List = [Element_1, Element_2,...,Element_n]. It may be decomposed as follows:
List = [Head|Tail]
whereHeadis the first element of the listList, andTailis the list that remains after removing the first element. BecauseTailis a list, it must obviously con- tain aHead_of_Tailand aTail_of_Tail. The last one is a list, therefore we can speak about theHead_of_Tail_of_Tailand theTail_of_Tail_of_Tail, and so on, until the empty list[]is reached, which has no head. So thelist concept is in fact defined recursively. And most predicates with lists as argu- ments use recursion as well. The most simple illustration is provided by defining a predicate that determines list membership. It has the structure:
membership(Member,List),
which is intended to mean thatMemberis an element of List. It is defined by stating the fact that the head of the list is a list member, no matter what the tail is:
and stating the recursive rule that a list member is the member of the list tail, no matter what the head is:
/*2*/ membership(Member,[_|O]) :- /*3*/ membership(Member,O).
The underscore_denotes an anonymous variable; it means we do not care about the value it may be grounded with and are not interested in knowing this value. Let’s have a look at how this definition is working. The program 2_3_list.pl: /*1*/ top:- /*2*/ membership(E,[1,2,3,4]), /*3*/ writeln(E), /*4*/ fail. /*5*/ top:-
/*6*/ writeln("Those are all elements of the list."). /*7*/ membership(Member,[Member|_]).
/*8*/ membership(Member,[_|O]) :- /*9*/ membership(Member,O).
generates the message:
1 2 3 4
Those are all elements of the list.
The programs query is top. The logical constant topwill be used in the sequel for all Prolog/CLP programs in the book. Prolog compiler attempts to satisfy the query, i.e. make top true. In order to do it, it has to satisfy the predicate in line /*2*/. This invokes the definition from line /*7*/, E is grounded to/*1*/, and because of the other part of the definition (lines/*8*/ and /*9*/, a choice point is created for the predicate membership(). The predicate fail/0 is a built-in that cannot be satisfied, so a backtrack to the choice point is made generating the next element of the list and another choice point, and so on, until (thanks to removing head after head by the action of line /*7*/,/*8*/and/*9*/predicates) the list becomes empty.
The example presented is more general than it seems: Prolog recursion is always defined between a list (in the head of the recursive rule) and the tail of this list (in the body of the recursive rule).
ForECLiP SeP rologthe built-in predicatemember(?Member,?List)functions exactly as the abovemembership()predicate: while using it instead of mem- bership in line/*2*/, lines /*7*/, /*8*/, and /*9*/are not needed, see top1 in2_3_list.pl.
2.1.9
Basic list operations
There are only two such operations:
1. Removing successive heads from a non-empty list and constraining them. This is continued until the list is empty, as shown in the following example:
recursive_predicate([H|T],....):-
% The head is removed and processed: constraining_the_head(H),
... recursive_predicate(O,....).
% Removing of heads leads to an empty list: recursive_predicate([],....).
The recursion with heads removal starts with a [H|T]list, the heads of which are successively removed and processed, until the list is empty. The recursion occurs between the list [H|T](in the head of the rule) and the tail of the listT(in the recurred predicate in the rule body).
2. Adding - as heads - successive elements, generated by some constraints, to a list which is initially entirely or partially empty. This is continued until some special list is generated, as shown in the following example:
recursive_predicate(Tail_of_list,....):- % The head is determined and added: determine_the_head(Head),
...
% Adding heads leads to some Special_list: recursive_predicate(Special_list,,....).
The recursion with adding heads start with an entirely or partially empty Tail_of_list, to which are successively addedHeadsgenerated by some constraints, until the list is some Special_list. The recursion occurs between the Tail_of_list(in the head of the rule) and thehead-added- list [Head|Tail_of_list]in the recurred predicate in the rule body. The important thing to remember is that only heads may be removed from a list, and only heads me be added to a list.
This is illustrated by program2_4_reversal.plthat reverses the order of list elements using two private predicates:
1. my_reverse(Initial_list, Reversed_list) 2. my_reverse(Initial_list, Reversed_list,
Accumulator_of_reversed_list)
The namemy_reversewas chosen to distinguish it from the built-inreverse/2, which does exactly the same job.
The program is removing successively heads from the the Initial_list and adding the removed heads successively to the initially empty list named Accumulator_of_reversed_list. When theInitial_listis empty (i.e. when all its elements have been transferred in reverse order to theAccumulator_of_ reversed_list), theReversed_listis unified with the accumulator.
The program looks like this:
/*1*/ top:- /*2*/ my_reverse([a,b,c,d],Reversed_list), /*3*/ write(Reversed_list). /*4*/ my_reverse(Initial_list,Reversed_list):- /*5*/ my_reverse(Initial_list,Reversed_list,[]). /*6*/ my_reverse([],A,A). /*7*/ my_reverse([H|T],Reversed_list,A):- /*8*/ my_reverse(T,Reversed_list,[H|A]).
The message generated is:
Reversed list = [d, c, b, a]
The program uses two predicates with the same name but different arity (my_reverse/2and my_reverse/3), which is perfectly O.K. Different arities make the names distinguishable to the compiler.
The basic rule is in lines /*7*/and /*8*/: there the headH of the initial list[H|T]is removed from this list and added as head to the listA, resulting in [H|A].
The use of accumulator deserves some comments. InProlog/CLP accumu- lators areartificial variables14 that allow to write so-calledtail-recursiverules, i.e. rules with the head calling itself at the end of the body; the rule in lines /*7*/and/*8*/is just a trivial example of a tail-recursive rule. Tail-recursion is the most parsimonious type of recursion as far as stack space is concerned: it does not need any stack at all.