~1
                       LOGIC PROGRAMMING LESSON 8


In this lesson, I shall combine the Prolog control language (Lesson 7)
and the logic language (Lessons 1 to 6). This will enable you to write
command predicates that vary their action according to their arguments.

~2
Let's look at a simple example. Here is a command predicate that takes a
number as argument, and writes a message saying how big it is.

      write_size(N) :-
          N < 3, !,
          write(tiny).

      write_size(N) :-
          N < 10, !,
          write(small).

      write_size(N) :-
          N < 100, !,
          write(medium).

      write_size(N) :-
          write(big).

There's a copy of this in knowledge base SIZE. Restore it, and try it
out on a few numbers.

~3
Each clause of this predicate (except the final one) consists of three
goals. Remember from Lesson 4 that a GOAL is what we call one single use
of a predicate in the tail of a clause.

The first goal is a test, expressed in Prolog's logic language. It uses
arithmetic comparisons, introduced in Lesson 5.

The second goal is the special symbol !. In speech and English writing,
this is always called "cut", but you must write it in programs as !.
Don't confuse it with the stick-symbol | .

The third goal is a command: the sort of thing we dealt with in Lesson
7.

How does this translate into English?

~4
Altogether, each clause (except the last) can be translated thus:

      To write the size of N:
          if N < 3 then
          write "tiny"
          otherwise go on to the next clause.

The last clause takes effect if none of the comparisons in the others
succeed, and it can be translated as

      To write the size of N:
          (none of the tests in the clauses above succeeded)
          write "big"

~5
So we are defining a command predicate, but a special one where the
internal command is only obeyed if the argument passes a previous test.

It's important to realise that this works because Prolog scans clauses
from the top of the database downwards. If it started at the bottom and
worked up, or picked a clause at random, the effect would be different,
because the tests are not mutually exclusive. For example, what would be
the effect if Prolog started at the bottom and worked up? Clearly it
would be the same as if _our_ clauses were in reverse order with Prolog
scanning downwards, as it does.

By using "copy" and "delete" you can move clauses around. If you're
doubtful that their order matters, try re-ordering them and then calling
"write_size". This should convince you that when writing these command
predicates, you have to take clause order into account. There are some
languages whose programs would work just as well if you punched them on
cards and threw the cards at random into the computer. This is not true
of Prolog!

~6
Going back to the translation, you can see that I'm using the cut as a
kind of "if". Don't confuse this kind of "if" with :- .

The :- "if" is used only to connect a predicate you're defining with
the goals that make up its tail. It is not a goal, and it can't appear
in the tail of such a predicate.

The ! "if" is a special kind of goal that has the function of
interfacing between tests (in the logic language) and commands (in the
control language). It can only appear in the tail of a predicate.

~7
There is actually more to the cut than this. What I'm doing is
presenting a very simplified story; "taming" the cut by showing you a
particular pattern of usage. Understanding the cut in full is rather
complicated, and it's best to take it a bit at a time. If you did the
exercise at the end of Supplement 5, you will realise that this is so:
please bear with me until the end of this lesson.

~8
Now back to our example, which you should have already restored. In
general, how do you translate such a thing into English? Well, the test
(before the cut) is in Prolog's logic language, so you translate it as
described in Lesson 4.

The commands (after the cut) are in Prolog's control language. Translate
them as in Lesson 6.

Then join the two by an "if".

Finally, translate the head of the predicate as a command predicate ("to
do something...").

Repeat this for each clause.

~9
This will give you

      To write the size of N:
          If N is less than 3, then write "tiny".

      To write the size of N:
          If N is less than 10, then write "small".

      To write the size of N:
          If N is less than 100, then write "medium".

      To write the size of N:
          Otherwise, write "big".

~10
For a bit of practice: modify "write_size" so that it writes out
"enormous" if N is >= 100000, and "minuscule" if it's less than 0.00005.

~11
Here is another example, based on the "mars" knowledge base of Lesson 5,
the one with sweet prices. Type in these clauses for "describe":
      describe(S) :-
          costs( S, P ), P < 10, !,
          write( bargain ).

      describe(S) :-
          costs( S, P ), P < 18, !,
          write( average ).

      describe(S) :-
          costs( S, P ), P < 100, !,
          write(expensive).

      describe(S) :-
          write('only for millionaires').

~12
Now restore mars, and try calling "describe".

How does it translate to English? As for "write_size", the test is
translated as in the logic language. The commands (write) are translated
as in the control language. So the first clause becomes:

      To describe S:
          If S costs P, and P is less than 10, then
          write "bargain".

which means

      To describe S:
          If S costs less than 10p, then
          write "bargain".

~13
Incidentally, you may have tried "analyse" on these facts. Don't get
confused. Analyse can't distinguish between the logic and control
languages, and it treats everything as being in the logic language. So
it will not really help you translate anything with commands in.

~14
Now, please restore knowledge base "links". This contains facts similar
to those for the connections in Traveller's board, such as
      goes_to( a, b ).
      goes_to( b, c ).
      goes_to( c, d ).
Unlike the connections in Traveller, these are one-way links, so each
square has one successor: a has successor b, which has successor c.

Now, can you write a command predicate "look(X)". X is the name of a
square, such as a or b. "look" must write "goes to c" or "goes to d"
(e.g.) if X has a successor, and "dead end" if it doesn't.

So the message for a square with successor contains the name of the
successor.

~15
If in doubt, think up the English translation and work backwards. You
want the clauses to do the following:

      To look at X:
          if X has successor Y,
          then write "goes to" and Y.

      To look at X:
          (otherwise)
          write "dead end".

~16
Hint:

      look( X ) :-
          ... , !,
          write( '...' ), write ( ... ).

      look( X ) :-
          write( '...' ).

~17
The answer will be something like

      look( X ) :-
          goes_to( X, Y ), !,
          write( 'Goes to ' ), write ( Y ).

      look( X ) :-
          write( 'Dead end' ).

~18
"look" is itself a command predicate, so it can be called from the
command part of a command predicate. So: what trivial alteration to
"look" will enable it, having reported a "goes to", to then look at
_that_ square, and so on until it runs off the end of the sequence?

~19
Hint: use recursion in clause 1.

~20
Here's the answer, with an "nl" added to separate the messages.

      look( X ) :-
          goes_to( X, Y ), !,
          write( 'Goes to ' ), write ( Y ), nl,
          look( Y ).

      look( X ) :-
          write( 'Dead end' ).

~21
You can now, at last, get an idea of how Prolog can be used for data
navigation, both around abstract structures like those of the story and
depression examples in Supplement 4, and around the very concrete
structures describing Traveller. And since you already know how to add
and delete facts using "add" etc, you can even see how the
world-simulator in Traveller can run a lorry round the board, calling
your logic predicate "act" at each stage, examining its actions, and
updating the world accordingly.

~22
Before leaving this lesson, there are two final topics. Find or re-type
your version of "write_size". Alter it so that if the number N is two or
three, "write_size" writes out its name, otherwise writes an estimation
of the size as before.

~23
You do this in exactly the same way, but since you want to test for
exact equality with a number, you use = instead of < in these new
clauses.

      write_size( N ) :-
          N = 2, !,
          write( two ).

~24
There is a convenient abbreviated form of such clauses you can use in
such cases:

      write_size( 2 ) :-
          !,
          write( two ).

It would translate as:

      To write the size of 2:
          (no extra conditions)
          then write "two".

~25
In general, if you want to write a clause which takes some action, but
only if given one particular constant, you can write the constant in the
appropriate argument, instead of using an = test. This is what I did
with write_size(2), above.

~26
Now, before ending this lesson, the truth! There are not two languages
but one. Prolog recognises no distinction between the control language
and logic language (which is why "analyse" can't do so either). The
distinction is, though, a convenient way of thinking about programs.

Some predicates are obeyed for their side-effects: write, nl, adda,
look. Many are obeyed for their logical result: costs, >, loves. You can
mix the two indiscriminately and Prolog will never complain.

However, programs in which this is done are hard to understand, and it
is a good idea (at least if you're new to Prolog) to follow a few
standard patterns when you write code.

~27
During these lessons, I have introduced three such patterns.

1) Predicates whose tails contain only logical predicates. Obviously
such a predicate can itself never have side-effects (can not be a
command predicate), and is thus a logical predicate.

2) Predicates whose tails contain only command predicates. Obviously
such a predicate is itself a command predicate.

3) Because commands are no good if they can't adapt to their
environment, we need to write command predicates that can be guided by
logical tests. There are various ways to do this: but the
      command :- test1, !, action1.
      command :- test2, !, action2.
      ...
      command :- action_n.
formula is adequate for almost all purposes, and results in a nice
easy-to-read style

~28
In fact, _any_ predicate can be understood both as if in the logic
language and as if in the control language. In the logic language,
command predicates like "write" almost always turn out to have the
logical result "true". In the control language, logic predicates do not
have side-effects (they don't change their environment).

Both are related by Prolog's order of execution. Because it scans the
database from top to bottom, you can understand a sequence like
      command :- test1, !, action1.
      command :- test2, !, action2.
      command :- action_n.
as a sequence of if-then-elses.

Because it works through goals from left to right, you can understand
      a, b, c
as meaning "do a; then do b; then do c".

~29
Since there is no difference between logic and control language, the cut
can't be an interface between the two. That was a convenient fiction
that ties in with my recommended programming style. The command
predicates in this lesson would all work after a fashion if the cuts
were removed. However, they would produce spurious answers or output if
suitably provoked by backtracking, mentioned at the end of Lesson 5. If
you did the experiments with cut at the end of Supplement 5, you might
now like to carry them further. If not, you can ignore the final
sections of this lesson, and just follow the rule that, in writing a
command predicate, you should always put a cut between the test and the
action.

~30
OK - I assume you are willing to experiment further with cut. Please
restore SIZE (so you have the original) and ask
      write_size( 1 ), 1=2?

Now edit the definition so as to remove all the cuts, giving you clauses
like
      write_size(N) :-
          N < 3,
          write(tiny).

and ask again
      write_size( 1 ), 1=2?

~31
Why do you think this happens? Essentially, the cut inhibits Prolog's
clause scanning and says "once this clause has succeeded, never try an
alternative".

It is certainly undesirable that a command predicate should behave like
this; the point of such a predicate is that each condition has one and
only one correct action. If you don't backtrack into cut-less command
predicates, they will behave as expected. But if you do, they won't. So
for safety, I always use cuts. They also act as a good psychological cue
signalling where the test ends and the action begins.
