
62
Chapter
2.
Definite Programs
§11. Cuts
63
The attraction
of
the slowsort progranfis that
it
does give a very clear logic
component for a sorting program. The disadvantage for standard PROLOG systems
is that the only way to make
it
reasonably efficient is to substantially change the
logic. To keep the above simple logic what we require is a computation rule which
coroutines between perm and sorted. In this case, the list is given to perm which
generates a
partial permutation
of
it
and then checks with sorted to see
if
the
partial permutation is correct so far.
If
sorted finds that the partial permutation is
indeed sorted, perm generates a bit more
of
the permutation and then checks with
sorted again. Otherwise, perm undoes a bit
of
the partial permutation, generates a
slightly different partial permutation and checks with sorted again. Such a program
is clearly going to be a great deal more efficient than the one which generates an
entire permutation before checking to see
if
it is sorted.
Thus we can obtain a more efficient sorting program by adding clever control
to the simple logic.
(Of
course, much more efficient sorting programs are known,
but this is not the point
of
the discussion.) There are now a number
of
PROLOG
systems which allow the programmer to specify such control. For example, in
NU-PROLOG [104] the programmer could add the
when declarations
?-
sorted(nil) when ever
?- sorted(x.y) when y
to the program.
If
the argument
of
the call to sorted either is nil or has the form
s.t, where t is a non-variable, then the call proceeds. Thus the calls sorted(nil) and
sorted(3.2.x) will proceed.
If
the argument
of
the call to sorted does not unify
with nil or x.y, then the call proceeds (and then fails).
If
the argument
of
the call
to sorted has the form y or s.y, then the call to sorted delays. Thus the call
sorted(3.y) will delay. When a call sorted(y) or sorted(s.y) is delayed, the variable
y is marked. When this variable is bound later, the delayed subgoal is resumed.
This simple mechanism achieves the desired behaviour.
In standard PROLOG systems, a "generator" subgoal should come before a
"test"
subgoal. Thus perm should be put before sorted,
if
slowsort is to be run on
a standard PROLOG system. However, in NU-PROLOG, the
"test"
should be put
before the "generator". This order, together with appropriate when declarations
on the
"test",
ensures proper coroutining between the
"test"
and the "generator".
The coroutining starts by delaying the
"test".
The "generator" is then run until it
creates a binding which causes the
"test"
to be resumed, and so on.
When declarations would not be
of
major interest
if
their addition always
required programmer intervention. However, NU-PROLOG has a preprocessor
which is able to
automatically add when declarations to many programs in order to
obtain more sensible behaviour. For example, given the slowsort program
as
input,
the preprocessor outputs the above when declarations for sorted. (It also gives
when declarations for perm, delete and
::;;,
but these are not needed for the use
we
have made
of
slowsort.) It does this by finding clauses with recursive calls which
could cause infinite loops and generating sufficient when declarations to stop the
loops. The preprocessor is also able to recognise that sorted is a
"test"
and should
appear before perm in the first clause. It will reorder sorted and perm,
if
necessary.
An account
of
the automatic generation
of
control is given in [74]. By relieving
programmers
of
some
of
the responsibility for providing control in this way,
NU-
PROLOG is a step towards the ideal
of
purely declarative programming.
§11. CUTS
In this section, we discuss the cut, which is a widely used and controversial
control facility offered by PROLOG systems. It is usually written
as
"!"
in
programs, although some systems call it
"slash"
and write it as
"I".
There has
been considerable discussion
of
the advantages and disadvantages
of
cut and, in
particular, whether it "affects the semantics"
of
programs in which it appears. We
argue that cut does
not affect the declarative semantics
of
definite programs, but it
can introduce an undesirable form
of
incompleteness into the refutation procedure.
(In §15, we discuss the effect that cuts can have in a program which has negative
literals in the body
of
a program clause.)
First, we must be precise about what a cut actually does. Throughout this
discussion, we restrict attention to systems which always select the leftmost atom
in a goal. Cut is simply a non-logical annotation
of
programs which conveys
certain control information to the system. Although it is written like an atom in the
body
of
a clause,
it
is not an atom and has no logical significance at all.
On
the
other hand, for pedagogical reasons, it is sometimes convenient to regard it
as
an
atom which succeeds immediately on being called. The declarative semantics of a
program with cuts is exactly the declarative semantics
of
the program with the cuts
removed. In other words, the cuts do not in any way modify the declarative
reading
of
the program.