Home
Archives
About us...
Advertising
Contacts
Site Map
 

ruby in steel

 

FIRST STEPS IN PROLOG

Explore the fascinating world of ‘logic programming’ with our simple hands-on guide to Prolog
by Huw Collingbourne

Requirements:
A Prolog system such as PIE or SWI-Prolog

 

Download The Source Code:
prolog_src.zip

 

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
   /* execute Rule1’s code */
else if (Rule2=true) then
  /* execute Rule 2’s code */
else
  /* execute Rule 3’s code */

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.

Debugging

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

 


Head Or Tail...?

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…


Going Further…

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

 


Home | Archives | Contacts

Copyright © 2006 Dark Neon Ltd. :: not to be reproduced without permission