Prolog: Introduction to Cuts¶
This min
function has a problem:
min(X, Y, X) :- X =< Y. % clause 1
min(X, Y, Y) :- X > Y. % clause 2
For instance:
?- min(1, 2, M).
M = 1 ;
false.
But what we really want is for it to work like this:
?- min(1, 2, M).
M = 1.
The problem is that it returns false
after it succeeds with M = 1
. The
cause of this problem is that min(1, 2, M)
satisfies clause 1, and then,
if the user types ;
, it keeps going and tries to satisfy clause 2, which
fails and produces the false
.
To fix this, we need some way to tell Prolog to not keep going after it
succeeds in the first clause. The cut operator, !
, does exactly that.
When Prolog encounters a !
, it will not search for further ways to
satisfy the query.
So we can rewrite min
like this:
min(X, Y, X) :- X =< Y, !. % clause 1
min(X, Y, Y) :- X > Y. % clause 2
Now it works as desired:
?- min(1, 2, M).
M = 1.
The min_list
function has the same problem. Even when using the version of
min
with the !
, it still does not work as we would like:
min_list([X], X). % base case
min_list([Head|Tail], Min) :- % recursive case
min_list(Tail, Tmin),
min(Head, Tmin, Min).
For instance:
?- min_list([4, 3, 5], M).
M = 3 ;
false.
We can fix this by adding a !
to the end of the recursive case:
min_list([X], X). % base case
min_list([Head|Tail], Min) :- % recursive case
min_list(Tail, Tmin),
min(Head, Tmin, Min),
!.
Now it does this:
?- min_list([4, 3, 5], M).
M = 3.
Using cuts takes some getting used to, so lets see another example. The
neg(Nums, Negs)
function assigns to Negs
all the numbers in Nums
that are less than 0:
negs([], []). % clause 1
negs([N|Ns], [N|Rest]) :- % clause 2
N < 0,
negs(Ns, Rest).
negs([_|Ns], Rest) :- % clause 3
negs(Ns, Rest).
It has a problem:
?- negs([-3], Negs).
Negs = [-3] ;
Negs = [].
As you can see, it returns two values for Negs
because after it succeeds
with Negs = [-3]
, it then keeps searching and also satisfies the 3rd
clause.
One way to fix it is to add a cut, !
, in the second clause:
negs([], []). % clause 1
negs([N|Ns], [N|Rest]) :- % clause 2
N < 0,
negs(Ns, Rest),
!. % cut added here
negs([_|Ns], Rest) :- % clause 3
negs(Ns, Rest).
The cut tells Prolog to not continue searching after clause 2 is satisfied.
No cut is needed in the first clause because []
never matches with a
pattern like [H|T]
. The third clause doesn’t need a cut because there are
no more clauses after it.
Removing Items From a List¶
Suppose you want to remove all occurrences of an element X
from a list.
You could do it like this:
remove_all(_, [], []). % base case
remove_all(X, [X|Rest], Result) :- % recursive case
remove_all(X, Rest, Result),
!.
remove_all(X, [First|Rest], [First|Result]) :- % recursive case
X \= First, % \= means "doesn't unify"
remove_all(X, Rest, Result).
Note the use of the cut, !
, to stop the search after it succeeds in the
first recursive case.
Here’s how it works:
?- remove_all(5, [3, 4, 5, 6, 5], X).
X = [3, 4, 6].