One very important, and very tricky, predicate is the ’cut’, written as the exclamation mark !. This is quite dangerous in inexperienced hands (such as yours!). It is an instruction to Prolog not to back-track looking for other solutions. Here is are some examples:
Negation as failure In dealing with data-base Prolog, we discovered that the correct definition of brother/2 and sister/2 require a test for inequality:
sister(X,Y):-
female(X), parent(P, X), parent(P, Y), different(X,Y). |
We know how to check that two terms are the same (by unification, using =/2), but how do we check that they are different? In principle by checking that they do NOT unify. In fact, Prolog has a built-in predicate \+/1 which allows this to be done:10
?- \+ a=b.
Yes |
So different/2 could be programmed as:
different(X, Y):-
\+ X=Y. |
The interesting thing from the present perspective is how \+/1 itself is programmed. The standard definition is the following two clauses:
\+ X :- X, !, fail.
\+ X. |
These clauses MUST occur in this order, if the definition is to work. When \+ term is called, X is instantiated to term, and the first call in the body of the first clause of \+/1 tests to see if term is true. If it is known to the Prolog data-base, this call will succeed and control will pass to !/0 (’cut’). Cut always succeeds, so control passes to the built-in predicate fail/0. fail/0 always fails. (Surprise, surprise.)
Normally, when a goal fails, Prolog backtracks to see if there are any alternative solutions. Here, to see if term has any more solutions. It will then repeat the procedure until all the possibilities have been exhausted, at which point control would pass to the second clause of \+/1, which (since it has no conditions) would succeed.
However, the presence of the cut in the first clause prevents this behaviour. Instead, when the call to cut has succeeded, Prolog ‘cuts away’ all the information about alternative choices before the cut. Therefore, when fail/0 is encountered, there is nowhere else to go and the call \+ term fails straightaway.
The only situation in which the second clause will be reached is if term itself fails. This will be the case if term can’t be found in the Prolog database, or proved indirectly.
So, \+ a=b will fail if a=b; and will succeed if a=b fails.
Read carefully a Prolog textbook before using the cut yourself. (Using \+/1 is OK, provided you realise that when it fails its ’meaning’ is ’Prolog was unable to find a solution’. I.e. ’don’t know’, rather than false. This definition of negation is called ’negation by failure’.
delete_all/3 You may have noticed that the predicate delete_all/3 (which you defined in response to an exercise) does not work as anticipated if you force Prolog to give you more than one solution. Here is what happens:
?- delete_all(a, [a,b,a], X).
X = [b] ? ; X = [b,a] ? ; X = [a,b] ? ; X = [a,b,a] ? ; no |
Recall the definition of delete_all/3:
1. delete_all(_, [ ], [ ]).
2. delete_all(Item, [Item|Tail] , Result):- delete_all(Item, Tail, Result). 3. delete_all(Item, [Head|Tail] , [Head|Result]):- delete_all(Item, Tail, Result). |
What happens is that, when the user requests a second solution, Prolog first of all goes back to the last procedure call made in providing the first solution (this was the second line of clause 2 of the definition, which succeeded by using clause 1 of the definition), and tries to see if there is an alternative way of satisfying the goal
delete_all(a,[ ],_595)
|
There isn’t, so Prolog backtracks; having tried clause 2 unsuccessfully, it now tries clause 3. Clause 3 succeeds with Item = a and Head = a, as can be seen in the trace:
[trace] ?- delete_all(a, [a,b,a], X).
Call: (7) delete_all(a, [a, b, a], _G294) ? creep Call: (8) delete_all(a, [b, a], _G294) ? creep Call: (9) delete_all(a, [a], _G354) ? creep Call: (10) delete_all(a, [], _G354) ? creep Exit: (10) delete_all(a, [], []) ? creep Exit: (9) delete_all(a, [a], []) ? creep Exit: (8) delete_all(a, [b, a], [b]) ? creep Exit: (7) delete_all(a, [a, b, a], [b]) ? creep X = [b] ; Redo: (10) delete_all(a, [], _G354) ? creep Fail: (10) delete_all(a, [], _G354) ? creep (in clause 2) Redo: (9) delete_all(a, [a], _G354) ? creep (in clause 3) Call: (10) delete_all(a, [], _G357) ? creep Exit: (10) delete_all(a, [], []) ? creep Exit: (9) delete_all(a, [a], [a]) ? creep Exit: (8) delete_all(a, [b, a], [b, a]) ? creep Exit: (7) delete_all(a, [a, b, a], [b, a]) ? creep X = [b, a] Yes |
The problem arises because we have assumed that the second clause excludes the third - but it doesn’t. Now that we have access to the cut, we can impose this assumption on the program.
There are (at least) two ways to do this:
3. delete_all(Item, [HeadTail] , [Head|Result]):-
\+ Item = Head, delete_all(Item, Tail, Result). [trace] ?- delete_all(a, [a,b,a], X). Call: (7) delete_all(a, [a, b, a], _G294) ? creep Call: (8) delete_all(a, [b, a], _G294) ? creep Call: (9) a=b ? creep Fail: (9) a=b ? creep Call: (9) delete_all(a, [a], _G354) ? creep Call: (10) delete_all(a, [], _G354) ? creep Exit: (10) delete_all(a, [], []) ? creep Exit: (9) delete_all(a, [a], []) ? creep Exit: (8) delete_all(a, [b, a], [b]) ? creep Exit: (7) delete_all(a, [a, b, a], [b]) ? creep X = [b] ; Redo: (10) delete_all(a, [], _G354) ? creep Fail: (10) delete_all(a, [], _G354) ? creep Redo: (9) delete_all(a, [a], _G354) ? creep Call: (10) a=a ? creep Exit: (10) a=a ? creep Fail: (9) delete_all(a, [a], _G354) ? creep Fail: (8) delete_all(a, [b, a], _G294) ? creep Redo: (7) delete_all(a, [a, b, a], _G294) ? creep Call: (8) a=a ? creep Exit: (8) a=a ? creep Fail: (7) delete_all(a, [a, b, a], _G294) ? creep No |
delete_all(_, [], []).
delete_all(Item, [Item|Tail] , Result):- !, delete_all(Item, Tail, Result). delete_all(Item, [Head|Tail] , [Head|Result]):- delete_all(Item, Tail, Result). |
Once execution of the program has passed the point at which the cut occurs, it cannot subsequently backtrack beyond this point. This means that once the head of clause 2 has unified with a goal, there is no longer any possibility of resorting to clause 3, as the following trace shows.
[trace] ?- delete_all(a, [a,b,a], X).
Call: (7) delete_all(a, [a, b, a], _G294) ? creep Call: (8) delete_all(a, [b, a], _G294) ? creep Call: (9) delete_all(a, [a], _G354) ? creep Call: (10) delete_all(a, [], _G354) ? creep Exit: (10) delete_all(a, [], []) ? creep Exit: (9) delete_all(a, [a], []) ? creep Exit: (8) delete_all(a, [b, a], [b]) ? creep Exit: (7) delete_all(a, [a, b, a], [b]) ? creep X = [b] ; Redo: (10) delete_all(a, [], _G354) ? creep Fail: (10) delete_all(a, [], _G354) ? creep Fail: (9) delete_all(a, [a], _G354) ? creep Fail: (8) delete_all(a, [b, a], _G294) ? creep Fail: (7) delete_all(a, [a, b, a], _G294) ? creep No |
Exercise 12Another example. Recall the definition of my_member/2:11
my_member(Item, [Item|_]).
my_member(Item, [_|Tail]):- my_member(Item, Tail). |
We previously used my_member/2 to discover whether the first argument was an element of the second argument, but, if the first argument is a variable, it can also be used to generate a set of answers:
?- my_member(X, [a,b,c]).
X = a ? ; X = b ? ; X = c ? ; no |
As the following trace shows, each time a goal succeeds, it is by choosing the first clause of my_member/2. Whenever an additional solution is requested, Prolog backtracks from the first clause of the definition and tries the second clause instead, until all possibilities have been exhausted.
[trace] ?- my_member(X, [a,b,c]).
Call: (7) my_member(_G292, [a, b, c]) ? creep Exit: (7) my_member(a, [a, b, c]) ? creep X = a ; Redo: (7) my_member(_G292, [a, b, c]) ? creep Call: (8) my_member(_G292, [b, c]) ? creep Exit: (8) my_member(b, [b, c]) ? creep Exit: (7) my_member(b, [a, b, c]) ? creep X = b ; Redo: (8) my_member(_G292, [b, c]) ? creep Call: (9) my_member(_G292, [c]) ? creep Exit: (9) my_member(c, [c]) ? creep Exit: (8) my_member(c, [b, c]) ? creep Exit: (7) my_member(c, [a, b, c]) ? creep X = c ; Redo: (9) my_member(_G292, [c]) ? creep Call: (10) my_member(_G292, []) ? creep Fail: (10) my_member(_G292, []) ? creep Fail: (9) my_member(_G292, [c]) ? creep Fail: (8) my_member(_G292, [b, c]) ? creep Fail: (7) my_member(_G292, [a, b, c]) ? creep No |
Here is a modified version of the standard definition of member/2; one with a cut in the first clause. How does its behaviour differ from the standard member/2, and why?12
my_memberchk(Item, [Item|_]):- !.
my_memberchk(Item, [_|Tail]):- my_memberchk(Item, Tail). |
Exercise 13Do this exercise with pencil and paper; not on a computer.
What is the function of the following program?
pred(X, [X]).
pred(X, [_|Y]):- pred(X, Y). |
Provide a trace of the program executing the following call and state what the value of X will be when the call terminates.
|?- pred(X, [the,talk,of,the,town]).
|
What would be the result of forcing the program to backtrack?