Reference Manual Visual Prolog 7.1
Reference Manual Visual Prolog 7.1
Eduardo Costa
PREFACE
This book started as a personal project. My intention was simply to
write a tutorial on Logic Programming for my son. The success of the book,
however, was immense, and I received many suggestions on how to improve
the text, or the code, and encouragement to keep on with the work. Mail
came from Saudi Arabia, China, Russia, Spain, France, Brazil, Canada, and
many other places. Although the space is short to list all people who wrote
me about the book, I want to acknowledge the collaboration and enthusiasm
of the following persons:
• Elena Efimova, who made many corrections on the text, and in the
historical background of Mathematics and Logics.
• Mark Safronov, who translated the book to Russian, and made many
corrections on the contents of the original. By the way, the Russian
translation has a much better layout than the English original.
• Stuart Cumming. I often give examples that show what one should
avoid from the point of software engineering. Stuart pointed out that
people could be misled by these examples; he suggests that, whenever
an implementation is not robust, I should emphasize its weak points.
Although I agree with Stuart, I thought it better to remove the negative
examples altogether.
• Yuri Ilyin, who helped me with his expertise; without him, this book
would not be written.
• Thomas Linder Puls and Elizabeth Safro, from PDC, for support and
encouragement.
Contents
I Savoir-faire 11
1 Introduction 13
1.1 Creating a project in VIP . . . . . . . . . . . . . . . . . . . . 13
1.1.1 Create a new GUI project: name . . . . . . . . . . . . 14
1.1.2 Compile and execute the program . . . . . . . . . . . . 15
1.2 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16
1.3 Notions of Logics: Ancient Greeks . . . . . . . . . . . . . . . . 16
2 Forms 17
2.1 Create a form: folder/name . . . . . . . . . . . . . . . . . . . . 17
2.2 Enable the task menu: e.g. File/New option . . . . . . . . . . . 17
2.3 In CodeExpert, add code to project tree item . . . . . . . . . . 18
2.4 Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22
2.5 Notions of Logics: Aristotle’s Syllogistic . . . . . . . . . . . . 22
2.5.1 Valid syllogisms . . . . . . . . . . . . . . . . . . . . . . 24
3 Mouse events 27
3.1 Add code to MouseDownListener . . . . . . . . . . . . . . . . 27
3.2 onPaint . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
3.3 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30
3.4 Notions of Logics: Boolean algebra . . . . . . . . . . . . . . . 30
3.5 Argument forms . . . . . . . . . . . . . . . . . . . . . . . . . . 31
4 Less Figures 33
4.1 Task menu . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33
4.2 Project Tree . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34
4.2.1 Code Expert . . . . . . . . . . . . . . . . . . . . . . . . 35
4.3 Create Project Item . . . . . . . . . . . . . . . . . . . . . . . . 36
4.4 Create a new class: folder/name . . . . . . . . . . . . . . . . . 39
4.5 Edit field contents . . . . . . . . . . . . . . . . . . . . . . . . . 39
4.6 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
1
2 CONTENTS
5 Horn Clauses 41
5.1 Functions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 41
5.2 Predicates . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
5.3 Solutions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 42
5.4 Multi-solutions . . . . . . . . . . . . . . . . . . . . . . . . . . 46
5.4.1 A program that uses multi solution predicates . . . . . 47
5.5 Logical junctors . . . . . . . . . . . . . . . . . . . . . . . . . . 48
5.6 Implication . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
5.7 Horn Clauses . . . . . . . . . . . . . . . . . . . . . . . . . . . 49
5.8 Declarations . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51
5.9 Drawing predicates . . . . . . . . . . . . . . . . . . . . . . . . 53
5.10 GDI Object . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
5.11 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56
5.12 Notions of Logics: Meaning of Horn clauses . . . . . . . . . . . 56
6 Console Applications 57
6.1 Cut . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57
6.2 Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 58
6.3 List schemes . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64
6.4 String operations . . . . . . . . . . . . . . . . . . . . . . . . . 68
—Parsing into tokens: fronttoken . . . . . . . . . . . . . . . . . . 68
—String concatenation: concatList . . . . . . . . . . . . . . . . . 68
—String concatenation: concat/2 and concat/3 . . . . . . . . . . 68
—Term to String conversion: toTerm . . . . . . . . . . . . . . . . 69
—Term to String conversion: hasdomain . . . . . . . . . . . . . . 69
—Term to String conversion: toString . . . . . . . . . . . . . . . . 69
—Term to String conversion: format . . . . . . . . . . . . . . . . . 70
6.4.1 Useful String Predicates . . . . . . . . . . . . . . . . . 71
—String processing: Useful predicates . . . . . . . . . . . . . . . . 71
6.5 Notions of Logics: Grammar for Predicates . . . . . . . . . . . 76
7 Grammars 79
7.1 Parsing grammar . . . . . . . . . . . . . . . . . . . . . . . . . 80
7.2 Generating grammar . . . . . . . . . . . . . . . . . . . . . . . 81
7.3 Why Prolog? . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
7.4 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84
7.5 Notions of Logics: Natural deduction . . . . . . . . . . . . . . 84
CONTENTS 3
8 Painting 87
8.1 onPainting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 88
8.2 Custom Control . . . . . . . . . . . . . . . . . . . . . . . . . . 91
8.3 Notions of Logics: Resolution principle . . . . . . . . . . . . . 93
9 Data types 95
9.1 Primitive data types . . . . . . . . . . . . . . . . . . . . . . . 95
9.2 Sets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97
9.3 Sets of numbers . . . . . . . . . . . . . . . . . . . . . . . . . . 97
9.4 Irrational Numbers . . . . . . . . . . . . . . . . . . . . . . . . 100
9.5 Real Numbers . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
9.6 Mathematics . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
9.7 Format . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
9.8 domains . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
9.8.1 Lists . . . . . . . . . . . . . . . . . . . . . . . . . . . . 104
9.8.2 Functors . . . . . . . . . . . . . . . . . . . . . . . . . . 104
9.9 Notions of Logics: Horn clauses . . . . . . . . . . . . . . . . . 108
11 Facts 129
11.1 Class file . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132
11.1.1 Reading and writing a string . . . . . . . . . . . . . . . 132
11.2 Constants . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 133
14 L-Systems 151
14.1 Class draw . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 152
14.2 Examples . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 154
4 CONTENTS
15 Games 155
15.1 Object Facts . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162
16 Animation 163
16.1 dopaint . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164
16.2 Handling the timer . . . . . . . . . . . . . . . . . . . . . . . . 166
16.3 How the program works . . . . . . . . . . . . . . . . . . . . . 166
18 Printing 171
20 Bugs 195
20.1 Type error . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 196
20.2 Non-procedure inside a procedure . . . . . . . . . . . . . . . . 197
20.3 Non-determ condition . . . . . . . . . . . . . . . . . . . . . . . 198
20.4 Impossible to determine the type . . . . . . . . . . . . . . . . 198
20.5 Flow pattern . . . . . . . . . . . . . . . . . . . . . . . . . . . 200
3.1 PaintResponder . . . . . . . . . . . . . . . . . . . . . . . . . . 29
3.2 Paint Responder . . . . . . . . . . . . . . . . . . . . . . . . . 30
7
8 LIST OF FIGURES
Savoir-faire
11
Chapter 1
Introduction
13
14 Introduction
The system of windows and dialogs that you will create to communicate with
potential users of your programs is called a Graphical User Interface, or GUI
for short.
This step is quite easy. Choose the option Project/New from the task menu,
as shown in figure 1.1. Then, fill in the Project Settings dialog as in figure 1.2.
Press the button OK and you will get the project tree (figure 1.3).
1.2 Examples
The example in this chapter shows how to run an application, and is in di-
rectory plot1. In the next chapter, you will add functionality to the plot1
project, which means that will make the corresponding application do some-
thing useful.
Forms
In this chapter, you will add a form to the empty project that you have
created in chapter 1. A form is a window pane where you can add components
like buttons, that trigger actions, edit fields, that you can use to input text,
and canvas, where you can draw things.
To create a form, choose the option File/New in New Package from the task
menu, as in figure 2.1. Select Form from the left pane and fill in the Create
Project Item dialog as in figure 2.2. The new form name is query. Since you
have chosen the option New in New Package, Visual Prolog will create the
query form in a package named after the form. After you press the Create
button of the Create Project Item dialog, the IDE shows you a prototype
of the new form, as in figure 2.3. You may want to change the size of the
window rectangle, making it slightly larger than the prototype. In order to
do this, click and hold the mouse at the lower right corner and drag it, as
you do when you resize a normal window.
17
18 Forms
Finally, press the button Add (refer to figure 2.7), and double click on the
branch id_file_new->onFileNew. This will open a text editor, with the
following snippet:
clauses
onFileNew(_Source, _MenuTag).
clauses
onFileNew(W, _MenuTag) :- X= query::new(W), X:show().
Build the program again, choosing the option Build/Build from the task
menu, as in figure 1.4. Execute the program, and you will see that, whenever
you choose the File/New option, a new form is created.
2.4 Example
The example in this chapter shows how one creates a form by choosing the
option File/New, then filling in the Create Project Item dialog.
Let an asterisk ∗ represent any of the four quantifiers (‘a’, ‘e’, ‘i’, ‘o’). In
this case, the syllogisms are classified in three figures, to wit,
P ∗M M ∗P P ∗M
M ∗S M ∗S S∗M
P ∗S P ∗S P ∗S
n
| × n ×{zn × . . . n} = np
p times
In the present case, one has to choose three quantifiers from the set {a, e, i, o}.
Therefore, there are 43 = 64 possibilities for each figure; since we have three
figures, the number of possibilities grows to 192.
on the Medieval Latin pronunciation, where a word like Cæsare was spelled
Cesare, and pronounced accordingly. Anyway, here is the complete list of
the valid moods:
∀P is M
∃M is S
∃S is P
Where the symbol ∀ means All, and ∃ means Some. Venn claims that the
universal premise should be diagrammed before diagramming a particular
premise. Therefore, at the left hand side of figure 2.10, we have shaded
the area of P that is not on M , as required by the premise ∀P is M . The
26 Forms
next step is to place the ∃ symbol in the area corresponding to the premise
∃M is S. Figure 2.10 shows that the ∃ symbol could be in the SM P̃ area or
in the SP M area. Since we do not know exactly which area it is in, we put
the ∃ symbol on the line. The conclusion is that Some S is P.
Alice Liddell’ s father wanted to give her a very good education. When
he noticed that there did not exist any good Greek-English dictionaries,
he and his friend Scott sat and prepared a very good one, that almost all
students of Greek use with pleasure and profit. He also hired the great
writer [Lewis Carroll] as a teacher of Logics for his two daughters. In order
to make his classes interesting, [Lewis Carroll] wrote a few books for children:
Alice in the Wonderland, Through the Looking Glass, and the Game of Logics.
The example below is from the Game of Logics.
X
+
Y
M
+
The following solution to the above syllogism has been kindly supplied to
me by Mr. Venn himself: The Minor Premiss that some of the constituents
in my’ must be saved — mark it with a cross. The Major declares that all
xm must be destroyed; erase it. Then as some my’ is to be saved, it must
clearly be my’x’. That is, there must exist my’x’; or, eliminating m, y’x’. In
common phraseology, Some not-gamblers are not philosophers.
Chapter 3
Mouse events
In the first two chapters of this document, you learned how to build an appli-
cation with a form, that pops up whenever you choose the option File/New,
from the application menu.
27
28 Mouse events
clauses
onMouseDown(S, Point, _ShiftControlAlt, _Button) :-
W= S:getVPIWindow(), Point= pnt(X, Y),
vpi::drawText(W, X, Y, "Hello, World!").
3.2 onPaint
You have used the event handler onMouseDown/4 to paint something on the
form. There are programmers that do not think that this is a good idea. In
fact, there are languages that make this approach very difficult. Java is one
of these languages, a very popular one, by the way. In Java, one should do
all paintings and drawings inside the following method:
Of course, one can get around this restriction, even in Java. Anyway, let us
learn how to put all drawing activities inside the event handler onPaint/3.
3. Enable the task menu option File/New, and add the snippet
clauses
onFileNew(S, _MenuTag) :-
Q= query::new(S), Q:show().
to ProjTree/Taskwindow.win/Menu/TaskMenu/id_file/id_file_new.
class facts
mousePoint:pnt := pnt(-1, -1).
predicates
onMouseDown : drawWindow::mouseDownListener.
clauses
onMouseDown(_S, Point, _ShiftControlAlt, _Button) :-
mousePoint := Point,
Point= pnt(X, Y),
R= rct(X-8, Y-8, X+60, Y+8),
invalidate(R).
predicates
onPaint : drawWindow::paintResponder.
clauses
onPaint(_Source, _Rectangle, GDIObject) :-
if pnt(X, Y)= mousePoint, X>0
then
mousePoint := pnt(-1, -1),
GDIObject:drawText(pnt(X, Y), "Hello, World!", -1)
else succeed() end if.
3.3 Examples
This chapter shows how to handle a mouse event, such as a left click.
Truth tables. In order to establish the truth value of a formula, one often
resorts to truth tables. Let us learn about truth tables through examples.
Let us consider the well formed formula p ∧ q. If 1 represents the constant
true, and 0 represents false, the truth table of the negation(¬p), of logical
3.5 Argument forms 31
p q ¬p p∧q p∨q
0 0 1 0 0
0 1 1 0 1
1 0 0 0 1
1 1 0 1 1
Informally, the formula p → q means that
p q p→q
0 0 1
0 1 1
1 0 0
1 1 1
It is possible to prove that P → Q is equivalent to the formula ¬P ∨ Q; let
us verify this assertion.
p q ¬p ¬p ∨ q p→q
0 0 1 1 1
0 1 1 1 1
1 0 0 0 0
1 1 0 1 1
¬p
`q
where ` represents the logical assertion. Roughly speaking, we are told that
either p or q is true; then we are told that p is not true; so we infer that it
has to be q that is true.
32 Mouse events
q←p
p
`q
You have learned that p → q means if p then q, i.e., p implies q. Therefore,
q ← p means that q is implied by p, or q if p. In fewer words, the first line
of the argument says that q is true, if p is true; the second line says that
p is true; then man or machine can infer that q is true. Let us consider a
concrete example.
hearsSocrates(plato)
` happy(plato)
Another valid argument is called modus tollens:
p→q
¬q
` ¬p
Chapter 4
Less Figures
Before going ahead, let us find out a way to describe our projects without
having to resort to figures.
General
Project Name: factorial
UI Strategy: Object-oriented GUI(pfc/gui)
Target Type: Exe
Base Directory: C:\vispro
you should enter the Project Settings dialog (by choosing the Project/New
option of the VDE task menu) and fill in the General form as shown above.
Do it mutatis mutandis, since your computer may not have space available
on drive C:, or you may want to put your programs in a directory different
from C:\vispro.
33
34 Less Figures
If you left click on a folder, it will open and present its contents. If you click
on an item with the right button of the mouse, a floating menu opens, as
shown in the figure. If I want you to go to the code expert, and add a snippet
to the TaskWindow.win, I will say:
To carry out this instruction, go to the project tree and do the following:
Edit
Attribute
Delete
Code Expert
File Properties...
clauses
onFileNew(W, _MenuTag) :- S= query::new(W), S:show().
When you create a form, the IDE shows shows the Form Window dialog
(see figure 4.2). You can take the opportunity to resize the window and add
an edit field (handle: edit_ctl) and a button (handle: factorial_ctl), as
shown in the figure. You can also reach the Form Window dialog by left
clicking on the form name in the project tree.
menu entry, as shown in the figure below, where I have pressed the button
(icon) labelled New Item and created the entry Drawing Tools by filling in
the dialog. As you can see from the figure, the new option is enabled from
the start. The symbol & in the &File entry makes F a shortcut key.
Let us start a new project from scratch, to see what we can do with fewer
figures and check whether we have a terse description of a VIP project.
clauses
onFileNew(W, _MenuTag) :- S= query::new(W), S:show().
to TaskWindow.win/Menu/TaskMenu/id_file->id_file_new->onFileNew.
Build the application to insert the new classes into the project.
4.4 Create a new class: folder/name 39
To create a new class and put it inside the package formcontainer, select the
folder formcontainer in the Project Tree, and choose the option File/New in
Existing Package from the IDE task menu. Select the option Class from the
Project Item dialog, and fill in the class Name field with fn. Make sure to
untick the Create Objects box.
When you press the Create button, VIP will display the files fn.pro
and fn.cl, that contain a prototype of the fn class. It is our job to add
functionality to these files, by replacing the contents of listing of page 40
for them. Then, build the application once more, to make sure that Visual
Prolog inserts the fn-class into the project.
clauses
onFactorialClick(_S) = button::defaultAction() :-
fn::calculate(edit_ctl:getText()).
4.6 Examples
In this chapter, you have learned how to cre-
ate a form with a button(factorial_ctl, and
an edit field (edit_ctl); you have also learned
how to access the contents of the edit field.
Below is the fn-class, that implements the fac-
torial function.
%File: fn.cl
class fn
predicates
classInfo : core::classInfo.
calculate:(string) procedure.
end class fn
% File fn.pro
implement fn
open core
class predicates
fact:(integer, integer)
procedure (i,o). Figure 4.3: The fn class
clauses
classInfo("forms/fn", "1.0").
fact(0, 1) :- !.
fact(N, N*F) :- fact(N-1, F).
calculate(X) :- N= toterm(X),
fact(N, F), stdio::write(F, "\n").
end implement fn
Horn Clauses
5.1 Functions
I am sure you know what a function is. It is possible that you do not know
the mathematical definition of function, but you have a feeling for functions,
acquired by the use of calculators and computer programs or by attending
one of those Pre-Algebra courses. A function has a functor, that is the name
of the function, and arguments. E.g.: sin(X) is a function. Another function
is mod(X, Y ), that gives the remainder of a division. When you want to use
a function, you substitute constant values for the variables, or arguments.
For instance, if you want to find the remainder of 13 divided by 2, you can
type mod(13, 2) into your calculator (if it has this function, of course). If
you want the sinus of π/3, you can type sin(3.1416/3).
One could say that functions are maps between possible values for the
argument and a value in the set of calculation results. Domain is the set of
possible values for the argument. Image is the set of calculation results. In
the case of the sinus function, the elements of the domain is the set of real
numbers. There is an important thing to remember. Mathematicians insist
that a function must produce only one value for a given argument. Then, if a
calculation
√ produces more than one value, it is not a function. For instance,
4 can be 2 or −2. Then, the square root of a number is not a function;
however, you can force it into the straightjack of the function definition, by
saying that only values greater or equal to zero are part of the image.
What about functions of many arguments? For example, the function
max(X, Y ) takes two arguments, and returns the greatest one. In this case,
you can consider that it has only one argument, that is a tuple of values.
Thus, the argument of max(5, 2) is the pair (5, 2). Mathematicians say that
the domain of such a function is the Cartesian product R × R.
41
42 Horn Clauses
There are functions whose functor is placed between its arguments. This
is the case of the arithmetic operations, where one often writes 5 + 7 instead
of +(5, 7).
5.2 Predicates
Predicates are functions whose domain is the set {verum, falsum}, or, if
you do not like the Latin names used by logicians, you can always opt for
the English equivalent: {true, false}. There are a few predicates that are
well known to any person who tried his hand on programming, or even by a
student who is taking Pre-Algebra. Here they are:
A predicate with more than one argument shows that there exists a relation-
ship between its arguments. In the case of X = Y , the relationship is the
equality between X and Y. It would be interesting to have a programming
language, with predicates stating attributes and relationships other than the
few offered by calculators and main stream computer languages. For in-
stance, many people, during their lifetime, have felt a compelling need for
the prediction of one of the predicates listed in figure 5.1, specially the third
one. Prolog is a computer language that was invented to fulfill such a need.
5.3 Solutions
Suppose that you have a predicate city(Name, Point) that gives the coor-
dinates of a city on a map. The city/2 predicate has the domain 1
This predicate checks whether the given position of a given city is correct,
when one is not sure about it. Here are some queries that one could pose to
the city/2 predicate.
There is no question that you could find a use for such a predicate. However,
a predicate that returns the coordinates of a city, given the name, would be
even more useful.
In this new kind of predicate, symbols that start with uppercase are called
variables. Examples of variables: X, Y, Wh, Who, B, A, Xs, Temperature,
Humidity, Rate. Therefore, if you want to know whether a symbol is a
variable, check its first letter. If it is uppercase or even the underscore sign(_),
you are dealing with a variable.
When you use a variable, as P in the query city("Salt Lake", P), you
want to know what one must substitute for P in order to make the predicate
city("Salt Lake", P) true. The answer is P= pnt(30, 40). Sophocles
said that your hand should not be faster than your mind, nor fail to keep
pace with it. Therefore, let us implement the city/2 predicate.
Project Settings. Enter the Project Settings dialog by choosing the option
Project/New from the VDE task menu, and fill it in.
General
Project Name: mapDataBase
UI Strategy: Object-oriented (pfc/GUI)
Target Type: Exe
Base Directory: C:\vispro
Sub-Directory: mapDataBase\
Create Project Item: Form. Select the root node of the Project Tree.
Choose the option File/New in New Package from the task menu. In
the Create Project Item dialog, select the option Form. Fill in the
dialog:
Name: map
Add the following buttons to the new form: Logan, Salt Lake, Provo.
Window Edit. Resize the new form rectangle.The form window must have
a sufficient size to show our “map”. Leave a generous empty space in
the center of the form.
clauses
onFileNew(S, _MenuTag) :-
X= map::new(S), X:show().
% File: draw.cl
class draw
open core, vpiDomains
predicates
classInfo : core::classInfo.
drawThem:(windowHandle, string) procedure.
end class draw
clauses
onLoganClick(S) = button::defaultAction() :-
Parent= S:getParent(),
P= Parent:getVPIWindow(),
draw::drawThem(P, "Logan").
to the ClickResponder.
Repeat the operation for "Salt Lake" and "Provo". Don’t forget to change
the names of the buttons to logan_ctl and provo_ctl, and saltlake_ctl
respectively; change also the city name "Logan" in "drawThem" to "Provo"
or "Salt Lake". Build the project and execute the program. If you do not
remember how to build and execute the program, take a look at section 1.1.2.
In the new application, choose the option File/New. A new form will pop
up. Whenever you click on a button, the program draws the corresponding
city.
46 Horn Clauses
% File:draw.pro
implement draw
open core, vpiDomains, vpi
constants
className = "draw".
classVersion = "".
class facts
city:(string, pnt).
clauses
classInfo(className, classVersion).
5.4 Multi-solutions
In the last section, you saw predicates that are used for attributing solutions
to their variable, not for checking whether a relationship is true or false.
In the example, the predicate city/2 is used to retrieve only one solution.
However, there are situations that call for more than a single solution. Let
conn/2 be a predicate that establishes a connection between two cities.
conn(pnt(30, 40), pnt(100, 120)).
conn(pnt(100, 120), pnt(100, 200)).
conn(pnt(30, 40), pnt(200, 100)).
..
.
5.4 Multi-solutions 47
You can use it to retrieve all connections between cities, as the example
shows:
Project Settings. Enter the Project Settings dialog by choosing the option
Project/New from the IDE task menu, and fill it in.
Create Project Item: Package. Select the drawMap node of the project
tree. Choose the option File/New in New Package from the task menu.
In the Create Project Item dialog, select Package and fill in the form:
Name: plotter
Parent Directory:
Create Project Item: Form. Select the plotter node of the project tree.
Choose the option File/New in Existing Package from the task menu.
In the Create Project Item dialog, select the option Form, and fill it.
Name: map
Package: plotter.pack (plotter\)
clauses
onFileNew(S, _MenuTag) :- F= map::new(S), F:show().
to Menu/TaskMenu/id_file/id_file_new/onFileNew
Create class. Create a draw class inside the package plotter, as explained
in section 4.4. Untick the box Create Objects. Put class code in files
draw.cl and draw.pro, as shown in figure 5.4. Build the application.
clauses
onPaint(S, _Rectangle, _GDIObject) :-
W= S:getVPIWindow(),
draw::drawThem(W).
If you build and run this program, you should obtain a window with a map,
such as the one in figure 5.5, whenever you press File/New.
X>2, X<4
5.6 Implication
An implication is a junctor represented by the symbol :-, that means if.
Therefore,
means that you drawThem on Win if you draw connections on Win and
drawCities on Win.
One predicate Horn clauses are called facts. In the example, the facts state
a relationship between a city and its coordinates. The domain of these pred-
icates is a set of pairs, each pair containing the name of the city and its
coordinates. A Horn clause can also take the form
drawThem(Win) :- connections(Win),
drawCities(Win).
is an instance of Horn clause. In a Horn clause, the part that comes before
the :- sign is called the head. The part that comes after the sign :- is the
tail. In the example, the head is drawThem(W), and the tail is
connections(Win), drawCities(Win)
A set of Horn clauses with the same head defines a predicate. For instance,
the four city Horn clauses define the city/2 predicate, and
defines drawThem/1.
50 Horn Clauses
% File: draw.cl
class draw
open core, vpiDomains
predicates
drawThem:(windowHandle) procedure.
end class draw
% File: draw.pro
implement draw
open core, vpiDomains, vpi
class facts
city:(string Name, pnt Position).
conn:(pnt, pnt).
class predicates
connections:( windowHandle).
drawCities:(windowHandle).
clauses
city("Salt Lake", pnt(30, 40)).
city("Logan", pnt(100, 120)).
city("Provo", pnt(100, 160)).
city("Yellowstone", pnt(200, 100)).
5.8 Declarations
The definition of a predicate is not complete without type/flow pattern dec-
laration. Here is the type and flow (or mode) declaration of drawThem/1.
predicates
drawThem:(windowHandle) procedure (i).
class predicates
connections:( windowHandle).
drawCities:(windowHandle).
class facts
city:(string Name, pnt Position).
conn:(pnt, pnt).
52 Horn Clauses
Later on, you will see that facts can be asserted or retracted from the data
base. The arguments of conn/2 are a pair of pnt values. The type pnt is
defined in class vpiDomains. To use it in class draw, I have two options. I
can spell out the class to which pnt belongs
class facts
conn:(vpiDomains::pnt, vpi::Domains::pnt).
or else, I can open that class inside draw. I have chosen the second option:
open core, vpiDomains, vpi
Determinism declarations
To declare that a predicate has a single solution, or many solutions, one uses
the following keywords:
procedure. This kind of predicate always succeeds, and has a single solu-
tion. The predicates connections/1 and drawCities/1 defined in figure 5.4
are procedures, and could have been declared thus:
class predicates
connections:( windowHandle) procedure (i).
drawCities:(windowHandle) procedure (i).
The nondeterm fact conn(P1, P2) provides two points, that are used by the
procedure drawLine(Win, P1, P2) to draw a straight line. Then, Prolog
tries to satisfy the predicate fail, that always fails, as its name shows.
Therefore, Prolog backtracks, and tries another solution, until it runs out
of options; then it tries the second clause of connection/1, which always
succeeds.
clauses
onPaint(S, _Rectangle, _GDIObject) :-
W= S:getVPIWindow(), draw::drawThem(W).
In the draw class, the handle W is passed around. For instance, in the clause
that draws an ellipse on the window W. The ellipse is inscribed inside the
rectangle rct(X1, Y1, X2, Y2), where X1, Y1 are the coordinates of the
upper left corner, and X2, Y2 are the coordinates of the lower right corner.
clauses
onFileNew(S, _MenuTag) :-
F= map::new(S), F:show().
to Menu/TaskMenu/id_file/id_file_new/onFileNew
Add clauses
onPaint(_S, _Rectangle, GDIObject) :-
draw::drawThem(GDIObject).
to the PaintResponder of the map.frm.
5.10 GDI Object 55
%File: draw.cl
class draw
open core
predicates
drawThem:(windowGDI).
end class draw
% File: draw.pro
implement draw
open core, vpiDomains, vpi
class facts
city:(string Name, pnt Position).
conn:(pnt, pnt).
class predicates
connections:( windowGDI).
drawCities:(windowGDI).
clauses
city("Salt Lake", pnt(30, 40)).
city("Logan", pnt(100, 120)).
city("Provo", pnt(100, 160)).
5.11 Examples
There are three examples in this chapter: mapDataBase draws the position
of the three main cities of Utah, when one presses the appropriate button;
drawMap draws a coarse road map of Utah; drawMapObj is the GDI version
of drawMap.
H : −T1 , T2 , T3 . . .
This last interpretation sheds light on the meaning of the neck symbol, where
the semicolon stands for the connective or, and the minus sign stands for the
logical negation. Then,
H : −T
means: H is true, or not T. To finish our study of the Prolog connectives, let
us remind you that the comma stands for and, the semicolon stands for or,
and -> stands for if. . . then.
Chapter 6
Console Applications
Since people like graphic applications, we started with them. However, they
add details that sway your attention from what really matters. Thus, let us
see a few basic programming schemes without resorting to a graphic interface.
6.1 Cut
What should you do if you don’t want the system either to backtrack or try
another clause after finding a solution? In this case, you insert an exclamation
mark at a given point in the tail of the Horn clause. After finding the
exclamation mark, the system interrupts its search for new solutions. The
exclamation mark is called cut. Let us illustrate the use of cuts with an
algorithm that is very popular among Prolog programmers. Let us assume
that you want to write a predicate that finds the factorial of a number.
Mathematicians define factorial as:
factorial(0) → 1
factorial(n) → n × factorial(n − 1)
Using Horn clauses, this definition becomes:
fact(N, 1) :- N<1, !.
fact(N, N*F1) :- fact(N-1, F1).
The cut prevents Prolog from trying the second clause for N = 0. In other
words, if you pose the query
fact(0, F)?
the program succeeds in using the first clause for N = 0, and returns F = 1.
Without the cut, it will assume that the second clause of the definition also
57
58 Console Applications
provides a solution. Then, it will try to use it, and will go astray. The
cut ensures that factorial is a function and, as such, maps each value in
the domain to one and only one value in the image set. To implement the
factorial function, follow the following directive:
Create a New Project. Choose the option Project/New and fill the Project
Settings dialog thus:
General
Project Name: facfun
UI Strategy: console
Pay attention to the fact that we will use the console strategy, not GUI.
Build. Choose the option Build/Build from the task menu to add a pro-
totype of class facfun into the project tree. Edit facfun.pro as shown
below. Build the project again, and execute it using Run in Window.
Write a number to the prompt, and you will get its factorial. NB: To
test a console program, use Run in Window, not Execute.
%File: main.pro
implement main
class predicates
fact:(integer N, integer Res) procedure (i,o).
clauses
classinfo("facfun", "1.0").
fact(N, 1) :- N<1, !.
fact(N, N*F) :- fact(N-1, F).
run():- console::init(),
fact(stdio::read(), F), stdio::write(F), stdio::nl.
end implement main
goal
mainExe::run(main::run).
6.2 Lists
There is a book about Lisp that says: A list is a an ordered sequence of
elements, where ordered means that the order matters. In Prolog, a list is
6.2 Lists 59
[X|Xs]
with the list [3.4, 5.6, 2.3] you will get X = 3.4 and Xs = [5.6, 2.3], i.e., X
matches the head of the list and Xs matches the tail. Of course, you can use
another pair of variables instead of X and Xs in the pattern [X|Xs]. Thus,
[A|B], [X|L], [Head|T ail], [F irst|Rest] and [P |Q] are equivalent patterns.
More examples of matches for the pattern [X|Xs]:
Pattern List X Xs
[X|Xs] [3.4, 5.6, 2.3] X = 3.4 Xs = [5.6, 2.3]
[X|Xs] [5.6, 2.3] X = 5.6 Xs=[2.3]
[X|Xs] [2.3] X = 2.3 Xs = []
[X|Xs] [] Does not match.
As you can see, the pattern [X|Xs] only matches lists with at least one
element. Here is a pattern that only matches lists with at least two elements:
Let avg be a small project that calculates the length of a list of real numbers.
As in the case of factorial, make sure that avg is a console application.
General
Project Name: avg
UI Strategy: console
Build the project to insert the avg class into the project tree. Then, edit
avg.pro as shown below. Execute using Run in Window, as before.
/* File: main.pro */
implement main
open core, console
domains
rList= real*.
class predicates
len:(rList, real) procedure (i, o).
clauses
classInfo("avg", "1.0").
len([], 0) :- !.
len([_X|Xs], 1.0+S) :- len(Xs, S).
Let us examine the concepts behind this short program. The first thing that
you have done is to create a list domain:
domains
rList= real*.
Then, you define a predicate len/2, that inputs a list from the first argument,
and outputs its length to the second argument. Finally, you define a run()
predicate to test your program:
run():- console::init(), % Initialize the console.
hasdomain(rList, List), % Create a rList variable.
List= read(), % Read List from console.
len(List, A), % Find the length of List.
write(A), nl. % Output the length.
6.2 Lists 61
The domain declaration is handy if you are dealing with complex algebraic
data types. However, it is not required for such a simple structure as a list of
reals. Below you will find the same program without the domain declaration.
/* File: main.pro */
implement main
open core, console
class predicates
len:(real*, real) procedure (i, o).
clauses
classInfo("avg", "1.0").
len([], 0) :- !.
len([_X|Xs], 1.0+S) :- len(Xs, S).
This program has a drawback: One can use len/2 only for calculating the
length of a list of reals. It would be nice if len/2 could take any kind of list.
This should be possible if one substitutes a type variable for real*. However,
before checking whether this scheme really works, it is necessary to devise
a way of informing L= read() which will be the eventual type of the input
list, and feed the information to len/2. Fortunately there is a predicate that
creates a variable for any given type. You will discover below how to use it.
implement main /* in file main.pro */
open core, console
class predicates
len:(Element*, real) procedure (i, o).
clauses
classInfo("avg", "1.0").
len([], 0) :- !.
len([_X|Xs], 1.0+S) :- len(Xs, S).
run():- console::init(),
hasdomain(real_list, L), L= read(),
len(L, A), write(A), nl.
end implement main
goal mainExe::run(main::run).
62 Console Applications
The next step in our scheme of things is to insert sum/2 clauses to main.pro.
sum([], 0) :- !.
sum([X|Xs], S+X) :- sum(Xs, S).
Let us examine what happens if one calls sum([3.4, 5.6, 2.3], S).
1. sum([3.4, 5.6, 2.3], S), matches
sum([X|Xs], S+X) :- sum(Xs, S), with X=3.4, Xs=[5.6, 2.3],
yielding sum([3.4, 5.6, 2.3], S[5.6,2.3] + 3.4) :- sum([5.6, 2.3], S[5.6,2.3] )
3. add([2.3], 0.0+3.4+5.6, S)
matches add([X|Xs], A, S) :- add(Xs, X+A, S)
yielding add([ ], 0+3.4+5.6+2.3, S)
that matches add([], A, A) yielding S = 11.3.
You could use add/3 to calculate the average value of a list of numbers.
6.2 Lists 63
len([], 0) :- !.
len([_X|Xs], 1.0+S) :- len(Xs, S).
add([], A, A) :- !.
add([X|Xs], A, S) :- add(Xs, X+A, S).
The above program goes through the list twice, first to calculate the sum, and
second to calculate the length. Using two accumulators, you can calculate
the length and the sum of the list together.
%File: main.pro
implement main
open core, console
class predicates
avg:(real*, real, real, real) procedure (i, i, i, o).
clauses
classInfo("avg", "1.0").
avg([], S, L, S/L).
avg([X|Xs], S, L, A) :-
avg( Xs,
X+S, % add X to the sum acc
L+1.0, % increments the length acc
A).
run():- console::init(),
List= read(),
avg(List, 0, 0, A),
write(A), nl.
end implement main
goal
mainExe::run(main::run).
The above listing shows a program to calculate the average value of a list of
real numbers. One makes use of two accumulators, one for the sum, and the
other for the number of elements.
64 Console Applications
Reduction
The scheme used to calculate the sum of the elements of a list is called
reduction, as it reduces the dimension of the input. In fact, lists can be
thought of as one-dimensional data, and the sum is a zero-dimensional datum.
There are two ways of performing a reduction: recursive and iterative.
%Recursive reduction
class predicates
sum:(real*, real) procedure (i, o).
clauses
sum([], 0) :- !.
sum([X|Xs], S+X) :- sum(Xs, S).
The term iterative comes from the Latin word ITERUM, that means again.
You can also imagine that it comes from ITER/ITINERIS= way, road. An
iterative program goes through the code again, and again.
%Iterative reduction
red([], A, A) :- !.
red([X|Xs], A, S) :- red(Xs, X+A, S).
sumation(Xs, S) :- red(Xs, 0.0, S).
%Iterative reduction
red([], A, A) :- !.
red([X|Xs], A, S) :- red(Xs, X*A, S).
product(Xs, S) :- red(Xs, 0.0, S).
The two pieces of code are identical, except for a single detail: one of them
has X+A in the second argument of the iterative call of the predicate red/3,
while the other has X*A. [Wadler & Bird] and [John Hughes] suggests that
one capture this kind of similarity in higher order predicates or functions.
Before them, when he received the Turing award, [John Backus] made the
6.3 List schemes 65
same proposition. Let us see that one can use this prestigious method of
programming in Visual Prolog.
In the program of figure 6.1, I have declared a domain that is a two-place
function. In the section containing the class predicates, I have defined a few
functions belonging to this domain. Finally, I defined a red/4 predicate that
capture the structure of the reduction operation.
implement main
open core
domains
pp2 = (real Argument1, real Argument2) -> real ReturnType.
clauses
classInfo("main", "hi_order").
class predicates
sum:pp2.
prod:pp2.
red(_P, [], A, A) :- !.
red(P, [X|Xs], A, Ans) :- red(P, Xs, P(X, A), Ans).
run():- console::init(),
red(prod, [1,2,3,4,5], 1, X),
stdio::write(X), stdio::nl,
succeed().
end implement main
goal
mainExe::run(main::run).
ZIP
There is a device used for continuous clothing closure invented by the Cana-
dian electrical engineer Gideon Sundback. The device drives one crazy when
it does not work, and is the cause of so many accidents with young boys,
that doctors and nurses need special training to deal with it.
class predicates
dotproduct:(real*, real*, real) procedure (i, i, o).
clauses
dotproduct([], _, 0) :- !.
dotproduct(_, [], 0) :- !.
dotproduct([X|Xs], [Y|Ys], X*Y+R) :- dotproduct(Xs, Ys, R).
The example shows a zipping scheme followed by a reduction step, in order
to perform the dot product of two lists. In the expression X*Y+R, one uses
the multiplication to close one clasp of the zipper.
We can capture the zipping scheme in a higher-order predicate, the same
way we have captured the reducing scheme. In fact, we can close each clasp
of the zipper with the help of the same type of functions that we have used
to perform a reduction step. In figure 6.2, you can find a (not very efficient)
definition of zip in Prolog. I added this listing because more than one person
wrote me requesting for an in-depth treatment of this important topic; I be-
lieve that I cannot provide an in-depth treatment, since this would go beyond
the scope of this book; after all, my goal is to avoid subjects that present
steep learning curves to beginners; however, the examples in figures 6.1,
and 6.2 should be enough for showing how to write higher-order predicates
in Visual Prolog. People who want to explore the possibilities of higher-order
Logics, and functions may refer themselves to books like [Wadler & Bird], or
in papers like [John Hughes].
The predicate zip has four arguments. The first argument is a two-
place function; in listing 6.2, I provide two of such functions, but you can
define others. The second and third arguments contain the lists to be zipped
together. The fourth argument contains the output of the operation. The
definition of zip/4 is simple:
zip(_P, [], _, []) :- !.
zip(_P, _, [], []) :- !.
zip(P, [X|Xs], [Y|Ys], [Z|As]) :- Z= P(X, Y),
zip(P, Xs, Ys, As).
If either input list becomes empty, the first or the second clause stop the
recursion, producing the empty list as output; otherwise, the third clause
6.3 List schemes 67
clasps the heads of the two input lists, and proceeds to the rest of the input
lists. I believe that this should be enough for a starting point.
implement main
open core
domains
pp2 = (real Argument1, real Argument2) -> real ReturnType.
clauses
classInfo("main", "zip").
class predicates
sum:pp2.
prod:pp2.
red:(pp2, real*, real, real) procedure (i, i, i, o).
zip:(pp2, real*, real*, real*) procedure (i, i, i, o).
dotprod:(real*, real*, real) procedure (i, i, o).
clauses
sum(X, Y)= Z :- Z=X+Y.
prod(X, Y)= Z :- Z=X*Y.
red(_P, [], A, A) :- !.
red(P, [X|Xs], A, Ans) :- red(P, Xs, P(X, A), Ans).
run():- console::init(),
V1= [1,2,3,4,5], V2= [2,2,2,2,2],
dotprod( V1, V2, X),
stdio::write(X), stdio::nl.
end implement main
goal
mainExe::run(main::run).
tokenize(S, [T|Ts]) :-
frontToken(S, T, R), !, tokenize(R, Ts).
tokenize(_, []).
run():- console::init(),
L= ["it ", "was ", "in ", "the ",
"bleak ", "December!"],
S= concatList(L), UCase= toUpperCase(S),
RR= string::concat("case: ", Ucase),
R1= string::concat("It is ", "upper ", RR),
stdio::write(R1), stdio::nl, tokenize(R1, Us),
stdio::write(Us), stdio::nl.
end implement main
goal
mainExe::run(main::run).
Try to understand how frontToken works, because it is a very useful pred-
icate. One uses it to break a string into tokens, in a process called lexical
analysis. For instance, if you execute
frontToken("break December", T, R)
you will get T="break", and R=" December".
One often has to transform a string representation of a term into an oper-
ational representation. In this case, s/he can use the function toTerm. In the
example below, Sx= stdio::readLine() reads a string from the command
line into Sx; then toTerm transforms the string representation into a real
number; a call to hasdomain(real, IX) makes sure that toTerm will yield
a real number.
6.4 String operations 69
implement main
clauses
classinfo("main", "toTerm_example").
run():- console::init(),
Sx= stdio::readLine(),
hasdomain(real, IX),
IX= toTerm(Sx),
stdio::write(IX^2).
end implement main
goal mainExe::run(main::run).
This scheme works even for lists, and other complex data structures, as you
can see below. Unfortunately, hasdomain does not accept the star notation
for list types; therefore, you must declare a list domain, or use a pre-defined
domain, like core::integer_list.
implement main
clauses
classInfo("main", "hasdomain_example").
run():- console::init(),
hasdomain(core::integer_list, Xs),
Xs= toTerm(stdio::readLine()),
stdio::write(Xs), stdio::nl.
end implement main
goal mainExe::run(main::run).
implement main
clauses
classInfo("main", "toString_example").
run():- console::init(),
Xs= [3.4, 2, 5, 8],
Str= toString(Xs),
Msg= string::concat("String representation: ", Str),
stdio::write(Msg), stdio::nl.
end implement main
goal mainExe::run(main::run).
70 Console Applications
If you want to format numbers and other simple terms nicely into a string,
you can use the string::format format function; an example is given below.
implement main
clauses
classInfo("main", "formatting").
run():- console::init(),
Str1= string::format("%8.3f\n %10.1e\n", 3.45678, 35678.0),
Str2= string::format("%d\n %10d\n", 456, 18),
Str3= string::format("%-10d\n %010d\n", 18, 18),
stdio::write(Str1, Str2, Str3).
end implement main
goal
mainExe::run(main::run).
In the above example, the format "%8.3f\n means that I want to repre-
sent a real number, and a carriage return; the field width is 8, and the number
must be represented with three digits of precision. The format "%010d\n"
requires an integer right justified on a field of width 10; the field empty po-
sitions must be filled in with zeroes. The format "%-10d\n" specifies the
representation of a left justified integer on a field of width 10; the minus
sign assures left justification; right justification is the default. The format
"%10.1e\n" specifies scientific notation for real numbers. Below, you can see
the output of the example.
3.457
3.6e+004
456
18
18
0000000018
Below, you will find a listing of the data types accepted by the format string.
Note that when converting real values into a text representation they are
truncated and rounded with 17 digits, unless another precision is specified.
f Format reals in fixed-decimal notation (such as 123.4).
c Format as a char.
s Format as a string.
domains
adjustBehaviour =
expand();
cutRear();
cutOpposite().
adjustSide = left(); right().
caseSensitivity =
caseSensitive();
caseInsensitive();
casePreserve().
/*********************************
Project Name: adjust_example
UI Strategy: console
***********************************/
implement main
clauses
classInfo("main", "adjust_example").
run():- console::init(),
FstLn=
"Rage -- Goddess, sing the rage of Peleus’ son Achilles,",
Str= string::adjust(FstLn, 60, "*", string::right),
stdio::write(Str), stdio::nl.
end implement main
goal mainExe::run(main::run).
/*********************************
Project Name: stringops
UI Strategy: console
***********************************/
implement main
clauses
classInfo("main", "stringops").
run():-
console::init(),
SndLn= [ "murderous", "doomed",
"that cost the Achaeans countless losses"],
Str= string::concatWithDelimiter(SndLn, ", "),
Rule= string::create(20, "-"),
stdio::write(Str), stdio::nl,
stdio::write(Rule), stdio::nl.
end implement main
goal mainExe::run(main::run).
/*********************************
Project Name: trim_example
UI Strategy: console
***********************************/
implement main
clauses
classInfo("main", "trim_example").
run():-
console::init(),
Str= " murderous, doomed ",
T= string::trim(Str),
stdio::write(T), stdio::nl,
T1= string::trimInner(T),
stdio::write(T1), stdio::nl.
end implement main
goal mainExe::run(main::run).
murderous, doomed
murderous, doomed
∃X(sin(X) = 0)
Clausal form Any sentence of the predicate can be re-written into the
clausal form, i.e., as a disjunction of conjunctions, with no existential quantifier(∃),
and no universal quantifier inside parentheses. Since A ∨ B is a disjunction,
and A ∧ B is a conjunction, any formula can be re-written as
where Lij are literals, or negations of literals. We have already seen how to
remove existential quantifiers from a formula. Let us examine the remaining
necessary steps to obtain the clausal formula.
1. Remove implications, and equivalences.
α→β ¬α ∨ β
α≡β (α ∧ β) ∨ (¬α ∧ ¬β)
¬(α ∨ β) ≡ ¬α ∧ ¬β
and that
¬(α ∧ β) ≡ ¬α ∨ ¬β
Then, use these results to make the following re-writing operations:
¬(α ∨ β) ¬α ∧ ¬β
¬(α ∧ β) ¬α ∨ ¬β
78 Console Applications
∃X(p(X)) p(s0 )
∀X(man(X) → ∃W (loves(W, X))) ∀X(man(X) → loves(w(X), X))
(α ∧ β) ∨ γ (α ∨ β) ∧ (β ∨ γ)
¬(α ∧ β) ¬α ∨ ¬β
Chapter 7
Grammars
class semantic
open core
domains
category= art; nom; cop; rel.
tree= case(category, string); world(tree, tree); silence.
predicates
classInfo : core::classInfo.
end class semantic
79
80 Grammars
For instance, the constructor case/2 has two arguments, a grammatical cat-
egory and a string representing a token.
%File: german.pro
implement german
open core, semantic
clauses
classInfo("german", "1.0").
If you pay attention to the declaration of article, you will notice that its
flow pattern is (o, i, o), id est, it inputs a list of words, and outputs a parse
tree2 and the partially consumed input list. The definition of noun works
exactly like article. An example is worth one thousand words; let us feed
["die", "Welt", "ist", "alles"] to article/3.
The output will be T1= case(art, ""), and S1= ["Welt", "ist", "alles"].
Next, let us feed S1= ["Welt", "ist", "alles"] to noun/3.
The above call will match the first clause of noun/3, with R=["ist", "alles"].
The output will be T2=case(nom, "Welt"), and End=["ist", "alles"].
The definition nounphr makes these two calls in succession, parsing a noun
phrase like ["die", "Welt"]; copula/3, and phr/3 behaves like nounphr.
%File: english.cl
class english
open core, semantic
predicates
classInfo : core::classInfo.
article:(tree, string*) nondeterm (i,o)(o,o).
noun:(tree, string*) nondeterm (i,o)(o,o).
nounphr:(tree, string*) nondeterm (i,o)(o,o).
copula:(tree, string*) nondeterm (o,o)(i, o).
phr:(tree, string*) nondeterm (i,o).
end class english
% File: english.pro
implement english
open core, semantic
clauses
classInfo("english", "1.0").
The second argument of the head uses the list::append(Start, S1) func-
tion to concatenate a noun phrase with a copula, yielding a phrase.
To implement and test this program, create the tratactus console project.
Insert the classes german, english and semantic into the project tree. Do
not forget to untick the Create Objects box. Add the given code to the
appropriate class files. The testing program is given in figure 7.1. After
compiling it, if you want to test it from inside VIP IDE, use the option
Build/Run in Window. By the way, if you do not want to see a lot of errors,
introduce the semantic class first of all; build the application; then introduce
the german class, and build the application again; the next step is to add the
english class, and rebuild the application. Finally, you can insert the code
for the main class, and build the application for the last time.
7.4 Examples
The example of this chapter (tratactus) shows how one could write a program
that translates philosophical German into English.
/***********************************
************************************/
implement main
open core, console, semantic
class predicates
test:().
clauses
classInfo("main", "tratactus").
Figure 7.1: Die Welt ist alles, was der Fall ist.
• Prove that the introduction of control mechanisms does not change the
semantic of the algorithm.
To prove that a program is correct, [Gries] proposes that one specifies the
program with Predicate Calculus, and prove its correctness using a method
known as Natural Deduction. In Logics, natural deduction is an approach to
proof theory that tries to provide a more natural model for logical reasoning
than the one proposed by Frege and Russel.
Epistemology says that a judgment is something that one can know. An
object is evident if one in fact knows it. Something is evident if one can
observe it; for instance, global warming is evident because scientists have
records that shows it happening. Many times, evidence is not directly ob-
servable, but rather deduced from basic judgments. The steps of deduction
is what constitutes a proof. There are many kinds of judgement:
one of the logic connectives present in the calculus one is working with. If
one is working with Predicate Calculus, there are insertion, and elimination
rules for negation (¬P ), conjunction (P ∧ Q), disjunction (P ∨ Q), equiva-
lence (P ≡ Q), implication (P → Q), universal quantification(∀X(P )), and
existential quantification (∃X(P )). In an inference rule, one calls prosthesis
the expressions above the line, and apodosis what comes below the line. The
turnstile symbol ` is not a part of a well-formed formula: but a meta-symbol.
One can see P ` Q as meaning P entails Q, and ` Q as Q is a theorem. Here
are a few inference rules of the Natural Deduction system that one can use
with Predicate Calculus sentences in the clausal form:
E 1 , E2 , . . . E n
and Insertion ∧ /I :
E1 ∧ E2 . . . En
E1 ∧ E2 . . . En
and Elimination ∧ /E :
Ei
Ei
or Insertion ∨ /I :
E1 ∨ E2 . . . En−1 ∨ En
E1 ∨ E2 , E1 → E, E2 → E
or Elimination ∨ /E :
E
E 1 , E2 ` E
implication Insertion → /I :
E1 ∧ E2 → E
E1 → E2 , E1
Modus Ponens → /E :
E2
E ` E1 ∧ ¬E1
not Insertion ¬/I :
¬E
¬E ` E1 ∧ ¬E1
Modus Ponens ¬/E :
E
Since we intend to work with the clausal form, you will not need the rules
corresponding to the quantifiers. Let us play a little with our new toy. Prove
that p ∧ q ` p ∧ (r ∨ q):
p ∧ q ` p ∧ (r ∨ q)
1 p∧q premise 1
2 p ∧/E, 1
3 q ∧/E, 1
4 r∨q ∨/I, 3
5 p ∧ (r ∨ q) ∧/I, 2, 4
In the proof shown above, each step always contains the inference rule,
and the lines on which it was used. For instance, line p ∧ (r ∨ q) because of
the ∧/I inference rule, and of lines 2 and 4.
Chapter 8
Painting
In this chapter, you will learn how to draw pictures using the onPaint event
handler. Start by creating a project with a form, where you will draw things.
• Create a project:
• Enable the option File/New of the application task menu, and add
clauses
onFileNew(S, _MenuTag) :-
X= canvas::new(S), X:show().
to TaskWindow/Code Expert/Menu/TaskMenu/id_file/id_file_new.
After this step, if you compile and run the program, whenever you
choose the File/New option of the application task menu, the above
predicate will be executed. The command X= canvas::new(S) creates
a new X object of class window. This window will be the child of the
task window S. The command X:show() sends a message to the object
X to show the window.
What will you do next? If I were you, I would draw something on canvas,
to create an interesting background.
87
88 Painting
8.1 onPainting
Whenever a window needs painting, it calls the event handler onPainting.
Therefore, if you add instructions to onPainting, they will be executed.
% File dopaint.cl
class dopaint
open core
predicates
bkg:(windowGDI).
end class dopaint
%File: dopaint.pro
implement dopaint
open core, vpiDomains
clauses
bkg(W) :-
P= [pnt(0,0), pnt(10,80), pnt(150, 100)],
W:drawPolygon(P).
end implement dopaint
are given by the list P. Compile the program, and check whether it will act
exactly as said.
Let us improve method bkg(W). It draws a white triangle on the pane of
canvas. To make the triangle red, one must change the painter’s brush. A
brush is a two argument object.
Colors are represented by integers that specify the amount of red, green,
and blue. As you probably know, you can get a lot of colors by combining
red, green, and blue palettes. Let us represent the integers in hexadecimal
basis. In hexadecimal, numerals have fifteen digits: 0, 1, 2, 3, 4, 5, 6, 7,
8, 9, A, B, C, D, E, F. The red color is given by the integer 0x000000FF,
where 0x shows that we are dealing with the hexadecimal basis. Representing
colors by integers is fine, but you have the option of using identifiers. For
instance, color_Red represents the red color, as you can guess. There are
also identifiers for patterns:
Finally, here is a version of bkg(W) that draws an ellipse, and clears a green
rectangle inside it.
The last thing that you will learn in this chapter is how to add a bmp picture
to your window. Here is how to modify method bkg(W):
bkg(W) :- P= vpi::pictLoad("frogs.bmp"),
W:pictDraw(P, pnt(10, 10), rop_SrcCopy).
90 Painting
The program that put the eagle among the frogs is given
below, and is implemented in directory paintEagle, of the
examples.
The paintEagle project is exactly alike painting, except for
the definition of bkw(W).
% File dopaint.cl
class dopaint
open core
predicates
bkg:(windowGDI).
end class dopaint
%File: dopaint.pro
implement dopaint
open core, vpiDomains
clauses
bkg(W) :- P= vpi::pictLoad("frogs.bmp"),
W:pictDraw(P, pnt(10, 10), rop_SrcCopy),
Mask= vpi::pictLoad("a3Mask.bmp"),
Eagle= vpi::pictLoad("a3.bmp"),
W:pictDraw(Mask,pnt(50, 50),rop_SrcAnd),
W:pictDraw(Eagle,pnt(50, 50),rop_SrcInvert).
end implement dopaint
8.2 Custom Control 91
• Choose the option File/New in New Package from the IDE task menu.
At the Create Project Item dialog, select the Control tag from the left
hand side pane. The name of the new custom control will be canvas.
Name: canvas
New Package
Parent Directory [ ]
Leave the Parent Directory field blank. Press the Create button.
• Add a new form to the project tree. Choose File/New in New Package
from the task menu, and select the Form tag from the left hand side
pane of the Create Project Item dialog. Let the new form’s name be
greetings. After pressing the Create-button, you will obtain a proto-
type of the greetings-form.
clauses
onHiClick(_Source) = button::defaultAction :-
W= custom_ctl:getVPIWindow(),
X= convert(integer, math::random(100)),
Y= convert(integer, math::random(100)),
vpi::drawText(W, X , Y, "Hello!").
clauses
onFileNew(S, _MenuTag) :-
F= greetings::new(S), F:show().
to TaskWindow.win/Code Expert/Menu/TaskMenu/id_file/id_file_new.
Build and execute the application. Choose the option File/New, and
a new form will appear. Whenever you click on the canvas control of
the new form, it will print warm greetings.
P ∨ Q, ¬P ∨ R ` Q ∨ R
To prove this principle, we will need two syllogisms. One of them is the first
distributive law, that says
(a ∨ b) ∧ (a ∨ c) ≡ a ∨ (b ∧ c)
There is also a second distributive law, that we are not going to use in the
proof of Robinson’s resolution principle.
(a ∧ b) ∨ (a ∧ c) ≡ a ∧ (b ∨ c)
Let us take a concrete case of these laws. Lysias was an Athenian lawyer
that lived in the time of Socrates. In fact, he was the son of that Kephalos
who entertains Socrates in Plato’s Republic. He was a metic, or resident
alien; as such, he did not have civil rights; nevertheless his family was very
rich, as Plato explains in the Republic. His defences and prosecutions are
very interesting. For instance, during the dictatorship of the thirty tyrants,
the rulers of country decided to take 8 rich metics, and two poor ones, kill
them, and rob them; Lysias and his brother Polemarchus were among those
condemned to death. However, Lysias managed to flee, and returned to his
country to help restore the democracy, and to prosecute one of the tyrants
that killed his brother.
In another occasion, Lysias defended a man who killed his wife’s lover. To
understand his defence, you need to know a few facts about the Greek society.
Man and woman lived in different quarters: Man lived in the androecium, and
women lived in the gynoecium. You probably remember that angiosperms
also have androecia (housing for males) and gynoecia (housing for females);
the reason is that Carl von Linné named these parts of the vegetal physiology
after the housing arrangements of the Ancient Greeks. The other Greek
custom was the dowry. When the woman left her father’s house, she would
take a handsome amount of money to the house of her husband. Therefore,
no man was inclined to divorce his wive, because this would entail returning
the money to her family.
When Eratosthenes discovered that his wife was cheating him, he decided
to kill her lover, for the divorce would mean losing her rich dowry. He feigned
a trip to his farm in the outskirts of the city, but returned on time to kill the
94 Painting
villain on the unfaithful woman’s bed. However, the prosecutor claimed that
the ravenous husband killed the rival on the domestic altar, to where he fled
seeking the protection of Zeus. If Eratosthenes killed the man on the altar,
the jury would condemn him to death; if he killed him on the bed, he would
be acquitted of all charges. Lysias’s defense of his client was based on the
following argument:
He was on the altar or he was on the bed, and he was on the altar,
or he was killed.
The prosecutor accepted the argument. Then Lysias simplified it, using the
first distributive law:
He was on the altar, or he was on the bed and he was killed. Since
you know that he was killed, he was on the bed.
Eratosthenes was acquitted. After this long digression, let us prove the res-
olution principle.
p ∨ q, ¬p ∨ r ` q ∨ r
1 p∨q∨r ∨/I premise 1
2 ¬p ∨ q ∨ r ∨/I premise 2
3 (p ∨ q ∨ r) ∧ (¬p ∨ q ∨ r) ∧/I, 1, 2
4 (q ∨ r) ∨ (p ∧ ¬p) first distributive law, 3
5 q∨r Modus tollendo ponens
Chapter 9
Data types
You have seen that symbols look a lot like strings: Both are a sequence
of UNICODE characters. However, they are stored differently. Symbols
95
96 Data types
are kept in a look-up table and Prolog uses their addresses in the table to
represent them internally. Therefore, if a symbol occurs many times in your
program, it will use less storage than a string. Below, there is a very simple
example of using real numbers, symbols, and strings. This example is taken
from chemistry.
/********************************
Project Name: primtype1
UI Strategy: console
*********************************/
% File main.pro
implement main
open core
constants
className = "main".
classVersion = "primtype1".
clauses
classInfo(className, classVersion).
class predicates
energy:(real, real, real) procedure (i, i, o).
family:(symbol, string) procedure (i, o).
clauses
energy(N, Z, E) :- E= -2.18E-19*Z*Z/(N*N).
run():- console::init(),
energy(4, 1, E1),
energy(2, 1, E2),
DeltaE= E1-E2,
stdio::write(DeltaE), stdio::nl,
family("Na", F),
stdio::write("Na", " is an ", F), stdio::nl,
succeed().
end implement main
goal
mainExe::run(main::run).
9.2 Sets 97
9.2 Sets
A set is a collection of things. You certainly know that collections do not
have repeated items. I mean, if a guy or gall has a collection of stickers, s/he
does not want to have two copies of the same sticker in his/her collection. If
s/he has a repeated item, s/he will trade it for another item that s/he lacks
in his/her collection.
It is possible that if your father has a collection of Greek coins, he will
willingly accept another drachma in his set. However, the two drachmas are
not exactly equal; one of them may be older than the other.
Asunción Flores, listen to his song India, and I am sure that you will put
Paraguay in your map. As for Argentina, it is the fatherland of Bernardo
Houssay, Luis Federico Leloir, César Milstein, Adolfo Pérez Esquivel, Carlos
Saavedra Lamas, Jorge Luiz Borges, Alberto Calderon, Alberto Ginastera,
José Cura, Dario Volonté, Verónica Dahl, Carolina Monard, Che Guevara,
Diego Maradona, Juan Manuel Fangio, Osvaldo Golijov, Eva Peron, Hector
Panizza, José Cura etc. Before I forget, José Asunción Flores lived in Buenos
Aires. But let us return to sets.
When a mathematician wants to say that an element is a member of a
set, he writes something like
3∈Z
If he wants to say that something is not an element of a set, for instance, if
he wants to state that −3 is not an element of N, he writes:
−3 6∈ N
Let us summarize the notation that Algebra teachers use, when they explain
set theory to their students.
Double backslash. The weird notation {x2 ||x ∈ N} represents the set of
x2 , such that x is a member of N, or else, {0, 1, 4, 9, 16, 25 . . .}
Guard. If you want to say that x is a member of N on the condition that
x > 10, you can write {x||x ∈ N ∧ x > 10}.
Conjunction. In Mathematics, you can use a symbol ∧ to say and; therefore
x > 2 ∧ x < 5 means that x > 2 and x < 5.
Disjunction. The expression
(x < 2) ∨ (x > 5)
Visual Prolog does not have a notation for sets, but you can use lists to
represent sets. For instance, choose the option Project/New from the task
menu, and fill the Project Settings dialog thus:
General
Project Name: zermelo
UI Strategy: console
Pay attention that we are going to use the console strategy, not GUI. Choose
the option Build/Build from the task menu to put a prototype of class zermelo
into the project tree. Edit zermelo.pro as shown below. Build the project
again, and execute it using Build/Run in Window.
implement main
open core
clauses
classInfo("main", "zermelo").
run():-
console::init(),
Q= [tuple(X, Y) || X= std::fromTo(1, 4),
Y= std::fromTo(1, 5)],
stdio::nl,
stdio::write(Q), stdio::nl, stdio::nl,
foreach tuple(Num, Den)= list::getMember_nd(Q) do
stdio::write(Num, "/", Den,", ")
end foreach,
stdio::nl.
end implement main
goal
mainExe::run(main::run).
You may wonder why I named the above program after the German
mathematician Ernst Friedrich Ferdinand Zermelo. One reason could be
that he was appointed to an honorary chair at Freiburg im Breisgau in 1926,
which he resigned in 1935 because he disapproved of Hitler’s regime. Another
reason was that he invented the notation for set that we have been using.
In plain English, Q is a list of pairs (X, Y ) such that X belongs to [1, 2, 3, 4],
and Y belongs to [1, 2, 3, 4, 5]. The snippet
tells the computer to write(Num, "/", Den,", ") for each tuple (N um, Den)
that is member of the list Q.
The set of rational numbers is so named because its elements can be
represented as a fraction like
p
q
where p ∈ N and p ∈ N.
The next section is somewhat hard to digest. Therefore, if you want
to skip it, feel free to do so. In the section about Real Numbers, I will
summarize the important results, so you wont’t miss anything worth the
effort of understanding a whole page of nasty mathematics.
of the square, and q is the result of measuring the diagonal. The Pythagorean
2 2 2
theorem states that AC + CB = AB , i.e.,
p2 + p2 = q 2 ∴ 2p2 = q 2 (9.1)
You can also choose the meter so that p and q have no common factors.
For instance, if both p and q were divisible by 2, you could double the length
of the meter, getting values no longer divisible by 2. E.g. if p = 20 and
q = 18, and you double the length of the meter, you get p = 10, and q = 9.
Thus let us assume that one has chosen a meter so that p and q are not
simultaneously even. But from equation 9.1, one has that q 2 is even. But if
q 2 is even, q is even too. You can check that the square of an odd number
is always an odd number. Since q is even, you can substitute 2 × n for it in
equation 9.1.
2 × p2 = q 2 = (2 × n)2 = 4 × n2 ∴ 2 × p2 = 4 × q 2 ∴ p2 = 2 × n2 (9.2)
real — Reals must be written with a decimal point: 3.4, 3.1416, etc.
char — Characters between single quotes: ’A’, ’b’, ’3’, ’ ’, ’.’, etc.
9.6 Mathematics
I always thought it very hard to satisfy mathematicians, since they are so
picky, and I have an appalling dificulty in keeping even a pretense of being
formal. In particular, my definitions of the different numerical sets are not
rigorous as most mathematicians would require. However, I have no intention
of writing for an AMS journal, or making major contributions to number
theory; I will be content if I am able to give non-mathematicians a working
knowledge of the concepts involved. By working knowledge, I mean that I
hope that my readers will be able to understand what a type is, and how
type are related to Set Theory. As a bonus, I hope that students of literature
will find Borges’ books easier to digest, after reading my text; hence my
Argentinian friends may forgive me for telling jokes about them.
9.7 Format
The method format creates nice string representations of primitive data
types for output. For instance, it is nice to represent colors using hexadecimal
integers. In this representation, the fundamental colors become 0x00FF0000
(red), 0x0000FF00 (green), and 0x000000FF (blue). However, if you try to
print these numerals using stdio::write/n, you will get them in the decimal
basis. Using string::format/n, as shown below, you will get a printout of
the primitive colors in hexadecimal.
9.7 Format 103
/*******************************
Project Name: primtype2
UI Strategy: console
********************************/
% File main.pro
implement main
open core
constants
className = "main".
classVersion = "primtype2".
clauses
classInfo(className, classVersion).
class predicates
color:(symbol, integer) procedure (i, o).
clauses
color("red", 0x00FF0000) :- !.
color("blue", 0x000000FF) :- !.
color("green", 0x0000FF00) :- !.
color(_, 0x0).
run():- console::init(),
color("red", C),
S= string::format("%x\n", C),
stdio::write(S), stdio::nl,
succeed().
end implement main
goal
mainExe::run(main::run).
The first argument of function format specifies how you want the printout.
In the example, the argument is "%x\n": You want a hexadecimal repre-
sentation of a number, followed by a carriage return (\n). The arguments
that follow the first show the data that you want to print. Here are a few
examples of formatting:
9.8 domains
You may need other data types besides the primitives provided by Visual
Prolog. In this case, you need to specify the new data types that you will
insert in your code. In previous chapters you have already learned how to
declare domains; you also learned how to create complex data structures, like
lists. Therefore, all we have to do in this section is to recall concepts already
acquired, and to make a synthesis of what we have learned.
9.8.1 Lists
You have learned that a list is an ordered sequence of elements. You also
know how to create list domains, and declare predicates on lists. For instance,
here are a few list domains:
domains
reals= real*. % a list of reals
integers= integer*. % a list of integers
strings= string*. % a list of strings
rr= reals*. % a list of lists of reals
Once you have a domain declaration, you can use it like any primitive type.
In the case of list, domain declaration is not necessary, since one can use list
types like real*, string* or integer* directly in the predicate declaration,
as in the program of figure 9.2.
9.8.2 Functors
Predicate calculus, a branch of Logics, has a data type called functional
symbol. A functional symbol has a name, or functor, followed by arguments.
9.8 domains 105
/*******************************
Project Name: newdomains
UI Strategy: console
********************************/
% File main.pro
implement main
open core
class predicates
sum:(real*, real) procedure (i, o).
clauses
classInfo("main", "newdomains").
sum([], 0) :- !.
sum([X|Xs], X+S) :- sum(Xs, S).
run():- console::init(),
sum([1, 2, 3, 4, 5, 6], A),
stdio::writef("The sum is %-4.0f", A).
end implement main
goal
mainExe::run(main::run).
The arguments are written between parentheses, and separated from each
other by commas. To make a long story short, they have exactly the same
notation as functions. Here are a few example of functional symbols:
• author("Wellesley", "Barros", 1970)
• index("Functors", 165)
• sunday
106 Data types
The last example shows that a functional symbol can lack arguments. This
said, let us see how to declare data types for a few functors. The example
calculates the day of the week for any date between the years 2000 and 2099.
It has the following domain declarations:
DOMAINS Comments
year= integer. year is synonym of integer. The use of
synonyms makes things clearer.
day= sun; mon;tue; wed; This domain has many functional sym-
thu; fri; sat; err. bols without arguments. Each func-
tional symbol is separated from the
next by semicolon, and represents a day
of the week.
month= jan;feb;mar;apr; Another domain (type) with more than
may;jun;jul;aug;sep; one functional symbol.
oct;nov;dec.
month code= Here, one has a functional symbol with
m(month, integer). two arguments.
numberOfDays= Another type with two functional sym-
nd(integer); bols; the first functional symbol has one
february. argument; the second one has no argu-
ment at all.
/*******************************
Project Name: dayOfWeek
UI Strategy: console
********************************/
% File: main.pro
% This program calculates the day of the week
% for the years between 2000 and 2099.
implement main
open core
constants
className = "main".
classVersion = "dayOfWeek".
domains
year= integer.
day= sun; mon;tue; wed; thu; fri; sat; err.
month= jan;feb;mar;apr;may;jun;jul;aug;sep;oct;nov;dec.
month_code= m(month, integer).
numberOfDays= nd(integer); february.
9.8 domains 107
class predicates
class facts
monthCode:(string, month_code, numberOfDays).
clauses
classInfo(className, classVersion).
dayOfWeek(0, sun) :- !.
dayOfWeek(1, mon) :- !.
dayOfWeek(2, tue) :- !.
dayOfWeek(3, wed) :- !.
dayOfWeek(4, thu) :- !.
dayOfWeek(5, fri) :- !.
dayOfWeek(6, sat) :- !.
dayOfWeek(_, err).
108 Data types
run():-
console::init(),
calculateDay("May", 3, 2005, R),
stdio::write("Day of the week: ", R), stdio::nl,
succeed(). % place your own code here
end implement main
goal
mainExe::run(main::run).
The title of this chapter is a homage to Helder Coelho, the first author
of the best book on Prolog ever written: [HowToSolveItWithProlog]. This
book was printed by the Laboratorio Nacional de Engenharia Civil, where
Helder Coelho works in Portugal. Later, Coelho published Prolog by Example
(see [Coelho/Cotta]), that is good too, but not as good as his first book.
Coelho’s book is a collection of short problems, proposed and solved by
great programmers and computer scientists. The whole thing is organized as
a kind of FAQ. The problems are interesting, and the solutions are illumi-
nating. More important, the book is also very amusing. I hope that it will
see a new edition soon, since it shows the history of the creators of Logic
Programming, their discoveries, their explorations.
All programs in this chapter will be console applications. Besides this,
I will only provide the listing of the main class implementation. It is up to
you to complete the red tape. For instance, if I give you the listing
implement main
open core
clauses
classInfo("main", "hello").
clauses
run():- console::init(), stdio::write("Hello, world!\n").
end implement main
goal
mainExe::run(main::run).
109
110 How to solve it in Prolog
10.1 Utilities
Verbal statement: Search all members of a list L. Logic program:
implement main
open core
class predicates
member:(integer, integer*) nondeterm anyflow.
member:(string, string*) nondeterm anyflow.
test:(string*) procedure (i).
test:(integer*) procedure (i).
clauses
classInfo("main", "utilities").
member(H, [H|_]).
member(H, [_|T]) :- member(H, T).
goal
mainExe::run(main::run).
The example shows that you can define a predicate with different domains.
For instance, there is a definition of member/2 for string*, and another for
integer*. The same thing happens to test/1.
The definition of test/1 uses the predicate fail to backtrack, and print all
solutions of the nondeterm predicate member. As a result, the program will
print all elements of the list.
10.1 Utilities 111
implement main
open core, stdio
class predicates
isec:(integer*, integer*, integer*) nondeterm anyFlow.
memb:(integer, integer*) nondeterm anyFlow.
tst:(integer*, integer*, integer*, string) procedure(i, i, i, o).
findsec:(integer*, integer*, integer*) procedure (i, i, o).
length:(integer*, integer) procedure (i, o).
clauses
classInfo("main", "intersection").
goal
mainExe::run(main::run).
112 How to solve it in Prolog
implement main
open core
class predicates
app:(Elem* L1, Elem* L2, Elem* L3)
nondeterm anyFlow.
test1:(string*) procedure (i).
test2:(integer*, integer*) procedure (i, i).
clauses
classInfo("main", "append").
app([], L, L).
app([H|T], L, [H|U]) :- app(T, L, U).
clauses
run():- console::init(),
test1(["a", "b", "c", "d"]), stdio::nl,
test2([1, 2, 3], [4, 5, 6]).
end implement main
goal
mainExe::run(main::run).
This problem is an all time favorite. If you run the program, test1/1 will
find and print all ways of cutting the list ["a", "b", "c", "d"]. On the
other hand, test2/2 will concatenate two lists. Both predicates use app/3,
which perform two operations, concatenation, and cutting down lists.
10.1 Utilities 113
/*******************************
Project Name: reverse
UI Strategy: console
********************************/
implement main
open core
class predicates
rev:(integer*, integer*) procedure (i, o).
rev1:(integer*, integer*, integer*) procedure (i, i, o).
clauses
classInfo("main", "reverse").
rev1([], L, L) :- !.
rev1([H|T], L1, L) :- rev1(T, [H|L1], L).
goal
mainExe::run(main::run).
rev([1, 2, 3], R)
to Prolog. Then the inference engine will try to make rev([1, 2, 3], R)
equal to the head of one of the Horn clauses that are in the program. In the
present case, it will succeed if it makes L=[1,2,3] in the clause
Laplace used to say that Newton was not only the greatest scientist of all
times and lands, but also a very lucky man, for he was born before Laplace.
Of course, Le Marquis would discover the Celestial Mechanics before Newton,
if he had the chance of doing so. I suppose that you would write the quick
sort algorithm before Van Emden, if he was not lucky enough to put his hands
on a Prolog compiler before you. The quick sort algorithm was invented by
Tony Hoare, that wanted a fast method for sorting lists and vectors. This
was a time when B+Trees did not exist, and the only way to perform a fast
search in a list of customers was to have it sorted.
Let us see how Van Emden explained his algorithm to his friend Coelho.
First of all, he implemented a split/4 algorithm, that splits a list L in two
sublists: S(mall) and B(ig). The S sublist contains all of L that are smaller of
equal to P(ivot). The B sublist contains the elements that are larger than P.
Here is how Van Emden implemented the split/4:
implement main
open core
clauses
classInfo("main", "quicksort").
class predicates
split:(E, E*, E*, E*) procedure (i, i, o, o).
clauses
run():- console::init(),
split(5, [3, 7, 4, 5, 8, 2], Small, Big),
stdio::write("Small=", Small, ", Big= ", Big), stdio::nl,
split("c", ["b", "a", "d", "c", "f"], S1, B1),
stdio::write("Small=", S1, ", Big= ", B1), stdio::nl.
goal
mainExe::run(main::run).
Small=[3,4,5,2], Big=[7,8]
116 How to solve it in Prolog
which is what it should do. Now, let us understand the idea behind the quick-
sort algorithm. Let us assume that you have the list [5, 3, 7, 4, 5, 8, 2],
and you want to sort it.
Sorted_Small= [2, 3, 4, 5]
This algorithm needs a predicate that appends one list to another. You
can use app/3, that you learned on page 112. However, that algorithm is
nondeterm, which we do not need, since we do not need the functionality of
cutting a list in two; all we need is a vanilla concatenation. Therefore, you
can insert a cut after the first clause of app/3.
app([], L, L) :- !.
app([H|T], L, [H|U]) :- app(T, L, U).
Declare app as a procedure with two input arguments, and one output.
To test the algorithm, we are going to use a list of integers. Of course, it will
be more useful with a lists of strings.
10.1 Utilities 117
implement main
open core
clauses
classInfo("main", "quicksort").
class predicates
split:(E, E*, E*, E*) procedure (i, i, o, o).
clauses
split(P, [H|T], S, [H|B]) :- H>P, !, split(P, T, S, B).
split(P, [H|T], [H|S], B) :- !, split(P, T, S, B).
split(_, [], [], []).
app([], L, L) :- !.
app([H|T], L, [H|U]) :- app(T, L, U).
goal
mainExe::run(main::run).
118 How to solve it in Prolog
Verbal Statement: The number of days in a year is 366 each four years;
otherwise it is 365. How many days have years 1975, 1976, and 2000?
implement main
open core
class predicates
no_of_days_in:(integer, integer) procedure (i, o).
member:(integer, integer_list) nondeterm (o, i).
test:(integer_list) procedure (i).
clauses
classInfo("numdays", "1.0").
member(H, [H|_T]).
member(H, [_|T]) :- member(H, T).
run():- console::init(),
test([1975, 1976, 2000]).
end implement main
goal
mainExe::run(main::run).
10.1 Utilities 119
Verbal statement: Write a program for coloring any planar map with at
most four colors, such that no two adjacent regions have the same color.
The problem is a classical generate and test program. In test(L), the pred-
icate calls
generateColor(A), generateColor(B),
generateColor(C), generateColor(D),
generateColor(E), generateColor(F),
generate colors for the six regions of the map. Then, test(L) builds a the
map as a list of neighboring countries:
1. The generate and test scheme was one of the first Artificial Intelligence
techniques.
implement main
open core
domains
colors= blue;yellow;red;green.
neighbors= nb(colors, colors).
map= neighbors*.
class predicates
aMap:(map) nondeterm anyFlow.
test:(map) procedure anyFlow.
generateColor:(colors) multi (o).
clauses
classInfo("main", "fourcolors").
aMap([]).
aMap([X|Xs]) :- X= nb(C1, C2), not(C1 = C2),
aMap(Xs).
goal
mainExe::run(main::run).
10.1 Utilities 121
[1,1,1,1,1]
[ [1,1,1,1,1],
[1, 1],
[1,1,1,1]]
Two players, you and the computer, alternate making moves. As soon as a
player is unable to make a move, the game is over, and that player has lost.
A move consists of taking at least one match from exactly one heap. In the
example, if you take three matches from the third heap, you make a valid
move, and the board becomes
[ [1,1,1,1,1],
[1, 1],
[1]]
where you have removed three matches from the third heap. To implement
this project, we will use a programming technique called incremental de-
velopment of systems. First, you will implement and test a program that
appends two lists. Since you are going to use this program both to split and
to concatenate lists, you need to test both possibilities. In fact, if you run
the first program (next page), you will get
[[1],[1],[1],[1],[1],[1],[1],[1]]
[][[1],[1],[1,1]]
[[1]][[1],[1,1]]
[[1],[1]][[1,1]]
[[1],[1],[1,1]][]
The first line is the result of concatenating two sets of heaps; the lines that
follow shows the many possibilities of splitting a list of heaps. Then the first
program seems to be working properly.
122 How to solve it in Prolog
implement main
open core
domains
ms= integer*.
hs= ms*.
class predicates
append:(E*, E*, E*) nondeterm anyFlow.
clauses
classInfo("main", "nimgame").
append([], Y, Y).
append([U|X], Y, [U|Z]) :- append(X, Y, Z).
clauses
run():-
console::init(),
append([[1],[1],[1],[1],[1]], [[1],[1],[1]], L), !,
stdio::write(L), stdio::nl; succeed().
end implement main
goal
mainExe::run(main::run).
The next step is to write a predicate that takes at least one match from
a heap; of course, it can take more than one match. After removing one or
more matches from the heap, takesome/3 inserts the modified heap into a
set of heaps. If you test the program, it will print the following result:
[[1,1,1,1],[1,1]]
[[1,1,1],[1,1]]
[[1,1],[1,1]]
[[1],[1,1]]
[[1,1]]
NB: the first clause of takesome/3 makes sure that the predicate will not
insert an empty heap into the set of heaps.
10.1 Utilities 123
implement main
open core
domains
ms= integer*.
hs= ms*.
class predicates
append:(E*, E*, E*) nondeterm anyFlow.
test:() procedure.
takesome:(ms, hs, hs) nondeterm anyFlow.
clauses
classInfo("main", "nimgame").
append([], Y, Y).
append([U|X], Y, [U|Z]) :- append(X, Y, Z).
takesome([_X], V, V).
takesome([_X, X1|Y], V, [[X1|Y]|V]).
takesome([_X|T], V, Y) :- takesome(T, V, Y).
clauses
run():- console::init(),
test().
end implement main
goal
mainExe::run(main::run).
Now, you are ready to generate all possible moves from a given position. If
you run the test program, it will produce the following screen:
Possible moves from [[1,1,1],[1,1]]:
[[1,1],[1,1]]
[[1],[1,1]]
[[1,1]]
[[1,1,1],[1]]
[[1,1,1]]
124 How to solve it in Prolog
implement main
open core
domains
ms= integer*.
hs= ms*.
class predicates
append:(E*, E*, E*) nondeterm anyFlow.
takesome:(ms, hs, hs) nondeterm anyFlow.
move:(hs, hs) nondeterm anyFlow.
clauses
classInfo("main", "nimgame").
append([], Y, Y).
append([U|X], Y, [U|Z]) :- append(X, Y, Z).
takesome([_X], V, V).
takesome([_X, X1|Y], V, [[X1|Y]|V]).
takesome([_X|T], V, Y) :- takesome(T, V, Y).
goal mainExe::run(main::run).
10.1 Utilities 125
implement main
open core
domains
ms= integer*.
hs= ms*.
class predicates
append:(E*, E*, E*) nondeterm anyFlow.
takesome:(ms, hs, hs) nondeterm anyFlow.
move:(hs, hs) nondeterm anyFlow.
winningMove:(hs, hs) nondeterm (i, o).
clauses
classInfo("main", "nimgame").
append([], Y, Y).
append([U|X], Y, [U|Z]) :- append(X, Y, Z).
takesome([_X], V, V).
takesome([_X, X1|Y], V, [[X1|Y]|V]).
takesome([_X|T], V, Y) :- takesome(T, V, Y).
goal mainExe::run(main::run).
If there is a winning strategy, this amazing one line predicate will find it!
You will find below an example of a game against the computer. As you
can see, if there is a chance of winning, the machine will take it.
126 How to solve it in Prolog
[[1,1,1],[1,1]]
Your move: [[1], [1,1]]
My move: [[1],[1]]
Your move: [[1]]
My move: []
I win
To implement the final program, you will create a class for the game itself.
Let the name of this class be nim. Then, the main program is given below.
% File: main.pro
implement main
open core, stdio
clauses
classInfo("main", "nimgame-1.0").
goal mainExe::run(main::run).
The class interface exports the method game/1, and the domains that
you need to describe a game position. It is defined below.
% File: nim.cl
class nim
open core
domains
ms= integer*.
hs= ms*.
predicates
classInfo : core::classInfo.
game:(hs) determ (i).
end class nim
10.1 Utilities 127
% File:nim.pro
implement nim
open core, stdio
class predicates
append:(hs, hs, hs) nondeterm anyFlow.
takesome:(ms, hs, hs) nondeterm anyFlow.
move:(hs, hs) nondeterm anyFlow.
winningMove:(hs, hs) nondeterm (i, o).
iwin:(hs) determ (i).
youwin:(hs, hs) determ (i, o).
clauses
classInfo("nim", "1.0").
append([], Y, Y).
append([U|X], Y, [U|Z]) :- append(X, Y, Z).
takesome([_X], V, V).
takesome([_X, X1|Y], V, [[X1|Y]|V]).
takesome([_X|T], V, Y) :- takesome(T, V, Y).
The strategy used in this very simple program is called minmax algorithm.
The algorithm can be applied to any game of two contenders, where both
have perfect knowledge of the game state. Examples of such games: Chess,
Go, Checkers, Tic-Tac-Toe, and Nim. It can also be used to control robots;
in fact, there is a kind of filter based on the minmax algorithm that is similar
to Kalman’s. In the minmax strategy, the states of the game are organized in
a tree, that is called search tree. When it is the computer’s turn, it generates
states of minimum value for the opponent. On the other hand, it tries to
follow tree branches that leads to a winning position. In a simple game like
Nim, the computer is able to find a path to sure victory. In more complex
games, like chess, this is not always possible; one must interrupt the search
before being sure of winning the game. In this case, game strategists design
functions that assign values to a position even when it is not a sure win, or
defeat.
Chapter 11
Facts
class predicates
addItem:(volume).
clauses
addItem(V) :-
num := num+1,
assert(item(V, num)).
item(journal("AI", "AldinePress")).
creates the fact database bib, with a variable num and a predicate item/2;
the predicate is nondeterm, and the variable is single.
129
130 Facts
The domain volume, that will provide typing to the records of this small
database, is defined as
domains
name= string.
author= n1(name); n2(name, name); etal(name).
publisher= string.
title= string.
volume= journal(title, publisher); book(title, author).
The program below shows how to define facts, and how to save the fact
database to a file.
% File main.pro
implement main
open core
domains
name= string.
author= n1(name); n2(name, name); etal(name).
publisher= string.
title= string.
volume= journal(title, publisher); book(title, author).
class predicates
addItem:(volume).
prtDataBase:().
clauses
classInfo("main", "facttest").
clauses
run():-
console::init(),
addItem(journal("AI in focus", "MIT Press")),
addItem(book( "Databases in Prolog",
n1("Wellesley Barros"))),
file::save("bibliography.fac", bib),
prtDataBase().
end implement main
goal
mainExe::run(main::run).
After creating the database and saving it to a file, you can use it in another
program. To test this possibility, create a console project factread, and add
the following program to it:
% File: main.pro
implement main
open core
domains
name= string.
author= n1(name); n2(name, name); etal(name).
publisher= string.
title= string.
volume= journal(title, publisher); book(title, author).
class predicates
prtDataBase:().
clauses
classInfo("main", "factread").
clauses
run():- console::init(),
file::consult("bibliography.fac", bib),
prtDataBase().
end implement main
goal
mainExe::run(main::run).
You need to transfer the file
bibliography.fac
created by facttest to factread\exe. Then, run the program
factread.exe.
You will see that the program will read the database, and print it.
%File: main.pro
implement main
open core
constants
className = "main".
classVersion = "filetest".
clauses
classInfo(className, classVersion).
clauses
run():-
console::init(),
Str= file::readString("test.txt", Unicode),
stdio::write(Str),
S_Upper= string::toUpperCase(Str),
file::writeString("upper.txt", S_Upper, Unicode),
succeed(). % place your own code here
end implement main
goal
mainExe::run(main::run).
11.2 Constants
In order to learn how to use constants, let us build a form with a task menu;
Visual Prolog, and most other languages as well, represent menu options by
integer numbers; this representation is fine for the computer, but not as good
for people, who will analyze the program. Therefore, Visual Prolog represent
these these handles to menu options by constants with meaningful names.
134 Facts
By the way, janua is the Latin word for door; window is a small door,
or janella; the door of the year is mensis januarius, or january.
• Create a New Form in New Package; let the form be named txtWin.
From the Properties-dialog, add a TaskMenu to txtWin.
clauses
onMenuItem(_Source, M) :-
Item= resourceIdentifiers::id_help_contents,
M= Item, !,
stdio::write("Hello").
onMenuItem(_Source, _M) .
Enable the File/New option of the Taskmenu, and add the snippet
clauses
onFileNew(S, _MenuTag) :-
X= txtWin::new(S), X:show().
to TaskWindow.win/CodeExpert/Menu/TaskMenu/id_file/id_file_new.
Chapter 12
% File:account.i
interface account
open core
predicates
ssN:(string SocialSecurity) procedure (o).
setSocialSecurity:(string SSN) procedure(i).
deposit:(real).
end interface account
Class account will produce a new account object whenever you call the
method
A= account::new()
You can send messages to this object. For example, you may set the
SocialSecurity Number of the customer:
A:setSocialSecurity("AA345"),
135
136 Classes and Objects
Afterwards, you may retrieve this very same Social Security Number.
A:ssN(SocialSecurity),
% File: account.pro
implement account
open core
facts - customer
funds:real := 3.0.
personalData:(string Name,
string SocialSecurity) single.
clauses
classInfo("account", "1.0").
personalData("", "").
run():- console::init(),
A= account::new(), A:setSocialSecurity("AA345"),
A:ssN(SocialSecurity),
stdio::write(SocialSecurity), stdio::nl.
12.1 Object facts 137
are individual accounts. The state of the account (our object) specifies the
funds, the name of the account holder, his/her name, his/her Social Security
Number, the password, etc. This state is represented by a fact, and a variable:
facts - customer
funds:real := 3.0.
personalData:(string Name,string SocialSecurity) single.
Interface predicates are used to access the facts that give the state of the
object.
% File:account.i
interface account
open core
predicates
ssN:(string SocialSecurity) procedure (o).
setSocialSecurity:(string SSN) procedure(i).
deposit:(real).
end interface account
Giuseppe Peano
Giuseppe Peano was one of the greatest mathematicians of all time. He wrote
a small book whose title is
Arithmetices Principia Novo Methodo Exposita
From this, you can figure out how good he was. Not many people write things
in Latin at this time and age, and find readers. Gauss was one of these few
Mathematicians that could afford writing in Latin. Peano was another one.
Of course, Gauss wrote mainly in Latin. Peano wrote mostly in French,
the language of science at the beginning of the last century, and in Italian,
his mother tongue. The only thing he wrote in Latin was the Arithmetices
Principia, a very short book of 29 pages. There, he proposes to the world
the modern notation for formal logics, a branch of Mathematics that gave
origin to Prolog. He also talks about other things, like number theory, and
recursion. I strongly suggest that you learn Latin, and read Peano’s book.
• Create a New in New Package form: canvas (section 2.1). The name
of the form is canvas, and it is at the root of the peano project.
139
140 Giuseppe Peano
• Create a class named curve: Go to the project window, select the folder
canvas, choose the option File/New in Existing Package from the task
window, and create the class. Untick the Create objects box. Build the
application.
clauses
onFileNew(S, _MenuTag) :-
X= canvas::new(S),
X:show().
to TaskWindow.win/CodeExpert/Menu/TaskMnu/id_file/id_file_new.
• Edit curve.cl and curve.pro as in figure 13.1. Edit files turtle.cl and
turtle.pro, as shown in figures 13.2 and 13.3. Build the application.
clauses
onPaint(S, _Rectangle, _GDIObject) :-
W= S:getVPIWindow(),
curve::drawCurve(W).
Build and execute the project. Whenever you choose the option File/New
from the application menu, a window with a star will pop up on the screen.
% File: curve.cl
class curve
open core, vpiDomains
predicates
classInfo : core::classInfo.
drawCurve:(windowHandle).
end class curve
%File: curve.pro
implement curve
open core, vpi, vpiDomains, math
class predicates
star:(windowHandle, integer, real, integer) procedure (i, i, i, i).
clauses
classInfo("plotter/curve", "1.0").
A turtle has two state variables, i.e., its position and the direction of its
movement. These state variables are given by a fact:
class facts
turtle:(pnt, real) single.
clauses
turtle(pnt(200, 200), -pi/2).
...
X2 = X1 + L × cos(F ) (13.1)
Y2 = Y1 + L × sin(F ) (13.2)
After getting the new position P2= pnt(X2, Y2), forward/2 uses
drawline(Win, P1, P2)
to connect point P1 to P2. Finally, information about the new position of
the turtle is asserted into the database:
assert(turtle(P2, Facing))
The predicate move/1 is similar to forward/2, but does not connect the new
position of the turtle to the old one. Predicates right/1 and left/1 turn
the turtle to the right and to the left, respectively. Their implementations
are straightforward.
13.3 Recursion
Peano’s recursion postulate can be stated as follows: In order to prove that
a predicate is true for any natural number,
• Prove that it is true for 0.
% File: turtle.cl
class turtle
open core, vpiDomains
predicates
classInfo : core::classInfo.
forward:(windowHandle, integer) procedure.
move:(integer) procedure.
right:(real) procedure.
left:(real) procedure.
end class turtle
% File: turtle.pro
implement turtle
open core, vpiDomains, vpi, math
class facts
turtle:(pnt, real) single.
clauses
classInfo("turtleGraphics/turtle", "1.0").
2. Every word has the form of the Latin stem or root or radical.
implement curve
open core, vpi, vpiDomains, math
class predicates
peano:(windowHandle, integer, real, integer) procedure (i, i, i, i).
clauses
classInfo("plotter/curve", "1.0").
terms are either Greek or Greco-Latin. A few necessary classical Latin words
without international equivalents are a part of the vocabulary, to wit:
Latino English Example
Latino should not have any kind of inflection. The plural is formed by using
the word plure as in:
Plure vocabulo in Latino es in usu in Anglo
The words of Latino are in use in English.
However, Peano was a democrat, and since most people wanted to form the
plural with s, he gave his approval to an optional s-plural. Adjectives can be
created from nouns with help of preposition de:
• de fratre = fraternal
editorControl
package to the project root. To this end, choose the option File/Add
from the task menu, browse for the Visual Prolog installation directory,
and select the file
\pfc\gui\controls\editorControl\editorcontrol.pack
%File: grammar.cl
class grammar
open core
predicates
classInfo : core::classInfo.
dic:(string).
end class grammar
%File: grammar.pro
implement grammar
open core
clauses
classInfo( "editor/grammar", "1.0").
dic(S) :- stdio::write(S), stdio::nl.
end implement grammar
Enable the File/New option, of the application task menu. Add the snippet
clauses
onFileNew(S, _MenuTag) :- X= txtWin::new(S), X:show().
to ProjWin/TaskWindow.win/Code Expert/Menu/TaskMenu/id_file/id_file_new.
clauses
onHelpClick(_Source) = button::defaultAction :-
editorControl_ctl:getSelection(Start, End),
Txt=editorControl_ctl:getText(Start, End),
grammar::dic(Txt).
class predicates
tryGetFileContent:(
string FileName,
string InputStr,
string UpdatedFileName) procedure (i, o, o).
clauses
tryGetFileContent( "", "", "LsF.txt") :- !.
tryGetFileContent(FileName, InputStr, FileName) :-
trap(file::existFile(FileName), _TraceId, fail), !,
InputStr= file::readString(FileName, _).
tryGetFileContent(FileName, "", FileName).
new(Parent):- formWindow::new(Parent),
generatedInitialize(),
PossibleName= "LsF.txt",
tryGetFileContent(PossibleName, InputStr, _FileName), !,
XX=string::concat("Here: ", InputStr),
editorControl_ctl:pasteStr(XX).
If you open a new txtWin from the application, the above text will appear
in the window. If you select part of the text, and press the button Help from
the TaskMenu of the application, you will echo the selected text on the
message window. The next step is to add dictionary entries to the selected
text, before writing it on the message window (see figure 13.6). To this end,
we start by breaking down the selection into a list of words. In figure 13.6,
this is done by the following predicate:
strTok:(string, string_list) procedure (i, o).
Natural Language Processing (NLP) is the most difficult branch of Computer
Science, and outside the scope of this doc. If you want to learn the state of
the art, you can start by adding more entries to the program of figure 13.6.
13.7 Examples
The examples of this chapter are in directories peano, and LsF.
150 Giuseppe Peano
%File: grammar.pro
implement grammar
open core, string
class predicates
strTok:(string, string_list) procedure (i, o).
addExplanation:(string, string) procedure (i, o).
class facts
entry:(string, string).
clauses
classInfo( "editor/grammar", "1.0").
entry("que", "that").
entry("sine", "without").
entry("es", "am, are, is, was, were, to be").
entry("de", "of").
entry("et", "and").
L-Systems
• Add a class to aristid: draw. Untick the Create Objects box. Build
the project to insert the class into the project tree.
clauses
onFileNew(S, _MenuTag) :-
X= canvas::new(S), X:show().
to TaskWindow.win/CodeExpert/Menu/TaskMenu/id_file/id_file_new.
151
152 L-Systems
% File: draw.pro
implement draw
open core, vpiDomains, vpi, math
clauses
classInfo("plotter/draw", "1.0").
domains
command = t(commandList); f(integer);
r(integer); m.
commandList= command*.
class facts
pos:(real Delta, real Turn) single.
grammar:(commandList) single.
class predicates
move:(windowHandle, pnt, real, commandList)
procedure (i, i, i, i).
derive:(commandList, commandList)
procedure (i, o).
mv:(windowHandle, pnt, real, command, pnt, real)
procedure (i,i, i, i, o, o).
iter:(integer, commandList, commandList) procedure (i, i, o).
clauses
pos(4.0, 0.1745329).
grammar([f(1)]).
iter(0, S, S) :- !.
iter(I, InTree, OutTree) :- derive(InTree, S),
iter(I-1, S, OutTree).
14.1 Class draw 153
derive([], []) :- !.
derive([f(0)|Rest], [f(0),f(0)|S]) :- !,
derive(Rest, S).
derive([f(_)|Rest],
[f(0), t([r(1),f(1)]), f(0),
t([r(-1),f(1)]), r(1), f(1)|S]) :- !,
derive(Rest, S).
derive([t(Branches)|Rest], [t(B)|S]) :- !,
derive(Branches, B),
derive(Rest, S).
derive([X|Rest], [X|S]) :- derive(Rest, S).
tree(Win) :- grammar(Commands),
iter(5, Commands, C),
Facing= -pi/2,
Point= pnt(100, 250),
move(Win, Point, Facing, C).
end implement draw
154 L-Systems
clauses
onPaint(S, _Rectangle, _GDIObject) :-
draw::tree(S:getVPIWindow()).
14.2 Examples
The example of this chapter is in directory Lsys.
Chapter 15
Games
In this chapter, you will learn how to implement games in Visual Prolog. Of
course, a good game is like a good poem. It is not enough to learn Russian,
and versification rules in order to write like Pushkin. A poet, besides knowing
the language in which he will write, needs imagination, creativity, etc. The
same goes for a good game writer. You will need a lot of creativity to come
up with something like Tetris or Space Invaders. I used these examples to
show you that good games do not depend on fancy graphics or sound effects.
The important features are suspense, continued tension and a sense of reward
for good performance.
Once, a young man asked Lope de Vega how to write a poem. The answer
was: It is simple: Uppercase in the beginning of each verse, and a rhyme at
the end. “What do I put in the middle?”, asked the boy. In the middle, “hay
que poner talento”, stated Lope de Vega.
This lesson has to do with a few techniques, like uppercase letters and
rhymes. To get a good game, you will need to add talent to the recipe.
The game you will implement is simple. There is a worm moving fast on
the screen. The player must change its direction in order to prevent it from
hitting obstacles and walls. You will need to implement the following items:
1. A window, where the worm will move. The window must have four
buttons, to control the movement of the worm.
2. A state class, that describes the direction of the movement, and the
position of the worm. It exports the predicates mov/0, to change the
position of the worm, and turn/2, that can be used to turn it around.
155
156 Games
window. This would make the screen flicker. It must build a picture,
and then draw it onto the screen.
We know what to do; let us do it. I have avoided creating objects until
now. In fact, every time that you created a class, you were recommended
to untick the Creates Objects radio button. However, if we rigidly maintain
this state of mind, our game will have a very nasty property. If you lose,
quit the current game, and try to start another one, you will find out that
the new game is not in the initial state. In fact, it is in the state where you
lost the old one. The problem is that the variables used to describe a state
have memory that lasts from one form to another. Objects were invented to
prevent this kind of nasty behavior. Therefore, let us take this very good
opportunity to learn how to create objects, and deal with object states.
• Create a form: lawn. You will add four buttons to the lawn form; refer
to figure 15.1 for the design. In the Properties dialog, it is a good idea
to change the names of the buttons from pushButton_ctl to up_ctl,
down_ctl, left_ctl, and right_ctl. Build the application in order
to include the form. Then enable File/New, and add
clauses
onFileNew(S, _MenuTag) :- X= lawn::new(S), X:show().
to ProjWin/Code Expert/Menu/TaskMenu/id_file/id_file_new.
%File: click.cl
class click
open core, vpiDomains
predicates
bip:(window).
kill:().
end class click
% File:click.pro
implement click
open core
class facts
tm:vpiDomains::timerId := null.
clauses
bip(W) :- tm := W:timerSet(500).
kill() :- vpi::timerKill(tm).
end implement click
• Add
clauses
onDestroy(_Source) :- click::kill().
• Add
class facts
stt:objstate := objstate::new().
clauses
onShow(Parent, _CreationData) :- stt := objstate::new(),
stt:init(), click::bip(Parent).
clauses
onDownClick(_S) = button::defaultAction() :-
stt:turn(0, 10).
clauses
onLeftClick(_S) = button::defaultAction() :-
stt:turn(-10, 0).
clauses
onRightClick(_S) = button::defaultAction() :-
stt:turn(10, 0).
clauses
onUpClick(_S) = button::defaultAction() :-
stt:turn(0, -10).
• clauses
onTimer(_Source, _TimerID) :- stt:mov(),
R= rct(10, 10, 210, 210),
invalidate(R).
to TimeListener from the Properties-dialog of lawn.frm.
159
• Add
clauses
onPaint(_Source, _Rectangle, GDIObject) :-
draw::snapshot(GDIObject, stt).
Build and run the program. After choosing the option File/New, a win-
dow with a worm pops up. You can control the worm using the four buttons.
It is up to you to improve this game. You can add obstacles, like walls.
You can also put little apples for the worm to eat. Finally, you can write
a 3D game. The 3D game is very simple indeed. The segments have three
coordinates, and you must find their projections on the plane before drawing
them. If you info-fish using the Internet, you certainly will find formulae to
calculate the perspective projection of a point. Alex Soares wrote the original
game on which I based this tutorial. His game is in 3D, in Visual Prolog 5.2,
using Direct X, a library that has a lot of resources for 3D games.
Let us keep to our study of drawing predicates. If you draw figures at
every tick of the clock, you will have created an animation. However, if
you perform the drawings directly on a visible window, the animation will
blink, which is not very nice. The solution for this problem is to perform the
drawing on a hidden window, and transfer it to the visible window only after
it is ready for showing. The predicate
W= pictOpen(Rectangle)
opens a hidden window for you to draw things without worrying about a
blinking animation. The predicate
Pict= pictClose(W)
creates a picture from the contents of a hidden window; at the same time, it
closes the window. Finally, the predicate
pictDraw(Win, Pict, pnt(10, 10), rop_SrcCopy)
transfers the picture to a visible window. Here is an example of code con-
taining these three important predicates:
snapshot(Win, S) :- S:mov(), !,
Rectangle= rct(0, 0, 200, 200),
W= pictOpen(Rectangle), draw(W, S),
Pict= pictClose(W),
Win:pictDraw(Pict, pnt(10, 10), rop_SrcCopy).
160 Games
%File draw.cl
class draw
open core, vpiDomains
predicates
classInfo : core::classInfo.
snapshot:(windowGDI Win, objstate).
end class draw
%File draw.pro
implement draw
open core, vpiDomains, vpi
class predicates
draw:(windowHandle, objstate) procedure.
clauses
classInfo("playground/draw", "1.0").
draw(W, S) :- S:segm(Rectangle),
vpi::drawEllipse(W, Rectangle), fail.
draw(_W, _S).
snapshot(Win, S) :- S:mov(), !,
Rectangle= rct(0, 0, 200, 200),
W= pictOpen(Rectangle),
draw(W, S),
Pict= pictClose(W),
Win:pictDraw(Pict, pnt(10, 10), rop_SrcCopy).
end implement draw
%File:objstate.cl
class objstate : objstate
end class objstate
%File:objstate.i
interface objstate
open core, vpiDomains
predicates
init:().
turn:(integer, integer).
mov:().
segm:(rct) nondeterm (o).
end interface objstate
%File:objstate.pro
implement objstate
open core, vpiDomains
facts
w:(integer, pnt) nondeterm. % worm
d:(integer, integer) single. % direction
clauses
init() :- assert(w(1, pnt(110, 100))),
assert(w(2, pnt(120, 100))), assert(w(3, pnt(130, 100))),
assert(w(4, pnt(140, 100))), assert(w(5, pnt(140, 100))).
d(10, 0).
A class fact is shared by all objects of the class. This means that, if an object
predicate (a predicate declared in the interface) changes a fact, it is changed
for all objects of the class. Therefore, if you had kept the state of the worm
in class facts, the game would have the following nasty feature:
Nasty Game Property — Whenever the player loses a worm, quits the
current game, and plays another one, he will find that the new worm is not
in the initial state. In fact, it is in the state where he lost the old one.
The problem is that the class facts used to describe the state of the worm
have class memory that lasts from one form to another, from one worm
object to another. The solution is simple: Declare the facts without adding
the keyword class to the head of the declaration; then there will be a private
set of facts for each object. Here is how to declare object facts:
facts
w:(integer, pnt) nondeterm. % worm
d:(integer, integer) single. % direction
clauses
init() :- assert(w(1, pnt(110, 100))),
assert(w(2, pnt(120, 100))), assert(w(3, pnt(130, 100))),
assert(w(4, pnt(140, 100))), assert(w(5, pnt(140, 100))).
d(10, 0).
Animation
Please, take a look at chapter 8, page 87, if you do not remember how to use
the event painting. You will also need to use masks to create figures with a
transparent background; this theme is also explained in chapter 8. Finally,
you may want to recall to memory the use of GDIObject, which is dealt with
in section 3.2, page 28, where you will find material about invalidating a
rectangle for painting as well.
• Create a project.
In this project, you will animate the famous rattlesnake that appears
on the John Paul Jones’ Don’t tread on me flag.
• Create class click to hold clock events. Untick Creates Objects. Use
the code given in figure 16.1 to define and implement click.
onFileNew(S, _MenuTag) :-
F= canvas::new(S), F:show(), click::bip(F).
to TaskWindow.win/CodeExpert/Menu/TaskMenu/id_file/id_file_new.
163
164 Animation
%File: click.cl
class click
open core
predicates
bip:(window).
kill:(window).
end class click
%File: click.pro
implement click
open core
class facts
tm:vpiDomains::timerId := null.
clauses
bip(W) :- tm := W:timerSet(1000).
kill(W) :- W:timerKill(tm).
end implement click
16.1 dopaint
Class dopaint will handle painting, and works more or less like the dopaint
method in Java. Create a class, name it dopaint, and untick the Creates
Objects option. Here is the class definition:
%File: dopaint.cl
class dopaint
open core
predicates
classInfo : core::classInfo.
draw:(windowGDI).
invalidrectangle:(vpiDomains::rct) procedure (o).
end class dopaint
implement dopaint
open core, vpiDomains
constants
className = "snake/snakestate".
classVersion = "".
class facts
yesno:integer := 0.
class predicates
flipflop:(picture Picture,
picture Mask) determ (o, o).
clauses
classInfo(className, classVersion).
draw(W) :-
P= vpi::pictLoad("figs\\frogs.bmp"),
W:pictDraw(P, pnt(10, 10), rop_SrcCopy),
flipflop(Snake, Mask), !,
W:pictDraw(Mask, pnt(40, 50), rop_SrcAnd),
W:pictDraw(Snake, pnt(40, 50), rop_SrcInvert).
draw(_).
onDestroy(W) :- click::kill(W).
onTimer(_Source, _TimerID) :-
dopaint::invalidRectangle(R),
invalidate(R).
P= vpi::pictLoad("figs\\frogs.bmp"),
Text Editor
In this chapter, you will learn how to use the editControl class to create a
text editor.
1
Yale invented this kind of key, that is very popular nowadays. Legend has it that soon
after his invention, Houdini was able to break through the new device.
167
168 Text Editor
predicates
onSaveClick : button::clickResponder.
clauses
onSaveClick(_Source) = button::defaultAction() :-
Txt= editorControl_ctl:getEntireText(),
FName= vpiCommonDialogs::getFileName("*.*",
["Text", "*.txt"],
"Save", [], ".", _X), !,
file::writeString(FName, Txt).
onSaveClick(_Source) = button::defaultAction().
predicates
onLoadClick : button::clickResponder.
clauses
onLoadClick(_Source) = button::defaultAction() :-
FName= vpiCommonDialogs::getFileName("*.*",
["Text", "*.txt"],
"Load", [], ".", _X),
file::existFile(FName),
!,
Str= file::readString(FName, _IsUnicodeFile),
editorControl_ctl:pasteStr(1, Str).
onLoadClick(_Source) = button::defaultAction().
Printing
In the previous chapter, you learned how to build a text editor. In this
chapter, you will acquire introductory notions of printing.
• Create a project.
• On the Project Tree, right click on the TaskWindow.win, and choose the
option Code Expert from the floating menu, as in figure 2.6, page 21.
The Dialog and Window Expert will pop up. Open the folders Menu,
TaskMenu, and id_file, as shown below.
Select the branch id_file_new, and press the button Add. Then, dou-
ble click on the newly created id_file_new->OnFileNew branch; finally,
replace the onFileNew(_Source, _MenuTag) prototype with the snip-
pet given below.
171
172 Printing
clauses
onFileNew(_Source, _MenuTag) :-
PW=vpi::printStartJob("Recoreco"),
_HRES = vpi::winGetAttrVal(PW,attr_printer_hres),
VRES = vpi::winGetAttrVal(PW,attr_printer_vres),
V_SCR_RES=vpi::winGetAttrVal(PW,attr_screen_vres),
FNT=vpi::fontCreate(ff_Fixed,[], VRES*40 div V_SCR_RES),
vpi::winSetFont(PW,FNT),
vpi::printStartPage(PW),
vpi::drawText(PW, 100, 200, "Before the sunset!"),
vpi::printEndPage(PW),
vpi::printEndJob(PW).
The beauty of Visual Prolog printing scheme is that you deal with the printer
as if it were a normal graphic window. In fact, the first thing you do, in order
to print something, is to open a printer job window.
PW=vpi::printStartJob("Recoreco")
Once you have a window, you can use it to obtain information about the
printer resolution.
_HRES= vpi::winGetAttrVal(PW,attr_printer_hres),
VRES= vpi::winGetAttrVal(PW,attr_printer_vres),
V_SCR_RES=vpi::winGetAttrVal(PW,attr_screen_vres),
By trial and error, and using information about the printer resolution, you
can define a font, that looks good on print.
Finally, you can use any drawing predicate to produce a fine printed page.
Name: tab_example
UI Stategy: Object GUI
pfc\gui\controls\tabControl\tabControl.pack
• Build the application, in order to insert snippets for events. Add the fol-
lowing snippet to the ClickResponder belonging to button:yourname_ctl:
clauses
onYournameClick(_Source) = button::defaultAction :-
Name= edit_ctl:getText(),
Ans= string::concat("Hello, ", Name, "!\n"),
stdio::write(Ans).
173
174 Tab forms and other goodies
• Build the application, in order to insert code for events. Add the
following snippet to the ClickResponder belonging to button:fib_ctl:
class predicates
fibo:(integer, integer) procedure (i, o).
clauses
fibo(N, F) :-
if N<2 then F=1
else
fibo(N-1, F1), fibo(N-2, F2),
F= F1+F2
end if.
predicates
onFibClick : button::clickResponder.
clauses
onFibClick(_Source) = button::defaultAction :-
Num= edit_ctl:getText(),
I= toTerm(Num), fibo(I, F),
Ans= string::format("fibo(%d)= %d", I, F),
edit_ctl:setText(Ans).
• Go to the file tabs.pro, which you can access by clicking the corre-
sponding branch of the project tree, and replace the clause
clauses
new(Parent):-
formWindow::new(Parent),
generatedInitialize().
clauses
new(Parent):-
formWindow::new(Parent),
generatedInitialize(),
%
Page1 = tabPage::new(),
Tab1 = helloTab::new(Page1:getContainerControl()),
Page1:setText(Tab1:getText()),
tabControl_ctl:addPage(Page1),
Page2 = tabPage::new(),
Tab2 = fibTab::new(Page2:getContainerControl()),
Page2:setText(Tab2:getText()),
tabControl_ctl:addPage(Page2),
succeed.
clauses
onFileNew(S, _MenuTag) :-
W= tabs::new(S), W:show().
to TaskWindow.win/CodeExpert/Menu/TaskMenu/id_file/id_file_new.
19.2 Botany
Your interest in Botany may be none. However, there are many reasons for
cultivating this important branch of science. Let us enumerate them.
Saving the world. Global warming is the most serious challenge facing us
today. To protect the planet for future generations, we must reduce
19.2 Botany 177
I could say that Computer Scientist and Botanists can understand each
other, at least in the realms of computer graphics.
Orchids. These exotic plants are among the most beautiful creations of
Nature. They are objects of a cult that owes nothing to wine, or French
cuisine. By the way, the Serbian Nero Wold was fond of wine, French
cuisine, and orchids. If you read Wolf’s books, you will notice that
the suspense is not created by how Wolf solves mysteries, but by how
he will get the money necessary to maintain his expensive cults. He
hires Fritz Brenner, an exceptionally talented Swiss cook who prepares
and serves all his meals, and speaks French when addressing him; he
also hires Theodore Horstmann, an orchid expert who takes care of
178 Tab forms and other goodies
the plant rooms; finally, the most important of his employers is Archer
Goodwin, who works as a detective to earn the money necessary to
keep the expensive habits of his overweight boss.
One of the most interesting features of Botany, as compared with other sci-
ences, it that there is a special kind of Latin internationally used by botanists
for the description, and naming of plants. Since many Botanists do not know
enough Latin, a program to help them deal with this difficult language would
give a head start to the goals of saving the world, cultivating orchids, or cre-
ating cool computer graphics. In this section, I will see how such a program
can be written. Besides this, you will learn how to work with tab-controls,
and listEdit-controls.
The program to teach Botanical Latin is given in the skinner directory
of the folder containing the examples that accompany this document. The
program contains three tab-controls:
• Pronunciation tab. In order to teach you how to pronounce Latin, I
used excerpts from a very famous book called Philosophiae Naturalis
Principia Mathematica; the author of the book is a certain Isaac New-
ton; I am not sure whether you heard about this scientist, but English
scholars consider him to be the greatest scientist who ever lived. They
certainly are exaggerating; notwithstanding Newton was good enough
to write in Latin, and find readers; as you remember, when a scientist
is very good, one says that he could have written in Latin, that he
would find readers; Newton, Peano, Gauss, and others took this saying
literally, and wrote in Latin. In this tab, my son, who speaks fluent
Latin, English, Ancient Greek, and Chinese, will read Newton for you.
• Declensiontab. There are languages where nouns change to show their
function in a sentence. Latin, Sanskrit, German, Russian, Polish are
among these languages. Therefore, even if you do not think that Botan-
ical Latin is useful enough to merit your attention, you can use the
underlying structure of the Latin program to write programs to teach
Russian, or German.
• Verbtab. Declension is not the only feature that Latin shares with
German, Russian, and Ancient Greek. Latin has also a very complex
verbal system, that it passed down to the so called Romance Languages
(Spanish, Italian, Portuguese, Rumanian, French, Galician, and Cata-
lan).
If you want to learn one of the Romance languages, you can adapt the sup-
port provided for Latin, in order to study Spanish, or French verbs. By the
19.2 Botany 179
way, Spanish and Portuguese are much closer to Latin than other Romance
languages. The Latin verbal system was transmitted to Spanish and Por-
tuguese almost without alteration. Consider the verb amare:
Latin Spanish
Present Imperfect Present Imperfect
amo amabam amo amaba
amas amabas amas amabas
amat amabat ama amaba
amamus amabamus amamos amábamos
Now, let us concentrate our efforts on the design of the program. I suggest
that you place all tab controls in the same package; in the example, the pack-
age is called tabPackage. In order to create a tab control, choose the option
New in New Package from the IDE task menu; select the option Control from
the left hand pane; press the button Create, and mount the following layout:
The verbTab control contains a listEdit control, that will hold the verb drilling
list; two read-only edit controls, to show the tense, and mood that the student
will be practicing; and a button, that will compare the student’s answer with
a template. The main reason behind offering you this application is to show
180 Tab forms and other goodies
how to handle the listEdit control; therefore, let us take a close look at the
methods associated with this important kind of control. From the Properties-
dialog, you must press the Events-button, and add the following snippet
predicates
onShow : window::showListener.
clauses
onShow(_Source, _Data) :-
VerbList= listVerb_ctl : tryGetVpiWindow(),
verbs::allVerbs(L),
vpi::lboxAdd(VerbList, L),
vpi::lboxSetSel(VerbList, 0, 1), !,
verbs:: get_verb_state(Tense, Mood),
mood_ctl:setText(Mood),
tense_ctl:setText(Tense).
onShow(_Source, _Data).
to the showListener. The snippet shows you how to add a list of options to
the control; it shows also how to select an entry:
From the Properties-dialog, you must also add the code from figure 19.1 to
Events-onCheckClick; then you will learn a few other listEdit methods:
IV= vpi::lboxGetSelIndex(VerbList)
C= vpi::lboxCountAll(VerbList)
19.2 Botany 181
onCheckClick(_Source) = button::defaultAction :-
Txt= editorControl_ctl:getEntireText(),
Verb= listVerb_ctl:getText(),
VerbList= listVerb_ctl : tryGetVpiWindow(),
IV= vpi::lboxGetSelIndex(VerbList), !,
Mood= mood_ctl:getText(),
Tense= tense_ctl:getText(),
verbs::pres_root_conj(Verb, Tense, Mood, L),
if template::check_it(Txt, L, Ans) then
editorControl_ctl:pasteStr(" "),
vpi::lboxDelete (VerbList, IV) ,
C= vpi::lboxCountAll(VerbList),
if C= 0 then
verbs::next_verb_state(),
verbs::get_verb_state(NewTense, NewMood),
mood_ctl:setText(NewMood),
tense_ctl:setText(NewTense),
verbs::allVerbs(NewList),
vpi::lboxAdd(VerbList, NewList)
else
succeed()
end if,
vpi::lboxSetSel(VerbList, 0, 1)
else
template::check_present(Txt, L, Ans),
editorControl_ctl:pasteStr(Ans)
end if.
onCheckClick(_Source) = button::defaultAction.
Now, let us declare a Prolog predicate that will be called whenever you want
to trigger playSound, and a few necessary constants.
class predicates
playSound : (string Sound,
pointer HModule,
soundFlag SoundFlag)
-> booleanInt Success
language apicall.
constants
snd_sync : soundFlag = 0x0000.
/* play synchronously (default) */
snd_async : soundFlag = 0x0001.
/* play asynchronously */
snd_filename : soundFlag = 0x00020000.
/* name is file name */
snd_purge : soundFlag = 0x0040.
/* purge non-static events for task */
snd_application : soundFlag = 0x0080.
/* look for application specific association */
When using a computer to write Chinese, one usually express his/her ideas
in Pinyin, and a Word Processing Editor translates Pinyin into Simplified,
or Traditional Ideograms. Of course, I do not know Chinese, but my son
knows. Therefore, at my request, he has used the multiMedia_native class
to add sound to a couple of menu entries. You will find the snippet that he
has used below; browse the multimedia class to uncover additional resources.
predicates
onFilePronunciationHowAreYou : window::menuItemListener.
clauses
onFilePronunciationHowAreYou(_Source, _MenuTag) :-
_ = multiMedia_native::sndPlaySound("howareu.wav",
multiMedia_native::snd_ASync ).
editControl, and
a Pinyin2Ideogram button.
% File: ideograms.cl
class ideograms
open core
predicates
classInfo : core::classInfo.
ideograms:(string, string) procedure (i, o).
end class ideograms
184 Tab forms and other goodies
implement ideograms
open core
clauses
classInfo("chineseforms/ideograms", "1.0").
class facts - pideograms
pinyin:(string, string).
class predicates
pinyin2ideogram:(string, string) procedure (i, o).
converList:(string*, string) procedure (i, o).
clauses
pinyin("ni^", "\u4f60").
pinyin("ha^o", "\u597d").
pinyin("ma", "\u55ce").
pinyin("wo^", "\u6211").
pinyin("Ni^", "\u4f60").
converList([], "") :- !.
converList([P|Ps], Acc) :- pinyin2ideogram(P, Idea),
converList(Ps, Rest),
Acc= string::concat(Idea, Rest).
end implement ideograms
clauses
onPinyinClick(_Source) = button::defaultAction :-
Txt= editorControl_ctl:getEntireText(),
ideograms::ideograms(Txt, Ans),
editorControl_ctl:pasteStr(Ans).
predicates
onShow : window::showListener.
clauses
onShow(_Source, _Data) :-
editorControl_ctl:setFontDialog(), !.
onShow(_Source, _Data).
class predicates
test:(string) procedure (i).
clauses
test(Str) :-
regexp::search("-[0-9]+|[0-9]", Str, Pos, Len), !,
stdio::write(Pos), stdio::nl,
stdio::write(Len), stdio::nl.
test(Str) :- stdio::write("No integer found in ", Str),
stdio::nl.
clauses
run():-
console::init(),
test( "There were 99 small Romans"),
succeed(). % place your own code here
end implement main
goal
mainExe::run(main::run).
19.4 Regular expressions 187
article → [the]
noun → [iguana]
adjective → [pretty]
npr → article, adjective, noun
Any symbol that appears on the right hand side of an arrow is a non-terminal
symbol. Therefore, article, npr, noun, and adjective are non-terminal
symbols, i.e., symbols that the grammar defines. Symbols that appears only
on the right hand side of the rules are terminal symbols; e.g. [iguana]. The
rule for npr can be understood thus:
implement main
open core
class predicates
test:(string) procedure (i).
clauses
classInfo("main", "regexpr").
test(Str) :-
Ans= regexp::replace(Str, "<H1\\b[^>]*>(.*?)</H1>", "--\\1--"),
stdio::write(Ans), !.
run():- console::init(),
test( "<H1> Viatores in Roma </H1> etc.").
end implement main
goal
mainExe::run(main::run).
The example replaces an HTML tag with its contents. Anything between
the tags is captured into the first back reference. The question mark in the
regular expression makes sure that the star stops before the first closing tag
rather than before the last one.
19.5 A MIDI player 189
• Add a New form in a new package. Let the form name be midiPlayer.
Add a listEdit box to it, and two buttons: play_ctl and stop_ctl.
to the showListener.
190 Tab forms and other goodies
• Enable the File/New option of the application task menu. Then, add
the snippet
clauses
onFileNew(S, _MenuTag) :-
W= midiPlayer::new(S), W:show().
clauses
onPlayClick(_Source) = button::defaultAction :-
Null = null, Len = 300,
_= multiMedia_native::mciSendString(
string::createCopy("Close mid"),
string::create(300, " "), Len, Null),
Buffer =string::create(300, " "),
File= listSongs_ctl:getText(),
C= "open %s.mid type sequencer alias mid",
Command= string::format(C, File),
_= multiMedia_native::mciSendString(
Command, Buffer, Len, Null),
_= multiMedia_native::mciSendString(
"Play mid from 0",
Buffer, Len, Null),
_= vpi::processEvents().
• The final step is to add code to the button that will interrupt the music,
if you decide to do something else.
clauses
onStopClick(_Source) = button::defaultAction :-
Null= null,
_Z= multiMedia_native::mciSendString(
string::createCopy("Close mid"),
string::create(300, " "), 300, Null).
19.6 Midi format 191
clauses
onFileNew(S, _MenuTag) :-
W= midiform::new(S), W:show().
• Create a class with the name midi. You will find the class declaration
below, and the implementation on figure 19.3.
class midi
open core
predicates
classInfo : core::classInfo.
generate:(string, string*) procedure (i, i).
end class midi
In the examples, you will find a more refined and complete version of the
program listed in figure 19.3. The program of figure 19.3 represents a good
opportunity to learn how to write data to a file. The first step is to create
an output stream file:
P= outputStream_file::create(File, stream::binary())
Then you can write a list of bytes to the file by using the foreach construc-
tion, as you can see below.
foreach X= list::getMember_nd (MidiCode) do
P:write(X)
end foreach,
P:close().
From the point of view of a musician, the programs given in the examples are
grossly oversimplified. However, you can easily complete the missing steps.
For instance, you can add multiple tracks, improve the resolution, change
the instruments, etc.
India, bella mezcla de diosa y pantera, India, pretty mestiza of goddess and panther,
doncella desnuda que habita el Guairá. naked girl that lives in Guaira.
Arisca romanza curvó sus caderas Dance has bent her hips into the shape
copiando un recodo de azul Paraná. of the blue curves of the Parana river.
Bravea en las sienes su orgullo de plumas, On her temples shine the pride of feathers,
su lengua es salvaje panal de eirusu. her language is a wild honeycomb.
Collar de colmillos de tigres y pumas A necklace from teeth of tigers and pumas
enjoya a la musa de Ybytyruzú. is a jewel for the muse of Ybytyruz.
194 Tab forms and other goodies
implement midi
open core
class predicates
note:(string, unsigned8) procedure (i, o).
duration:(string, unsigned8) procedure (i, o).
tomidi:(string*, unsigned8*) procedure (i, o).
mSz:(integer, unsigned8, unsigned8, unsigned8, unsigned8)
procedure (i, o, o, o, o).
class facts
nt:(string, unsigned8).
clauses
classInfo("midi/midi", "1.0").
nt("C", 60). nt("D", 62). nt("E", 64). nt("F", 65). nt("G", 67).
nt("A", 69). nt("B", 71). nt("C5", 72). nt("D5", 74).
nt("C#", 61). nt("D#", 63). nt("F#", 66).
nt("G#", 68). nt("A#", 70). nt("Bb", 70).
Bugs
Due to bugs, the Visual Prolog compiler frequently returns error mes-
sages, instead of generating code and running the corresponding programs.
In fact, the Visual Prolog compiler gives more bug alerts than any other
language, except perhaps Clean. Students don’t appreciate this constant
nagging about errors, and seldom try to analyze the error messages, but be-
lieve me, you should be thankful to the designers of such a compiler that
gives you a chance to clean up all the stains that can mar your program. As
I have said, Visual Prolog is good at pinpointing bugs. Notwithstanding, if
you do not learn how to deal with its error messages, you will find debugging
a random walk that almost never leads to success. Therefore, in this chapter,
you will learn how to interpret messages from the compiler.
195
196 Bugs
implement main
open core
clauses
classInfo("main", "bugs").
class predicates
fact:(unsigned) -> unsigned.
clauses
fact(N) = F :- if N < 1 then F=1
else F=N*fact(N-1) end if.
run():- console::init(),
stdio::write("N: "),
N= stdio::read(),
stdio::write(fact(N)), stdio::nl.
end implement main
goal mainExe::run(main::run).
For instance, if you try to compile the program of figure 20.1, you will get
the following error message:
The error will disappear if you explicitly convert each input argument of
binum/3 to an unsigned integer; you must also convert the output of factorial
to an integer.
binum(N, P, R) :-
N1= convert(unsigned, N),
P1= convert(unsigned, P),
R = convert(integer, fact(N1) div (fact(P1)*fact(N1-P1))).
20.2 Non-procedure inside a procedure 197
implement main
open core
clauses
classInfo("main", "bugs").
class predicates
fact:(unsigned) -> unsigned.
binum:(integer, integer, integer) procedure (i, i, o).
clauses
fact(N) = F :- if N < 1 then F=1 else F=N*fact(N-1) end if.
run():- console::init(),
stdio::write("N: "), N= stdio::read(),
stdio::write("P: "), P= stdio::read(),
binum(N, P, R), stdio::write(R), stdio::nl.
end implement main
goal mainExe::run(main::run).
It is true that the weight/2 fact predicate is indexed by its integer argument.
This means that, given its integer argument, weight/2 should act like a
procedure. However Prolog is impervious to wishful thinking; then you need
to define a getweight/2 procedure, that will retrieve the weight values.
Below, you can see how getweight can be defined. Use it as a model
for getting around a situation, where you need to call a nondeterm predicate
from a procedure.
198 Bugs
implement main
class facts
weight:(integer, real).
class predicates
getweight:(integer, real) procedure (i, o).
clauses
classInfo("main", "nonprocedure").
weight(0, -1.0).
weight(1, 2.0).
weight(2, 3.5).
implement main
class facts
weight:(integer, real).
clauses
classInfo("main", "nonprocedure").
weight(0, -1.0).
weight(1, 2.0).
weight(2, 3.5).
run():- console::init(),
weight(1, X), stdio::write(X), stdio::nl.
end implement main
goal mainExe::run(main::run).
implement main
class predicates
vowel:(string, string) procedure (i, o).
member:(string, string*) nondeterm.
clauses
classInfo("main", "ifexample-1.0").
member(X, [X|_]).
member(X, [_|Xs]) :- member(X, Xs).
goal mainExe::run(main::run).
This chapter shows how to create a form with edit fields, and use its contents
to feed a data base. However, the most important thing that you will learn is
how to access the fields from the form. Edit field windows are stored as fact
variables, whose names you must discover, and use to handle the window, as
in the example below.
onSave(_Source) = button::defaultAction() :- Key= keyvalue_ctl:getText(),
Email= email_ctl:getText(), T= listEdit_ctl:getText(),
emails::insert_info(Key, Email, T).
Indexes that are stored in a special data structure called B+Tree. All you
need to know about B+Trees is that they store things in order (for
instance, in alphabetical order). When things are in order, like words
in a dictionary, it is easy to search for them.
Domains give the register structure. In our example, the contact domain
is defined as
domains
contact= email(string, string, string).
201
202 A Data Base Manager
• Using the option File/Add from the task menu, add the package
chainDB. This package is inside the folder pfc, in the installation di-
rectory. Build the application.
• Create a class emails at the root of the Project Tree. Untick the Creates
Objects option. In section 21.2, you will find the listing of emails.cl and
emails.pro, that you should insert in the appropriate files. Build the
application.
class predicates
createNewDB:(string FNAME).
predicates
oncreate : window::menuItemListener.
clauses
oncreate(_Source, _MenuTag) :-
FName= vpiCommonDialogs::getFileName("*.*",
["Email", "*.dbs"],
"Create", [], ".", _X), !,
createNewDB(FName).
oncreate(_, _).
createNewDB(Fname) :- file::existfile(Fname), !.
createNewDB(Fname) :- emails::data_base_create(Fname).
You must develop your programs step by step, checking whether each new
step is correct before going forward. In the present step, you have added
a new Create menu item to TaskMenu.mnu. You have also inserted code
into onCreate, in order to read a file name, and create a corresponding
database. Build the application, run it, and click on the Create option of
the application menu. When the file selection dialog asks you for a file name,
21.1 Database manager 203
type mails.dbs. If everything follows the script, the program will create
a mails.dbs file. Whenever you want to use a data base, you must open
it. After using it, you must close it, before turning off the computer. To
make sure that we will not forget to close the data base before exiting the
application, let us add the snippet
clauses
onDestroy(_) :- emails::data_base_close().
class predicates
openDB:(string FNAME).
predicates
onFileOpen : window::menuItemListener.
clauses
onFileOpen(_Source, _MenuTag) :-
FName= vpiCommonDialogs::getFileName("*.*",
["Email", "*.dbs"],
"Open", [], ".", _X), !,
openDB(FName).
onFileOpen(_, _).
openDB(Fname) :- file::existfile(Fname), !,
emails::data_base_open(Fname).
openDB(_).
to ProjWin/TaskWindow.win/Code Expert/Menu/TaskMenu/id_file/id_file_open.
Build the application. Then, from the VDE task menu, choose the option
File/New in New Package, and insert the form below onto the root of the
project.
204 A Data Base Manager
The form has a list box. Therefore, it is necessary to add a list of options
into it. In order to achieve this goal, add the snippet
clauses
onListEditShow(_Source, _CreationData) :-
WlboxHandle=listEdit_ctl:tryGetVpiWindow(), !,
vpi::lboxAdd(WlboxHandle, ["Person", "Commerce"]).
onListEditShow(_, _).
to the ShowListener of listEdit:listEdit_ctl.
The last step of this project is to add code to the Save, and List buttons.
Therefore, add
onSaveClick(_Source) = button::defaultAction() :-
Key= key_ctl:getText(),
Email= address_ctl:getText(),
T= listEdit_ctl:getText(),
emails::insert_info(Key, Email, T).
to ClickResponder of button:save_ctl. Finally, add
clauses
onListClick(_Source) = button::defaultAction :-
emails::db_list().
to the ClickResponder of button:list_ctl. Enable the File/New option of
the application task menu; add the snippet
onFileNew(S, _MenuTag) :-
X= insertForm::new(S), X:show().
to TaskWindow/Code Expert/Menu/TaskMenu/id_file/id_file_new. Build
the application once more, and that is it.
21.2 btr class 205
%File: emails.pro
implement emails
open core
domains
contact= email(string, string, string).
clauses
classInfo("db/emails/emails", "1.0").
data_base_create(FileName) :-
ChainDB= chainDB::db_create(FileName, chainDB::in_file),
ChainDB:bt_create("email", ContactName, 40, 21, 1),
ChainDB:bt_close(ContactName),
ChainDB:db_close(), !.
data_base_create(_).
data_base_open(FileName) :-
ChainDB= chainDB::db_open(FileName, chainDB::in_file),
ChainDB:bt_open("email", ContactName),
assert(dbase(ChainDB, ContactName)).
data_base_close() :-
retract(dbase(ChainDB, BTREE)), !,
ChainDB:bt_close(BTREE),
ChainDB:db_close().
data_base_close().
206 A Data Base Manager
class predicates
getInfo:(chainDB, chainDB::ref, string, string)
procedure (i, i, o, o).
dbLoop:().
clauses
getInfo(ChainDB, Ref, Name, Email) :-
ChainDB:ref_term(Ref, Term),
Term= email(Name, Email, _), !.
getInfo(_, _, "none", "none").
db_list() :-
dbase(ChainDB, BTREE),
ChainDB:key_first(BTREE, Ref), !,
getInfo(ChainDB, Ref, Name, Email),
stdio::write(Name, ", ", Email), stdio::nl,
dbLoop().
db_list() :- stdio::write("None"), stdio::nl.
del(Key) :-
dbase(ChainDB, BTREE),
ChainDB:key_search(BTREE, Key, Ref), !,
ChainDB:key_delete(BTREE, Key, Ref),
ChainDB:term_delete("Contacts", Ref).
del(_Key).
The database is a filing system. As any filing system, it has two com-
ponents: a chain of portfolios, each one containing data that one wants to
store; and an alphabetical index, that points to the different portfolios, and
allows a potential user to discover where the desired information is stored.
In the example database, the chain of portfolios is named "Contacts"; each
portfolio has the following form: email(string, string, string). The
first argument of email holds the name of a contact, the second argument
contains an email, and the third argument informs the type of contact one
has with the entity associated with the database entry.
The chain_insertz predicate stores the portfolio onto the "Contacts"
chain, and returns a RefNumber to its location. The RefNumber will be stored
in alphabetical order into the BTree.
To search a Contact-email, corresponding to a given Name, one searches
the BTree, in order to find the reference; once in possession of the reference
number, it is straightforward to obtain the portfolio.
data_base_search(Name, Contact, Tipo) :- dbase(ChainDB, BTREE),
ChainDB:key_search(BTREE, Name, Ref),
ChainDB:ref_term(Ref, Term),
Term= email(Name, Contact, Tipo), !.
data_base_search(_Name, "none", "none").
In this chapter, I will talk about a few books on Prolog, and programming
techniques. You will notice that the books are somewhat old, and out of
print. One reason for this is that people do not buy technical books as often
as before, since most students hope to get material like this tutorial from
the Internet. Another reason is that great computer scientists were very
enthusiastic about Prolog in the eighties and nineties. These scientists wrote
books that are hard to beat. For instance, I cannot find any modern book
that comes close to [Coelho/Cotta].
22.1 Grammars
Prolog excels in language processing, compiler writing, and document prepa-
ration. In fact, the language was invented with language processing in mind.
Most books deal with standard Prolog, that is slightly different from Visual
Prolog. While standard Prolog was designed for fast prototype of small ap-
plications and for testing ideas, Visual Prolog is used in large commercial
and industrial critical systems. Wellesley Barros, for instance, has designed
and programmed a large system for Hospital administration in Visual Prolog.
You bet that one cannot afford a failure in such a system due to type errors.
In critical situations, type checking and type declarations are very impor-
tant. In any case, although programming in Visual Prolog, you can profit
from ideas presented in books based on standard Prolog. An old book, that
you may find in your local library, was written by the Argentinean scientist
Veronica Dahl(see [Abramson/Dahl]), one of the greatest modern linguists.
Dr. Cedric de Carvalho wrote a Prolog compiler that generates Java byte-
code. This compiler allows you to write applets in Prolog. His compiler is
not a fully fledged tool, but you can easily improve it. In the mean time, you
209
210 Books and articles
will learn a lot about compilation, parsing, Java, etc. You can start reading
Cedric’s paper in [Cedric et all]. You can also port the compiler, written in
VIP 5.2, to VIP 6.2. The address is
http://netprolog.pdc.dk
22.2 Databases
David Warren, a computer scientist, is the main proponent of using Prolog
as a database engine. The book [Warren] is out of print, but you should get
a used copy, if you need to learn about databases. It is a classic.
David Warren leads a team that develops XSB, a database oriented Pro-
log, at Stony Brook University. Although I do not think that his version
of Prolog is usable for serious applications, since it relies on other tools for
building GUI, it is not compiled (it is not easy to write a beautiful applica-
tion in XSB), and it is not typed, I still strongly recommend a visit to the
XSB page. There you will find many good ideas on databases and Prolog,
and tools to prototype them. The address is
http://www.cs.sunysb.edu/~sbprolog/xsb-page.html
By the way, XSB provides a set of tools to export its functionality to other
languages. Therefore, if you like its features, you can call the XSB DLL from
Visual Prolog! The XSB page shows how to call the DLL from Visual Basic.
You can use the same approach to call it from Visual Prolog.
Prolog, you can even improve the original programs. For instance, one of my
students was able to create diagrams for the circuits described in Chapter 2
of [Sterling and Shapiro].
My favorite book on Prolog by far is Prolog by Example, by [Coelho/Cotta].
It is a kind of FAQ: The authors state a problem, and present the solution. It
is also an anthology. The problems and solutions were proposed and solved
by great computer scientists. This book deserves a new edition. Try, and try
hard, to get a used copy. If you are not able to get a copy, ask permission
from the authors, and make a xerox copy.
212 Books and articles
Part II
Artificial Intelligence
213
Chapter 23
Search
Patrick Henry Winston, author of a very famous book of LISP, starts his
exploration of Artificial Intelligence with search, claiming that search is ubiq-
uitous. Nothing could be closer to the truth. In fact, everything, from robot
motion planning to neural network, can be reduced to search. Therefore, like
Winston, let us start our study of Artificial Intelligence from search.
23.1 States
In artificial intelligence, the expression problem solving refers to the analysis
of how computers can use search to solve problems in well-circumscribed
domains. One question arises from this definition: What does an intelligent
program inspect in its search for the solution to a problem? It inspects a
maze of states connected by operations that cause feature transitions.
An object has states, i.e., it has a set of features that determines its
situation at a given instant. For example, Euclid defined point as an entity
whose sole feature is its position. Therefore, the state of a punctual object
is given by its coordinates. However, in general, one can use other features
besides position, in order to define the state of an object:
Each feature has one or more attributes, and values for the attributes. For
instance, the position has three attributes, wich are the coordinates of a
215
216 Search
point; in a 2D space, the position attributes can have values in the Cartesian
product R × R; therefore, the tuple (50, 200) gives valid values for the
position. To make a long story short, one can say that state is a snapshot in
time. Each attribute that one can change to solve a problem is called degree
of freedom. Then a point in a 3D space has three degrees of freedom.
Operator is a procedure that can be used to make the transition from one
state to another. The artificially intelligent program starts at an initial state
and uses the allowable operators to move towards a goal state. A set of all
states that the objects under consideration can take, and all transitions that
lead from one state to another is called state space.
implement main
domains
node= r(integer, string).
path= node*.
queue= path*.
class facts
operator:(integer, string, string).
class predicates
bSearch:(queue, path) determ (i, o).
nextLevel:(path, path) nondeterm (i, o).
solved:(path) determ.
prtSolution:(path).
clauses
classInfo("main", "breath").
prtSolution(L) :-
foreach P= list::getMember_nd(list::reverse(L)) do
stdio::write(P), stdio::nl
end foreach.
23.3 Breadth First Strategy 219
bSearch([T|Queue],Solution) :-
if solved(T) then Solution= T
else Extentions= [Daughter || nextLevel(T, Daughter)],
ExtendedQueue= list::append(Queue, Extentions),
bSearch(ExtendedQueue, Solution) end if.
run():- console::init(),
if bSearch([[r(0, "b")]], L) then prtSolution(L)
else stdio::write("No solution!"), stdio::nl end if.
end implement main /* breath*/
goal mainExe::run(main::run).
Initially, the Queue has a single path, that starts and ends at the root:
bSearch([[r(0, "b")]], L)
Since this path does not lead to the goal, it is extended to every daughter of
"b", and the extensions are appended to the end of the Queue:
At this point, the robot has visited only one node, to wit, "b". Then the
extensions of node "c" are appended to the end of the Queue.
220 Search
r(0,"b")
r(1,"c")
r(5,"g")
r(9,"k")
D:\vipro\aityros\aiprogs\breath\Exe>pause
Press any key to continue...
At this point, you are ready to try the algorithm on a real problem, to wit,
the slide puzzle.
implement slide
domains
node= r(string, stt).
path= node*.
queue= path*.
piece= e;b;w.
stt= st(piece, piece, piece, piece, piece).
class predicates
bSearch:(queue, path) determ (i, o).
nextLevel:(path, path) nondeterm (i, o).
solved:(path) determ.
prtSolution:(path).
operator:(string, stt, stt) nondeterm (o, i, o).
clauses
classInfo("breath", "1.0").
prtSolution(L) :-
foreach P= list::getMember_nd(list::reverse(L)) do
stdio::write(P), stdio::nl
end foreach.
operator("jump",st(A,B,e,C,D),st(e,B,A,C,D)).
operator("jump",st(A,B,e,C,D),st(A,B,D,C,e)).
operator("slide",st(A,B,C,e,D),st(A,B,e,C,D)).
operator("jump",st(A,B,C,e,D),st(A,e,C,B,D)).
operator("slide",st(A,B,C,e,D),st(A,B,C,D,e)).
operator("slide",st(A,B,C,D,e),st(A,B,C,e,D)).
operator("jump",st(A,B,C,D,e),st(A,B,e,D,C)).
bSearch([T|Queue],Solution) :-
if solved(T) then Solution= T
else Extentions= [Daughter || nextLevel(T, Daughter)],
ExtendedQueue= list::append(Queue, Extentions),
bSearch(ExtendedQueue, Solution) end if.
run():- console::init(),
if bSearch([[r("0", st(w,w,e,b,b))]], L) then prtSolution(L)
else stdio::write("No solution!"), stdio::nl end if.
end implement slide
goal mainExe::run(slide::run).
D:\vipro\aityros\aiprogs\slide\Exe>pause
Pressione qualquer tecla para continuar. . .
after visiting all descendants of "c", the depth first strategy visits "g". To
implement this kind of strategy, all you need to do is to append the children
of a given node to the front of the Queue, instead of appending them to the
tail. Here is the only necessary modification made to the previous program:
dSearch([T|Queue],Solution) :-
if solved(T) then Solution= T
else Extentions= [Daughter || nextLevel(T, Daughter)],
ExtendedQueue= list::append(Extentions, Queue),
dSearch(ExtendedQueue, Solution) end if.
I also changed the name of the predicate to dSearch for obvious reasons.
f (N ) = g(N ) + h(N )
In this formula, g(N ) is the known cost of going from the root until the N
node; the function h(N ) gives the estimated cost of going from N to the goal
node. The heuristic strategy sorts the Queue in such a way that paths with
lower values of f (X) are visited first. Let us use the heuristic strategy to
find the path between two cities on the map below.
because Dr. Fuchs was my minor adviser. However, it is not to brag that I have a low
Erdös number that I am writing this footnote, but to tell the opinion that Dr. Fuchs had
about the French language. He said that French is certainly the most beautiful and sweet
sounding language in the world; the only problem is the awful pronunciation that French
people insist on maintaining.
224 Search
However, before starting, I would like to tell a story. I like obscure lan-
guages, and have learned quite a few of them. For instance, I know Ancient
Egyptian, Homeric Greek, Esperanto, Latin, etc. When I was a teenager, I
even studied Guarani, a second rate language spoken only in Paraguay, an
underpopulated country.
A guy or gal has translated a book by a certain V. N. Pushkin from
Russian into one of those second rate languages that I learned. The book’s
title was: Heuristics, the Science of Creative Thought. When I read Pushkin’s
book in translation, I understood how difficult is to find a good heuristic
function, and how much more difficult it is to translate a text from a language
to another, specially if you do not know the subject treated in the text well.
Natural language processing is difficult not only for computers, but for people
too. For instance, Dr. Pushkin tried to explain the rules of chess, in order to
use the game as a test bed for Artificial Intelligence. In his explanation, he
has said that the knight moves along a Γ-shaped path, where Γ is a letter of
the Russian alphabet. The translator came out with the following rendering
of the original: The knight moves along an upsidedown L-shaped path. After
that long digression into the realms of translation, let us return to heuristic
search. The cities will be represented by their coordinates:
xy("a", 2, 4). xy("b", 5, 6).
Two place facts describe the one way roads that connect the cities:
op("a", "b" ). op("b", "m"). op("m", "f").
23.5 Heuristic search 225
You have learned that, in the heuristic search, the queue must be sorted so
that the most promising paths come forward. The sorting algorithm that
you will learn is very interesting, and was invented by Tony Hoare. Each
path is represented by the structure shown below.
t(real, real, it)
where the first argument gives the value of the heuristic function F for the
node; the second argument gives the value of g(N). The sorting algorithm has
a comparing predicate as its first argument, and pushes the most promising
branches to the front of the queue.
search([T|Queue],S):-
if goalReached(T) then S= T
else Extension= [E || toDaughter(T,E)],
NewQueue= list::append(Queue,Extension),
BestFirst= list::sortBy(cmp, NewQueue),
search(BestFirst, S)
end if.
The complete program is implemented below.
clauses
classInfo("main", "heureka-1.0").
init("a").
theGoal("s").
hn(N) = HN :- theGoal(S),
if getXY(S, XS, YS), getXY(N, XN, YN)
then HN= math::sqrt(((XN-XS)*(XN-XS)) +
((YN - YS)*(YN - YS)))
else HN= 0.0 end if.
23.5 Heuristic search 227
search([T|Queue],S):-
if goalReached(T) then S= T
else Extension= [E || toDaughter(T,E)],
NewQueue= list::append(Queue,Extension),
BestFirst= list::sortBy(cmp, NewQueue),
search(BestFirst, S)
end if.
toDaughter(t(_F,G,[r(B,N)|Path]),t(F1,G1,[r(Op, Child),r(B,N)|Path])):-
op(N, Child),
Op= string::format("%s to %s", N, Child),
not_in_circle(Child, Path),
G1 = G + cost(N, Child), F1 = G1 + hn(Child).
prtSolution( t(_,_,T)):-
foreach X= list::getMember_nd(list::reverse(T)) do
stdio::write(X), stdio::nl
end foreach.
goal mainExe::run(main::run).
228 Search
Chapter 24
Neural Networks
Spain is not distinguished for its science. In fact, there are families in France
that have twice as many Nobel Laureates in science than the whole of Spain.
In Europe, there are very few countries with less Nobel Laureates than Spain;
one of them is Servia, and one of the Serbians who did not receive the Nobel
prize is Nikola Tesla, and a country that has Nikola Tesla, does not need
Nobel laureates. Notwithstanding, the only two Spanish Nobel laureates are
counted among the greatest scientists of all time, together with Newton, and
Archimedes. In this chapter, you will learn about one of them.
The Spanish scientist Don Santiago Ramón y Ca-
...
jal studied the physiology of the nervous system and ...
...
...
...
concluded that it was driven by cells that he called ....
......
...
....
.. .
....
.......... . ......
... .......... . .....................
neurons. Nowadays, scientists believe that a given .....
...
.............. ..
...
.
... .. ...
..... ........ ...................................
neuron receives inputs from other neurons through ........
.. ...
...
.
.
......
...........
................... ......................
links called synapses. By the way, synapsis is the .....
.
.....
.... .
..
. .......
.....
.....
.....
Greek word for link. Typically, a neuron collects .....
.....
.....
.....
.....
signals from others through a set of structures called .....
.....
.....
.....
.....
dendrites, which means tree shoots in Greek; if the .....
...
intensity of the incoming signals goes beyond a certain threshold, the neuron
is fired, and sends out electrical stimuli through a stem known as an axon,
which splits into the branch-like dendrites, whose synapses will activate other
neurons. So, to sum up. . . at the end of each dendrite, a synapse converts
neuron activity into electrical stimulus that inhibits or excites another neu-
ron.
In 1947, Warren McCullogh and Walter Pitts proposed the first mathe-
matical model for a neuron. Their neuron is a binary device, with a fixed
threshold; it receives multiple inputs from excitatory synapses, which gives
to each input source a weight that is effectively a positive integer; inhibitory
inputs have an absolute veto power over any excitatory inputs; at each pro-
229
230 Neural Networks
cessing step the neurons are synchronously updated by summing the weighted
excitatory inputs and setting the output to 1 if and only if the sum is greater
than or equal to the threshold.
The next major advance in the theory of neural networks was the percep-
tron, introduced by Frank Rosenblatt in his 1958 paper. Rosenblatt was a
professor at Cornell University, that happens to be my alma mater. However,
I did not have the good fortune of knowing him, since he passed away many
years before my arrival at that university.
'&%$
Ã!"#TTT
TTT
3.0 TTTT
'&%$
Ã!"# TT'&%$
*4
2.5
jjjj/Ã!"#
O / Output
jj
jjj 2.0
Ã!"#jjj
'&%$ −1.0
'&%$
Ã!"#
0.6 ..
..
.
... than a threshold, it produces a non zero out-
..
...
.
..
put. This scheme was improved by feeding the
0.4 ..
..
.
.
..
...
..
weighted sum to a sigmoid function, and taking
0.2 ..
..
.
...
....
...
the value returned as the output of the percep-
.......
........
0.0 .............................
tron. If you plot a sigmoid, it takes the shape
of the Greek letter ς, that is known as final
−6 −4 −2 0 2 4 6 sigma; hence the name sigmoid. The sigmoid
is defined as
1
ς(x) =
1 + e−x
In Prolog, this definition becomes:
sigmoid(X)= 1.0/(1.0+math::exp(-X)).
Figure 24.1 above shows a perceptron with three inputs. Let the inputs be
x1 , x2 , and x3 . In this case, the output will be the result of the expression
ς(3x1 + 2.5x2 + 2.0x1 − 1)
One interesting feature of the perceptron is its learning abilities: You can
train the perceptron to recognize input patterns. In fact, given enough ex-
amples, there is an algorithm that modifies the weights of the perceptron, so
231
that it can discriminate between two or more patterns. For instance, a two
input perceptron can learn to tell when a and-gate will output 1, and when
it will output 0.
The learning algorithm of the perceptron must have a set of training
examples. Let us assume that it is learning the workings of an and-gate.
Here are the examples:
facts
eg:(unsigned, real, real, real).
clauses
eg(0, 1, 1, 1).
eg(1, 1, 0, 0).
eg(2, 0, 1, 0).
eg(3, 0, 0, 0).
P
The perceptron has a predicting procedure, that uses the formula ς( wi xi )
to predict the output value, given a set of inputs. In Prolog, the predicting
procedure can be coded as shown below.
predicted_out(E, V) :-
eg(E, I_1, I_2, _),
getWgt(0, W_0),
getWgt(1, W_1),
getWgt(2, W_2),
X = W_0+W_1* I_1 + W_2* I_2,
V= sigmoid(X).
facts
weight:(unsigned, real).
clauses
weight(0, 1.5). weight(1, 2.0). weight(2, 1.8).
The initial weights are fictitious. The true weights must be found by the
learning algorithm. The procedure getWgt has a very simple definition:
You may ask why one needs to define getWgt at all; it would be easier to use
weight directly; the problem is that weight defines a nondeterm predicate,
and we need a procedural weight retrieving predicate. By the way, one needs
a procedure to retrieve the examples too:
egratia(Eg, I1, I2, Output) :- eg(Eg, I1, I2, Output), !.
egratia(Eg, I1, I2, Output) :- Ex = Eg rem 4,
eg(Ex, I1, I2, Output), !.
egratia(_Eg, 1, 1, 0).
The learning algorithm is based on the calculation and correction of the error
made by the predicting function. The error of a given example is calculated
by the following predicate:
evalError(Obj, E) :- nn:predicted_out(Obj, VC),
nn:egratia(Obj, _I1, _I2, V),
E= (VC-V)*(VC-V).
What we really need is not the error of a given example, but the sum of the
errors for each example. One can calculate this total error as shown below.
errSum(Exs, _Partial_err, Total_err) :- acc := 0.0,
foreach Eg= list::getMember_nd(Exs) do
evalError(Eg, Err), acc := acc + Err
end foreach,
Total_err= acc.
The computational scheme foreach will pick each example from the Exs list,
calculate the corresponding error, and add the result of the calculation to
the accumulator acc, which is stored in a global variable.
class facts
acc:real := 0.0.
The weights will be updated through the gradient descent method in such a
way as to reduce the error to a minimum.
updateWeight(DX, LC, Exs) :- errSum(Exs, 0.0, Err0),
PN= [tuple(I, W) || nn:getWgt_nd(I, W)],
nn:setWeight([ tuple(P, NV) ||
tuple(P, V) = list::getMember_nd(PN),
V1= V+DX,
nn:assertWgt(P, V1),
errSum(Exs, 0.0, Nerr),
nn:popweight(P, V1),
NV= V+LC*(Err0-Nerr)/DX]).
233
'&%$
Ã!"#
.....
..
..
neural network of figure 24.2 does is to draw a
.....
.....
.....
.....
.....
..
.
.
..
straight lines separating the points where or-
.....
.....
.....
.....
.....
..
....
gate is 1 from the point where it is 0, as you
.....
.....
..... x 1 can see from the figure. Then, all that a per-
0 .
1
ceptron can do is to learn how to draw straight
lines in a state space, in order to separate clusters of points with a given
property. The problem is that one often cannot draw such straight lines in a
multidimensional space.
The xor-gate is much more useful than the or-gate for two reasons. First,
the two inputs of the xor-gate can represent logic propositions like:
x1 = the machine is on; x2 = the machine is off
x1 = the door is open; x2 = the door is closed
Since the machine cannot be on, and off at the same time, x1 = 1 and x2 = 1
cannot be true; x1 = 0 and x2 = 0 cannot be true either. To describe logical
gates, one often uses truth tables, that give the output value for all values of
the input variables; below, you can see a truth table for the xor-gate.
24.2 Implementation of a multi-layer network 235
x1 x2 output
1 1 0
1 0 1
0 1 1
0 0 0
'&%$
Ã!"#OO
OOO
OOO
OOO
OOO
'&%$
Ã!"#? O/'&%$
'
Ã!"#
Ä? OOOOO
−3.1
??? Ä
Input 1
?? ÄÄ OOO3.0
OOO
?? ÄÄÄ OOO
??ÄÄ 1.7 O'&%$
'7
Ã!"#
Ä? o / XOR
Ä
Ä ?? ? oo O gate
ÄÄ 1.5
?? 2.0 ooo
Ä oo
Ä ?? oo
ÄÄ ? ooooo
Ã!"#Ä
'&%$ 2.7 o7/ '&%$
Ã!"# −1.0
Input 2
o oooo
ooo
oo ooo
Ã!"#o
'&%$ '&%$
Ã!"#
Figure 24.3: A neural network that can learn how to calculate a xor-gate
• Select the root of the Project Tree. Choose the option File/New from
the VDE Task Menu. This will open the Create Project Item dialog.
Select the Class tag from the left hand side pane; create a network
class, that Creates Objects. Press the Create-button. Add the class
specification to network.cl, network.i, and network.pro.
%File network.pro
implement network
open core
constants
className = "network/network".
classVersion = "".
facts
weight:(unsigned, real).
eg:(unsigned, real, real, real).
24.2 Implementation of a multi-layer network 237
clauses
sigmoid(X)= 1.0/(1.0+math::exp(-X)).
setExamples(Es) :- retractall(eg(_,_,_,_)),
setEx(0, Es).
setEx(_N, []) :- !.
setEx(N, [e(I1, I2,R)|Es]) :-
assertz(eg(N, I1, I2, R)),
setEx(N+1, Es).
setWeight(Qs) :-
retractall(weight(_,_)),
foreach tuple(X, WX)= list::getMember_nd(Qs) do
assert(weight(X, WX))
end foreach.
predicted_out(Obj, V) :-
hidden(Obj, [3,4,5], I_1),
hidden(Obj, [6,7,8], I_2),
getWgt(0, W_0),
getWgt(1, W_1),
getWgt(2, W_2),
X = W_0+W_1* I_1 + W_2* I_2,
V= sigmoid(X).
238 Neural Networks
Objects of the network class can play the role of a neuron. In the main
module, you will find the training program, and a test run() predicate for
the and-gate, and also for the xor-gate. Build the application, and Run in
Window.
%File: main.pro
implement main
open core
constants
className = "main".
classVersion = "nnxor".
clauses
classInfo(className, classVersion).
class facts
acc:real := 0.0.
nn:network := erroneous.
class predicates
evalError:(unsigned Obj, real Err) procedure (i, o).
errSum:(unsigned* Exs,
real Partial_err, real Total_err) procedure (i, i, o).
updateWeight:(real DX, real LC, unsigned* Exs) procedure (i, i, i).
24.2 Implementation of a multi-layer network 239
clauses
evalError(Obj, E) :- nn:predicted_out(Obj, VC),
nn:egratia(Obj, _I1, _I2, V),
E= (VC-V)*(VC-V).
class predicates
train:(real DX, real LC, unsigned* Exs) procedure (i, i, i).
clauses
train(DX, LC, Exs) :- errSum(Exs, 0, Err0),
if (Err0 < 0.1) then
stdio::write("Total Err: ", Err0), stdio::nl
else
updateWeight(DX, LC, Exs),
train(DX, LC, Exs)
end if.
class predicates
training:(network, real, real) procedure (i, i, i).
clauses
training(NN, _DX, _LC) :- nn := NN,
WWW= [ tuple(0,0),tuple(1,2),tuple(2,0),
tuple(3,0),tuple(4,1),tuple(5,0),
tuple(6,0),tuple(7,0),tuple(8,0)],
nn:setWeight(WWW),
train(0.001,0.1,[0,1,2,3, 1, 2, 3, 0, 3, 2, 1, 0]).
240 Neural Networks
run():- console::init(),
XOR = network::new(), AND = network::new(),
XOR:setExamples([ network::e(1,1,0), network::e(1,0,1),
network::e(0,1,1), network::e(0,0,0)]),
AND:setExamples([ network::e(1,1,1), network::e(1,0,0),
network::e(0,1,0), network::e(0,0,0)]),
training(XOR, 0.001, 0.5),
training(AND, 0.001, 0.5),
XOR:usit( [1,1], XOR11), stdio::nl,
stdio::write("xor(1,1)=", XOR11), stdio::nl,
XOR:usit( [1,0], XOR10), stdio::nl,
stdio::write("xor(1,0)=", XOR10), stdio::nl,
XOR:usit( [0,1], XOR01), stdio::nl,
stdio::write("xor(0,1)=", XOR01), stdio::nl,
XOR:usit( [0,0], XOR00), stdio::nl,
stdio::write("xor(0,0)=", XOR00), stdio::nl, stdio::nl,
AND:usit( [1,1], AND11), stdio::nl,
stdio::write("1&&1=", AND11), stdio::nl,
AND:usit( [1,0], AND10), stdio::nl,
stdio::write("1&&0=", AND10), stdio::nl,
AND:usit( [0,1], AND01), stdio::nl,
stdio::write("0&&1=", AND01), stdio::nl,
AND:usit( [0,0], AND00), stdio::nl,
stdio::write("0&&0=", AND00), stdio::nl.
goal
mainExe::run(main::run).
xor(1,1)=0.08040475166406771
xor(1,0)=0.9194034557462105
24.4 A Historical Perspective 241
xor(0,1)=0.8913291572885467
xor(0,0)=0.0922342035736988
1&&1=0.8712088309977442
1&&0=0.08891005182518963
0&&1=0.09297532089700676
0&&0=0.009538209942478315
D:\vipro\aityros\aiprogs\neurons\Exe>pause
Press any key to continue. . .
As you can see, the learning algorithm converged to a behavior that is very
close to the desired behavior of the xor-gate.
Mancala is a family of board games that are very popular in Africa and
Asia; it is a count and capture game. The game of this family best known
in the Western world is Kalah. Mancala games play a role in many African
and some Asian societies comparable to that of chess in the West. It is also
popular in India, specially among the Tamil women.
In the process of sowing, all the seeds from a hole are dropped one-by-one
into subsequent holes in a motion wrapping around the board. Depending
on the contents of each hole sown in a lap, a player may capture seeds from
the board. If the hole contains less than 3 seeds, the player can capture its
contents, and also the seed that s/he would drop in that hole. The captured
seeds go to the players store. The game ends if one player captures at least
10 seeds more than his/her opponent. The game also ends if one player has
no seeds on his side of the board. In any case, the player that captures more
seeds wins the game.
243
244 Alpha Beta Pruning
B=5 C D
Build the application in order to insert the main class into the Project Tree.
Then create a class mv by selecting the option New in New Package from
the IDE task menu.
%File: mv.cl
class mv
open core
domains
side= a;b.
board= none ; t( side, integer LastMove,
integer Pot, integer Pot,
integer* Brd).
predicates
prtBoard:(board) procedure (i).
newBoard:(side, board) procedure (i, o).
addPot:(side, integer, integer, integer,
integer, integer) procedure (i,i,i,i, o,o).
changeSides:(side, side) procedure (i, o).
sowing:(side, integer, integer, positive,
integer, integer, integer*,
board) procedure (i, i, i, i, i, i, i, o).
sow:(integer, board, board) procedure (i,i,o).
%File: mv.pro
implement mv
open core
clauses
prtBoard(t(S, LM, P1, P2, [I0, I1, I2, I3, I4, I5, I6, I7])) :- !,
stdio::write("Move: ", LM), stdio::nl,
if S= a then T= "a" else T= "b" end if,
stdio::writef(" %4d %4d %4d %4d\n", I3, I2, I1,I0),
stdio::writef("%4d %4d %s\n", P1, P2, T),
stdio::writef(" %4d %4d %4d %4d\n", I4, I5, I6, I7).
prtBoard(_) :- stdio::write("None\n").
246 Alpha Beta Pruning
changeSides(a, b) :- !.
changeSides(b, a).
Now you are ready to implement the main class, that will search the game-
tree for the best move.
bestMin([Lnc|Lncs], LookAhead,
Alpha, Beta, BestMove, BestVal) :- !,
maxeval(Lnc, LookAhead, Alpha, Beta, _, Val),
alphaPruning(Lncs, LookAhead,
Alpha, Beta, Lnc, Val, BestMove, BestVal).
class predicates
readMove:(board, board) procedure (i, o).
legalMove:(core::positive, board) determ.
game:(board, integer) procedure (i, i).
endGame:(board) determ (i).
announceWinner:(integer) procedure (i).
clauses
legalMove(I, t(a, _LM, _P1, _P2, X)) :-
I >= 0, I < 4,
list::nth(I, X) > 0.
readMove(Pos, ReadPos) :-
stdio::write("Present situation: "), stdio::nl,
prtBoard(Pos),
%stdio::write("Your move: "),
SR= stdio::readLine(),
trap(I1= toTerm(SR), _, fail),
stdio::nl,
legalMove(I1, Pos),
sow(I1, Pos, ReadPos),
prtBoard(ReadPos), !.
readMove(Pos, ReadPos) :-
stdio::write("Something failed"), stdio::nl,
readMove(Pos, ReadPos).
run():- console::init(),
newboard(a, B),
stateval(B, Val),
game(B, Val).
goal mainExe::run(main::run).
Like Erast Fandorin, I don’t like games; besides this, I don’t have his luck,
which means that I always loose. All the same, I pitted my wits against the
computer, and inevitably lost. Here is the beginning of the game:
Present situation:
Move: -1
6 6 6 6
0 0 a
6 6 6 6
0
Move: 0
7 7 7 0
0 0 b
7 7 7 6
Present situation:
Move: 7
8 8 8 1
0 0 a
8 8 7 0
1
Move: 1
9 9 1 0
2 0 b
9 9 8 1
Present situation:
Move: 4
10 10 0 1
2 4 a
1 11 9 0
Index
251
252 INDEX
Lists, 58 onPaiting, 88
iterative reduction, 64 open class, 52
recusive reduction, 64
reduction, 64 painting
schemes, 64 event handling, 87
zip, 64 paiting
ZIP scheme, 66 drawPolygon, 88
lists Papert, 140
[X:Xs], 59 pattern style, 89
head, 59 Peano, 139
patterns, 59 curve, 143
tail, 59 Latino Sine Flexione, 143
logical and, 48 recursion postulate, 142
logical if, 49 Pereira
logical implication, 49 Luis Moniz, 119
logical junctors, 48 Piaget, 140
Logo, 140 pictDraw, 90
Lope de Vega, 155 pictLoad, 90
LsF picture
Latino sine Flexione, 147 mask, 90
pictDraw, 89
map pictLoad, 89
four colors, 119 transparent, 90
mask, 90 polymorphism, 110
member, 110 predicates
Menu class predicates, 51
Enable option, 17 declarations, 51
task, 37 definition of, 42
Mouse, 27 determ, 52
add code, 27 multi, 52
event, 27 nondeterm, 52
MouseDown, 27 procedure, 52
multi, 52 type/flow patterns, 51
multi-solutions, 46 procedure, 52
Natural Language Processing, 143 Project
Newton, 115 building, 15
Nim, 121 Create item, 36
nondeterm, 52 creating, 13
settings, 14
Object Project Tree, 34
interface, 157 Project tree
254 INDEX
255
256 BIBLIOGRAPHY