See also: our Guide To Free Prologs
To follow this introductory tutorial,
you will need to install a traditional Prolog system
(that is, an interpreter which is more or less compatible
with the so-called ‘Edinburgh’ Prolog
standard. In this tutorial, we provide step by step
instructions for two such systems, PIE
Prolog and SWI-Prolog.
PIE and SWI-PROLOG
Which Prolog
should you use for this tutorial?
PIE Prolog is a simple, easy-to-use system
In its favour, PIE Prolog has a straightforward
user interface with a built-in multi-window, syntax-coloured
code editor. Of the two systems, PIE is probably
easier to use for a newcomer to Prolog...
SWI-Prolog may not be as 'beginner-friendly' as
PIE but it has other benefits
On
the other hand, while the SWI-Prolog environment
may look a bit Spartan and lacks an integrated
editor, it does have a larger range of predicates
than PIE and also has a graphical debugger. (Note:
you can increase the user-friendliness of SWI-Prolog
with an add-on editor - see our Guide
To Free Prologs). |
Getting Started
You can download
PIE (Prolog Inference Engine) Prolog
direct from the Bitwise site. Unzip everything into a
directory of your choice and run Pie.exe.
SWI-Prolog can be downloaded from www.swi-prolog.org.
PIE Prolog is a sample program which is written using
PDC (Prolog Development Center) Visual
Prolog. You
can also download a free version of Visual Prolog itself
from the PDC Web Site www.visual-prolog.com.
This includes the full source code of the PIE system.
Note that the Visual Prolog language itself is substantially
different from traditional ‘Edinburgh’ Prolog
and the sample code provide here cannot be used ‘as
is’ in
Visual Prolog. For a complete beginner, PIE Prolog
is an excellent system to start with as it is easy
to use and has a standard Windows-style user interface.
By comparison with many other Prolog systems, however,
PIE Prolog is relatively unsophisticated. It has only
a small number of language constructs (‘predicates’)
available and does not have any visual design or debugging
tools. So, if you want to go further in Prolog, you
may want to download a more complete implementation
such as SWI-Prolog. As for Visual Prolog, we’ll
have more to say about that in another feature soon…
Prolog was developed in the early ‘70s
in Marseille and ‘Marseille Prolog’ is
one of two well-known syntactical variations
of the language; the other, and more widely used
syntax, derives from work done at the University
of Edinburgh and is, accordingly, called ‘Edinburgh
Prolog’. |
Why Prolog...?
Prolog is quite different from other programming languages.
In most programming languages it is normal to work
out the solution to a problem before you write the
code. The finished program then contains a ‘hard-wired’ version
of your solution. In Prolog, by contrast, it is quite
normal to express problems in the code and then ask
the program to find one or more possible solutions.
Prolog is particularly good at making inferences or
deductions from sets of facts and rules. This makes it
especially well suited for the development of complex
applications such as the interpretation of human language
or diagnostic ‘expert systems’. These areas
of research are often grouped together under the somewhat
misleading name, ‘Artificial Intelligence’ or
AI. While it is debatable whether a Prolog program is
really any more intelligent than other types of program,
it does at least have a good claim to being more logical.
Prolog originally grew out of a branch of mathematical
logic called predicate calculus and the name itself is
short for PROgramming in LOGic.
If you don’t know anything about predicate calculus,
don’t panic. Few Prolog programmers do! Be warned,
though - if you are moving to Prolog from a language
such as C++, Java, Delphi or Basic, you will need to
learn a new way of thinking. It is no good looking for
Prolog functions, case statements
or while loops because they don’t exist.
One of Prolog’s defining features
is that it is a declarative rather than a procedural language.
Procedural languages such as C, Java and Pascal
are aimed at producing programs which precisely
specify a ‘flow of execution’.
Prolog programs, on the other hand, frequently
do not define fixed solutions. Instead they contain
facts (pieces of data) and rules (statements of
relationships among piece of data or other rules).
At run-time a Prolog program may be asked to find
one or multiple possible solutions to a given problem. |
Everything But The Goal
A
First Prolog Program...
The best way to appreciate Prolog is to see it in action.
First, let’s dispense rapidly with the traditional ‘Hello
world’ program. Load up your chosen Prolog system
and enter this simple command (you can do this at the
?- prompt
in SWI-Prolog or on a blank line in the Dialog window
in PIE), making sure that you use lower-case characters
as shown and that you put a full-stop at the end. Then
press the Enter key:
write('Hello world'),nl.
Most Prolog systems such as SWI-Prolog will return
something like this:
Hello world
Yes
Here
we have entered our query next to the ?- prompt
in SWI-Prolog’s main window. It responds by displaying ‘Hello
world’ and newline character and ‘Yes’ to
show that it has successfully answered our query. Then
it displays another prompt.
However, PIE Prolog returns this:
Hello world
True
1 Solution
In
PIE Prolog, queries are entered on a blank line in its
Dialog window. Note that it doesn’t display
a prompt, it displays ‘True’ instead of ‘Yes’ and
it gives a running total (here just 1) of the number
of solutions it has found to your query.
On the first line, the Prolog interpreter has done
what you’ve asked it to do – namely, it has
written ‘Hello world’ followed by a newline
(nl) character. Beneath that it has responded ‘yes’ or ‘true’.
This indicates that it has successfully solved the goal
you set it. There is only one correct solution to the
command you entered (sometimes there are more) and PIE
Prolog explicitly states this fact.
Now enter these two goals, one at a time, at the prompt:
4 is 2 + 2.
4 is 2 + 3.
In the first case, Prolog responds ‘yes’ (or ‘true’)
since it has been able to evaluate the expression. In
the second case, it responds ‘No’ (or it
may tell you that it can find no solutions). Notice that
the is operator has been
used in the above examples rather than the equals = operator
since is evaluates an
arithmetical expression such as 2
+ 2 prior to
testing it for equality. The = does
no such evaluation so it would only return true if both
sides of the expression were identical as in this example:
4 = 4.
Now let’s take a closer
look at the code of our Hello World program:
write('Hello world'),nl.
Here, ‘Hello world’ is a string argument
which is passed to the write/1 predicate. The term ‘predicate’ is
used to describe a Prolog ‘routine’ or ‘rule’.
You may think of predicates as Prolog’s nearest
equivalent to the functions and subroutines found in
other programming languages.
All the standard predicates are documented in the help
systems of both SWI-Prolog and PIE. In books and academic
texts about Prolog you may find the write predicate
shown as write/1. Here
the number 1 indicates that the predicate takes one argument.
It is said to have an ‘arity’ of 1. The nl predicate, which prints a new line character to screen
may be shown as nl/0, indicating that this takes no arguments
and therefore has ‘zero arity’.
Traditional implementations of the Prolog language
such as PIE and SWI are not strongly-typed. This means
that predicates can often take differeing types of argument.
The write/1 predicate,
for instance, can take both numeric and string arguments.
Try this out by entering this expression at the prompt:
write(5),write( ' plus '),write(8),write('
is ' ),
X is 5+8,write(X),nl.
This time, Prolog returns this:
5 plus 8 is 13
X = 13
Note, in some systems (such as SWI), you may need to
press the Enter key after this solution in order to return
to the Prolog prompt. Let’s look at what the Prolog
system has displayed. On the first line, it writes
the specified strings and numerical arguments. On the
second line, it returns the value of the argument, X.
In Prolog, variables must begin with a capital letter
and predicates must begin with lowercase letters. Arguments
that begin with lower-case letters will be treated as
symbols rather than variables. To clarify this, enter
these expressions, making sure that the argument X is
uppercase the first time and lowercase the next time
:
X is 2, write(X),nl.
X is
2, write(x),nl.
You need to pay close attention to the ‘punctuation’ symbols
in Prolog. I’ve already mentioned the full-stop
terminating character. The comma is equally important.
Think of it as meaning ‘and’. So, the Prolog
expression write('Hello world'),nl. could
be translated as “Write ‘Hello world’ and
print a new line”.
There is only so much we can do from the system prompt,
however. To create complex programs we need to save our
code in files. Select File, Open (or File,
Edit in SWI
Prolog) and load up our sample file, proglang.pro (or
proglang.pl).
Note: For convenience, I have supplied two sets
of sample source files. One set uses the extension ‘.pro’ and
the other set uses the extension ‘.pl’.
The contents of both sets of files are identical.
However, while many Prolog interpreters use ‘.pl’ as
the normal extension, the PIE interpreter uses ‘.pro’ and
it provides syntax sensitive code colouring for the
code contained in files with this extension. |
This sample file starts by defining a simple database
of facts about programming languages. The first four
facts state that the named languages are OOP (Object
Orientated Programming) languages:
oop(delphi).
oop(java).
oop(smalltalk).
oop(eiffel).
You can query these facts from the prompt. But first
the Prolog system needs to interpret or ‘consult’ the
source code.
Note
that editing and consulting a program are two different
things. Here I’ve loaded a source file
into Notepad using SWI-Prolog’s File,
Edit menu.
But before I can continue I must exit Notepad and then
select File, Consult to load the source ready to query
it from the Prolog prompt.
If you are using SWI-Prolog. you will need to close
the source editor (Notepad) before continuing. Now select
File, Consult and select proglang.pro (or
proglang.pl). Then switch back to the Console or Dialog
window.
PIE
has its own built-in editor. In fact, you can load multiple
source files into different windows and consult the code
in the active window by making a menu selection
If you are using SWI-Prolog, press Enter to return
to the ?- prompt. At this prompt (or
on a new line in PIE’s Dialog window) enter these
queries, one by one:
oop(smalltalk).
oop(basic).
You will see that the first query succeeds (Prolog
returns ‘yes’ or ‘true’) whereas
the second fails (Prolog returns ‘no’ or ‘no
solutions’). But you aren’t restricted to
simple yes/no tests. You can also ask Prolog to return
all the oop languages it knows about by asking it to
find matches for a variable name. Remember that a variable
must begin with a capital letter. At the prompt, enter
this:
oop(Language).
Prolog assigns the value of the first language in the
database, which happens to be delphi,
to the variable, Language.
In Prolog terminology, the variable
Language is said to be ‘instantiated’ with
the value delphi.
PIE goes on to show all the other possible solutions:
java, smalltalk and eiffel.
However, if you are using SWI-Prolog you will notice
that the Console window shows only the first solution:
Language = delphi
It has not re-shown the system prompt, ?-,
however. In common with many traditional Prolog interpreters,
SWI-Prolog displays one solution at a time but it gives
the user the option of continuing trying to find more
solutions. That is why it has not reshown the prompt.
We can ask for the next solution by pressing the semi-colon
key, ; . Do that now. Prolog displays
the second instantiation:
Language = java
Press the semi-colon two more times. Prolog will display
the remaining OOP language names in its database before
finally returning you to the prompt. Now switch back
to the source code window (in PIE you can keep the source
loaded into an editing window. In SWI-Prolog, you may
find it convenient to find the source file using Windows
Explorer and load it into Notepad). You will see that
there is also a block of facts about people and the languages
they like. These facts are stated in this form:
likes(dolly,prolog).
The fact expressed here is really a relationship between
two pieces of data, dolly and
prolog. It is equivalent to the English language expression ‘Dolly
likes Prolog’. You can test this for yourself in
the Console or Dialog window by entering:
likes(dolly,prolog).
As expected the answer is ‘yes’ or ‘true’.
Notice that this database contains more facts relating
to Dolly – stating that she likes Java, Smalltalk
and Basic. It also contains more facts about Prolog – stating
that Huw and Dwight like the language. It is possible
to use variables in your queries to instruct Prolog to
search for all possible solutions based on the available
data. Try entering this query, being careful to start
the Person variable with a capital letter and use a small
letter for the data item, prolog:
likes(Person, prolog).
Once again, PIE returns all solutions in one go whereas
SWI-Prolog replies:
Person = huw
To tell SWI-Prolog to keep looking for other solutions,
press the semi-colon key. It will return two more matches:
Person = dolly ;
Person = dwight ;
To find all the languages a particular person likes,
you can just specify the person’s name in the query
and use a variable for the language. Try this:
likes(dolly,Language).
Prolog Rules, OK!
There is more to Prolog programming than searching
lists of data, though. The real power of the language
resides in its ability to express complex relations between
things. These relations are expressed in the form of
rules. While a fact expresses a relationship that is
invariable true, a rule expresses a relationship that
is true only if various conditions are met. So “Dolly
likes Prolog” is a fact whereas “Garth likes
any language that both Reba and Tammy like” is
a rule. This is how we could express this rule in Prolog:
likes(garth,Language) :-
likes(reba,Language),
likes(tammy,Language).
Here, the :- symbol can be read as ‘if’.
If you recall that the comma can be read as ‘and’,
the meaning of this code should be fairly clear. Try
it out by entering this query:
likes(garth, Language).
The first condition in this rule, (likes
reba,Language),
actually depends on a different rule (once again, you’ll
find this in our proglang.pro sample file) which determines
which languages Reba likes:
likes(reba,Language) :-
oop(Language),
not(likes(dolly,Language)).
This rule states that Reba likes any language that
is an OOP language as long as that language is not liked
by Dolly. So, turning back to the rule that defines Garth’s
likes, you can see that the search tree which Prolog
has to follow in order to satisfy that rule is quite
complex. The good news is that the complexity of the
search is hidden from the programmer. Once you’ve
defined some facts and rules, you can let Prolog do all
the hard work when it comes to tracking down solutions.
This provides you with enormous flexibility. To understand
this, try entering these queries one by one:
likes( reba,Language ).
likes(
tammy,Language ), oop( Language ).
likes(
Person, Language ).
Now try constructing your own queries by adding conditions
together using commas or negating conditions using not() .
You could also try extending the program by writing extra
facts and rules in the editor, saving this modified file
and re-consulting it.
Of course, there may be occasions when one solution
is all you require. In such a case, you will need to
prevent Prolog from ‘backtracking’ to look
for more solutions. To prevent backtracking, you can
insert an exclamation mark which, in Prolog, is called
a ‘cut’.
Cut And Run
To see one simple example of the cut in action, consult
proglang2.pl (or proglang2.pro). If you look at the source
code in an editor you will notice that this includes
a full database of languages defined at the top of the
file. Find the trendylang/1 rule at the end of the file.
This attempts to define a fashionable programming language
according to three rules. Rule 1 succeeds if Reba likes
a language and Rule 2 succeeds if Garth likes a language.
Rule 3 is a ‘catch all’ that prints a message
when neither Rule 1 nor Rule 2 succeeds. In other words,
this is like an IF..THEN..ELSE test in a more conventional
language. Its operation can be summarised in this way:
if (Rule1=true) then
else if (Rule2=true)
then
else
Enter these goals at the prompt:
trendylang(lisp).
trendylang(eiffel).
At first sight, our code seems to work as expected.
According to the rules defined, Eiffel is trendy but
Lisp is not. (Note: lovers of Lisp
should not take this personally. After all, we would
not wish to suggest that country music singers are the
ultimate arbiters of the relative merits of programming
languages!). But let’s
suppose that we now decide we want a complete list of
the names of the languages in our database along with
an indication of whether or not each language is trendy.
To do this we need to instantiate a variable to the
name to the name of each of the languages in turn. We
then need to pass this instantiated variable to be tested
by the trendylang rule. Try this by entering this at
the prompt:
language(L),trendylang(L).
PIE will show all matches made. In SWI-Prolog, you
will need to press the semi-colon repeatedly to view
all the matches made. Either way, you will see that we
have some problems. Prolog tells us first that Delphi
is trendy and then that Delphi is not trendy. This is
because even when Rule 1 or Rule 2 of the trendylang rule evaluates to true, there is nothing to prevent the
catch-all Rule 3 from evaluating to true too!
Worse still, we find that Eiffel is twice found to
be trendy. This is because both Rule 1 and Rule 2 of
trendylang make a match for Eiffel. Just to add to the
confusion, so does Rule 3! So we end up being told twice
that Eiffel is trendy and then being told that it isn’t!
This is clearly not what we want. Now let’s turn
to the code and see what we can do about this.
First of all, you will see that I have written another
rule, nicelangs/0, that contains the query we just entered
at the prompt. That is, it instantiates a variable, L,
to a language and then passes L to the trendylang rule.
Enter this at the prompt:
nicelangs.
The only trouble here is that, having succeeded with
one language (Delphi),the nicelangs rule doesn’t
bother backtracking in search of other matches. We can
force backtracking by making the rule fail. Edit the
nicelangs rule as follows and reconsult the saved source
file:
nicelangs :-
language(L),
trendylang(L),
fail.
Now it produces the same erroneous
list of results which you obtained when you entered the
query at the prompt. We need to tell Prolog that, once
one of the alternative trendylang rules succeeds, it
should stop looking for other matches. This is where
the cut comes into the picture. By placing a cut after
a piece of code, we instruct Prolog to stop looking for
further solutions if it has been successful up to that
point. So, in the first rule, this is how we can tell
Prolog that, if it has already verified that Reba likes
the language, L, it should stop looking for more solutions:
trendylang( L ) :-
likes(reba,L),
!,
write(L),write( ' is trendy'),nl.
I ’ve edited this rule in the proglang3 sample
file, placing the cut as shown. Now, only if this first
rule succeeds, will the second rule be evaluated. If
the second rule succeeds, we once again need to tell
Prolog to stop searching, thereby preventing it from
making a contradictory match in our catch-all third rule.
I have also edited this second rule by placing another
cut as shown:
trendylang( L ) :-
likes(garth,L),
!,
write(L),write( '
is trendy'),nl.
Consult proglang3.pro (or proglang3.pl) and once again
enter this goal:
nicelangs.
This time you should see that Prolog lists each language
once only and does not contradict itself by stating that
some languages both are and are not trendy. You might
want to experiment further by attempting to write a new
rule that returns a list comprising only those languages
that are found to be trendy, omitting all others.
Effective use of the cut is one of the most difficult
skills to learn in Prolog. Adding cuts can both improve
the efficiency of your code and prevent illogical contradictions
such as those in the example above. However, in a long
rule, the precise position of a cut can have differing
effects. Moreover, cuts can obscure the meaning of your
code. Look upon them as a necessary evil and use them
with extreme caution.
If you are getting unexpected results when you
run a program, the built-in tracing may help you
to solve your problems. PIE’s tracing features
are fairly rudimentary. When you select Trace
Calls from the Engine menu,
the Dialog shows which predicates are called and
the values they return....
PIE has a simple debug trace mechanism
SWI-Prolog, on the other
hand, has an interactive debugger. You must
first select this from the Debug menu
then issue a call to trace at the prompt. Below,
for example, I’ve
entered
trace, trendylang(X).
The debugger loads into a popup window
and highlights each code line as it is executed.
You can control the trace using buttons at
the top of the debugger window.
SWI-Prolog provides
a more visual debugger
|
Prolog is
good at managing lists of data. In Prolog a list is represented
as a series of items separated by commas. For example,
this is a list of integers and strings:
[1,2,3,'a','b','c']
The first item or Head of a list can be distinguished
from the remainder or Tail, using an upright bar character
in this way:
[Head | Tail]
This provides us with an easy way of processing each
item in a list by dealing with the Head then recursively
processing the Tail. For example, this is how we might
test an item, X, for membership of a list:
amember(X, [ X|Tail ] ).
amember(X, [ Head|Tail
] ) :-
amember(X, Tail ).
Here the first statement instantiates X to the first
item in a list. The second rule decomposes the list into
a Head and Tail and recursively calls the amember rule,
passing to it a list formed from the Tail. Try it out.
Consult the lists.pro (or lists.pl) file and enter these
goals:
amember(2,[1,2,3]).
amember(4,[1,2,3]).
amember(X,[1,2,3]).
SWI-Prolog comes with a large number of built-in
list processing predicates including member/2 (essentially
the same as our amember/2). Refer to its help system
for a full list of its built-in predicates.
We shall be looking in more detail at the Prolog languages
and development tools soon…
There is another beginner’s tutorial on PIE Prolog
here:
http://www.visual-prolog.com/vip6/Tutorial/tut04/default.htm.
For more information, on SWI-Prolog, refer to its interactive
FAQ:
http://gollem.science.uva.nl/twiki/pl/bin/view/FAQ/WebHome.
To go further with Prolog, you may find these tutorials
useful:
http://www.cse.ucsc.edu/classes/cmps112/Spring03/languages/prolog/PrologIntro.pdf
http://cs.wwc.edu/~cs_dept/KU/PR/Prolog.html
http://kti.ms.mff.cuni.cz/~bartak/prolog/
September 2005 |