Prolog: Symbolic Programming or Programation Logique

Prolog stands for Programation Logique.

Prolog is a general-purpose logic programming language associated with artificial intelligence and computational linguistics. Prolog has its roots in first-order logic, which is a formal logic, and unlike many other programming languages, Prolog is intended as primarily a declarative programming language: the program logic is expressed in terms of relations, represented as facts and rules. A computation is initiated by running a query over these relations.

Prolog implements the predicate calculus via Horn clauses. The inference mechanism of Prolog is based upon Robinson's resolution principle (1965) together with mechanisms for extracting answers proposed by Green (1968). These ideas came together forcefully with the advent of linear resolution procedures. Explicit goal-directed linear resolution procedures, such as those of Kowalski and Kuehner (1971) and Kowalski (1974), gave impetus to the development of a general purpose logic programming system.

A logic program is a set of axioms, or rules, defining relations between objects. A computation of a logic program is a deduction of consequences of the program. A program defines a set of consequences, which is its meaning. The art of logic programming is constructing concise and elegant programs that have the desired meaning.

The meaning of a logic program P, M(P), is the set of ground goals deducible from P.


Prolog was one of the first logic programming languages, and remains the most popular among such languages today, with several free and commercial implementations available. The language has been used for theorem proving, expert systems, term rewriting, type inference, and automated planning, as well as its original intended field of use, natural language processing. Modern Prolog environments support the creation of graphical user interfaces, as well as administrative and networked applications.

Prolog is well-suited for specific tasks that benefit from rule-based logical queries such as searching databases, voice control systems, and filling templates.

Why Prolog?

Much has been written about the wonders of Prolog as a declarative programming language and its strength as a language for implementing artificial intelligence (AI) applications. A look under the hood of both AI and Prolog reveals why.

Prolog features a collection of search and pattern- matching algorithms. This, it turns out, is the essence of much AI programming. A chess program searches for patterns in the game, a natural language program searches for patterns in lists of words, and a diagnostic program searches for rules that match symptoms. Prolog is very good at pattern matching and search.

Two features in a programming language that make pattern-matching easier are 1) support for symbols as a primitive data type that can be manipulated without the need to call special functions, and 2) dynamic memory management so the developer can simply use the symbols without worrying about memory allocation issues. Languages that have these features, such as Prolog and LISP, are called symbolic languages.


Consider, for example, a simple control loop that reads a command from a user and then does something. In C that code might look like

void main()
{
  char buf[20];
  do {
     gets(buf);
     if (0 == strcmp(buf, "open")) ...
     else if (0 == strcmp(buf, "add")) ...
     else if (0 == strcmp(buf, "delete")) ...
  } while (strcmp(buf, "quit"));
  printf("done");
}

In Prolog, dynamically allocated symbols are used instead of character strings, so the equivalent code looks like


    main :-
      repeat,
      read(X),
      do(X),
      X == quit,
      write(done).

    do(open) :- ...
    do(add) :- ...
    do(delete) :- ...

Notice the lack of data definition statements or string compares. The difference is not significant in a simple example such as this, but in applications where the bulk of the work is comparing symbols, it becomes quite significant.

In addition to dynamically allocated symbols, Prolog has, as an integral part of the language, a sophisticated pattern matching algorithm, called unification, and a search mechanism, called backtracking.

These can both be seen in the code fragment above. The pattern do(X) is unified against the first of the three do rules, defined by the :- symbol meaning if. If the user entered open then the first clause, as it's called, would match and the code on the right of the :- would be executed. If the user entered something other than open, Prolog would backtrack and continue to look for a do rule that does match the user's input.

Similarly, the use of the repeat and X == quit in the main rule causes the sample section of code to loop until the user types quit. The Prolog programmer doesn't write if-then-elses, calls, whiles, or other flow-of-control constructs, however, between unification and backtracking the programmer can induce any flow-of-control behavior in a Prolog program that can be achieved in any other language.

Symbols, unification, backtracking, and dynamic memory management, all tend to eliminate procedural code a programmer normally needs to write. It is no surprise that what is left looks much more declarative than conventional code and is often a fraction of the size. For example, Prolog can be used to analyze simple English sentences. In fact Prolog was originally designed for working with language. As such it is well suited for not just natural language work, but for implementing and/or experimenting with formal languages as well.

One common use in this line is to build shells for implementing expert systems. The shells use their own language to represent the knowledge for a particular type of problem. Diagnostic systems, for example, work differently from configuration systems.

A tax application in Prolog is simply a collection of rules that specify how to fill out each line of the form. Prolog's search and unification do the work of linking related lines of the form together so the program code winds up looking very much like the tax form itself. Business applications such as customer order entry are expressed as relationships describing the various transactions and constraints. Pricing and configuration, two difficult components of such a system, are relatively straight-forward when coded in Prolog.

While C can be used to write anything written in Prolog, Prolog code, for the applications it does best, is much less complex. For this reason, the Prolog developer can manage and maintain a greater degree of complexity in an application, thus providing a greater degree of sophistication to the user of an application.

For commercial applications, this leads to a competive edge, but the real benefit applies to any application-- Prolog is simply fun.

How Prolog is Used

The examples above provide insight into how a Prolog program is used. The grammar rules, for example, are a Prolog program. They are queried with lists of words. All Prolog programs are similar collections of rules, and are similarly activated by being queried, much the same way a database is queried.

This is true even of stand-alone compiled Prolog programs. In that case there is usually a single query, main, used to start the program. main is the name of a rule which then queries other rules which query other rules and so on. For example, the following Prolog program could be compiled and executed, with a classic result.

main :- write('Hello World').

It could also be loaded and run from an interpreter.

?- main.
Hello World

Starting Prolog

Consider the following problem description:

Three friends come first, second, and third in a tournament. Each of them has a different first name, likes a different sport and has a different nationality.

Michael likes basketball and did better than the American. Simon, the Israeli, did better than the tennis player. The cricket player came first.

A prolog shell may be started for the user-programmer to work interactively. Alternatively, you may cause a shell to read a database file, for instance by issueing consult(my_database). Alternatively, type [my_database].

Warning: The database name must be stripped of its extension

The Building Blocks of a Prolog Program

Facts

We first discuss facts about objects. Suppose we want to tell Prolog the fact that John likes Mary. This fact consists of two objects, called Mary and John, and a relationship or predicate, called likes. In Prolog, we need to write facts in a standard form, like this:

likes(john, mary).

The following things are important:

  • The names of all relationships and objects must begin with a lower-case letter. For example, likes, john, mary.
  • The relationship is written first, and the objects are written separated by commas and enclosed in round brackets.
  • The dot character . must come at the end of a fact. The dot is what some people also call a period or a full stop.

Questions

Once we have some facts, we can ask some questions about them. In Prolog, a question looks just like a fact, except that we put a special symbol before it. The special symbol is written as a question mark followed by a hyphen. Consider the question:

?- owns(mary, book).

If we interpret mary to be a person called Mary, and book to be some particular book, this question is asking Does Mary own the book?, or Is it a fact that Mary owns the book? We are not asking whether she owns all books, or books in general, because the first letter of book is lower case.

When a question is asked of a Prolog system, it will search through the database. It looks for facts that unify the fact in the question. Two facts unify if their predicates are the same (spelled the same way), and if their corresponding arguments each are the same. If Prolog finds a fact that unifies with the question, Prolog will respond yes. If no such fact exists in the database, Prolog will respond no.

Rules

In Prolog, rules are used when you want to say that a fact depends on a group of other facts. Rules are also used to express definitions. A rule is a general statement about objects and their relationships. For example, we can say that Fred is a bird if Fred is an animal and Fred has feathers, and we can also say that Bertram is a bird if Bertram is an animal and Bertram has feathers. So, we can allow a variable to stand for a different object in each different use of the rule.

In Prolog, a rule consists of a head and a body. The head and body are connected by the symbol :-, which is made up of a colon and a hyphen. The :- is pronounced if.

That John likes whoever likes wine is written in Prolog as:

likes(john, X) :- likes(X, wine).

Notice that rules also end with a dot (actually a period or full stop character). The head of this rule is likes(john, X). The head of the rule describes what fact the rule is intended to define. The body, in this case likes(X, wine), describes the conjunction of goals that must be satisfied, one after the other, for the head to be true. For example, we can make John more choosy about whom he likes, simply by adding more goals onto the body, separated by commas:

likes(john, X) :- likes(X, wine), likes(X, food).

or, in words, John likes anyone who likes wine and food. Or, suppose John likes any female who likes wine:

likes(john, X) :- female(X), likes(X, wine).

And so on.

Input, Output, and Input-Output Arguments to Predicates in Prolog

An argument may be either of them three and change its role according to the situation, for there is nothing in the syntax of prolog to indicate which.

Functors or Structures

A structure is written in Prolog by specifying its functor and its components. The functor names the general kind of structure, and corresponds to a datatype in an ordinary programming language. The components are enclosed in round brackets and separated by commas. The functor is written just before the opening round bracket. Consider the following fact, that John owns the book called Wuthering Heights, by Emily Bronte:

owns(john, book(wuthering_heights, bronte)).

Inside the owns fact we have a structure by the name of book, which has two components, a title and an author. Since the book structure appears inside the fact as one of the fact's arguments, it is acting as an object, taking part in a relationship. If we like, we can also have another structure for the author's name, because there were three Bronte writers we wish to distinguish:

owns(john, book(wuthering_heights, author(emily, bronte))).

Structures may participate in the process of question-answering using variables. For example, we may ask if John owns any book by any of the Bronte sisters:

?- owns(john, book(X, author(Y, bronte))).

And so on.

If you have guessed that the syntax for structures is the same as for Prolog facts, you are correct. A predicate (used in facts and rules) is actually the functor of a structure. The arguments of a fact or rule are actually the components of a structure. There are many advantages to representing Prolog programs themselves as structures. It is not important to know why just now, but do keep in mind that all parts of Prolog, even Prolog programs themselves, are made up of constants, variables, and structures.

Horn Clauses

In mathematical logic and logic programming, a Horn clause is a logical formula of a particular rule-like form which gives it useful properties for use in logic programming, formal specification, and model theory. Horn clauses are named for the logician Alfred Horn, who first pointed out their significance in 1951.

The Wikipedia

In logic programming, a Horn clause is an implication from a conjunction of none, one or several goals.

Jess will come if it is sunny AND her car starts, AND her mother is not ill.

This might be coded in prolog like this:

come(jess) :- sunny, start(car(jess)), not ill(mother(jess)).

A Simple Database of Prolog Facts and Rules

This is a simple database to establish if two people are friends:

friends(X,Y) :- likes(X,Z), likes(Y,Z).
likes(john,gin).
likes(peter,gin).
likes(andy,beer).

Symmetric Relationships in Prolog

The following database and program lead off to infinite loops when consulted (has_met(X,a).):

% database:
has_met(a,b).
has_met(a,c).
% rules:
has_met(X,Y) :- has_met(Y,X).

The following one, though, behaves well:

% forward 'has_met' data base:
has_met_fwd(a,b).
has_met_fwd(a,c).
has_met(X,Y) :- has_met_fwd(X,Y) ; has_met_fwd(Y,X).

Alternatively:

% forward 'has_met' data base:
has_met_fwd(a,b).
has_met_fwd(a,c).
% rules:
has_met(X,Y) :- has_met_fwd(X,Y).
has_met(X,Y) :- has_met_fwd(Y,X).

Which obligingly yields:

| ?- has_met(X,a).
X = b ? ;
X = c
yes

Transitivity

Here is a slightly more complicated example which codes some transitivity.

% database:
tr(a,b).
tr(b,c).
tr(c,d).
tr(bb,c).
tr(d,e).
tr(e,f).
% rules:
transitive(X,Y) :-           tr(X,Y).
transitive(X,Z) :-           tr(X,Y), transitive(Y,Z).

And this is the result of a difficult query:

| ?- transitive(X,f).
X = e ? ;
X = a ? ;
X = b ? ;
X = c ? ;
X = bb ? ;
X = d ? ;
no

Equivalence

Next, we would like to code an equivalence relationship.

            

...

            

...

            

...

Lists in Prolog

Writing a list to standard output is a nice exercise for understanding both lists and recursive control:

writelist([]).
writelist([H|T]) :- write(H),nl,writelist(T).

Membership in Prolog

Membership is established through recursion. I don't like it, though.

member(X,[X|_]).
member(X,[_|T] :- member(X,T).

Appending

The arguments to non-built-in append/3 predicate are the initial sublist and the remaining sublist of a list, which is the third argument. It may be defined as:

append([], X, X).
append([A|B], C, [A|D]) :- append(B, C, D).

Here is an example of execution of a query (find the lists X and Y such that the concatenation of X and Y is [a,b]):

| ?- append(X,Y,[a,b,c]).
X = []
Y = [a,b,c] ? ;
X = [a]
Y = [b,c] ? a % here the user presses a to compute all remaining solutions
X = [a,b]
Y = [c]
X = [a,b,c]
Y = []
no

Backtracking

Briefly, the backtracking algorithm examines each predicate specification in the order that it was placed in the Prolog knowledge base. If the variable bindings of the specification satisfy the query it accepts them. If they don't, the interpreter goes on to the next specification. If the interpreter runs into a dead end, i.e., no variable substitution satisfies it, then it backs up looking for other variable bindings for the predicates it has already satisfied.

Backtracking: Cuts and Repeats

We can imagine a conjunction of goals to be arranged from left to right, separated by commas. When handling such a conjunction of goals, Prolog attempts to satisfy each goal in turn, working from left to right. If a goal becomes satisfied, Prolog leaves a place-marker in the database that is associated with the goal. Think of this as drawing an arrow from the goal to the place in the database where the solution is. Furthermore, any variables previously uninstantiated might now be instantiated. If a variable becomes instantiated, all its occurrences in the question become instantiated, too. Prolog then attempts to satisfy the goal's right-hand neighbour, starting from the top of the database.

As each goal in turn becomes satisfied, it leaves behind a place-marker in the database (draws another arrow from the goal to the unifying fact), in case the goal needs to be re-satisfied at a later time. Any time a goal fails (cannot find a unifying fact), Prolog goes back and attempts to satisfy its left-hand neighbour, starting from its place-marker. Furthermore, Prolog must uninstantiate any variables that became instantiated at this goal. In other words, Prolog must undo all the variables when it re-satisfies a goal. If each goal, upon being entered from its right, cannot be re-satisfied, then the failures will cause Prolog to gradually creep to the left as each goal fails. If the first goal (the left-most goal) fails, then, since it does not have a left-hand neighbour it can attempt to re-satisfy, the entire conjunction fails. This behaviour, where Prolog repeatedly attempts to satisfy and re-satisfy goals in a conjunction, is called backtracking.

The Use of Cut to Prevent Backtracking in Prolog

Sometimes it is desirable to selectively turn off backtracking. Prolog provides a predicate that performs this function. It is called the cut, represented by an exclamation mark (!).

The cut effectively tells Prolog to freeze all the decisions after it occurs in a predicate. That is, if required to backtrack, Prolog will automatically fail without trying other alternatives.

The following database and query seems to fail:

related(X,Y) :- related(X,Z), related(Z,Y).
related(phil, roxanne).
related(roxanne, carol).
related(carol, alex).

Followed by the query:

related(phil,carol).

Performance is the main reason to use the cut. This separates the logical purists from the pragmatists. Various arguments can also be made as to its effect on code readability and maintainability. It is often called the goto of logic programming.

You will most often use the cut when you know that at a certain point in a given predicate, Prolog has either found the only answer, or if it hasn't, then there is no answer. In this case you insert a cut in the predicate at that point.

Similarly, you will use it when you want to force a predicate to fail in a certain situation, and you don't want it to look any further.

Some Examples of the Use of Cuts

The following program defines the minimum predicate with cuts:

minimum(X, Y, X) :- X =< Y, !.
minimum(X,Y,Y) :- X > Y, !.

...

repeat

The built-in predicate repeat is provided as an extra way to generate multiple solutions through backtracking. Although it is built-in, it can be thought of as behaving as though defined as follows:

repeat.
repeat :- repeat.

What is the effect of this if we put repeat as a goal in one of our rules? First of all, the goal will succeed, because of the fact which is the first clause of repeat. Secondly, if backtracking reaches this point again, Prolog will be able to try an alternative: the rule that is provided as the second clause of repeat. When it uses this rule, another goal repeat is generated. Since this matches the first fact, we have succeeded again. If backtracking reaches here again, Prolog will again use the rule where it used the fact before. To satisfy the extra goal generated, it will again pick the fact as the first option. And so on. In fact, the goal repeat will be able to succeed infinitely many times on backtracking. Note the importance of the order of the clauses here. (What would happen if the fact appeared after the rule?).

Why is it useful to generate goals that will always succeed again on backtracking? The reason is that they allow one to build — from rules that have no choices in them — rules that do have choices. And we can make them generate different values each time.

Loops with repeat/0

Furthermore, a clause body with a repeat/O followed by fail/O will go back and forth forever. This is one way to write an endless loop in Prolog.

A repeat/O followed by some intermediate goals followed by a test condition will loop until the test condition is satisfied. It is equivalent to a do until in other languages.

command_loop :-
  repeat,
  write('Enter command (end to exit): '),
  read(X),
  write(X), nl,
  X = end.

Unification, the Engine for Predicate Matching and Evaluation

When queried, a prolog program performs a series of resolutions on its database entries, rather than sequentally evaluating statements and expressions like a traditional language. This has an important ramification: variables are bound (assigned values, instantiated,...) by unification and not by evaluation unless an evaluation is explicitly requested.

One consequence of this is the relaxation of the requirement to specify variables (function parameters) as input or output.

Unification is a powerful technique for rule-based and frame-based expert systems, where it matches case-specific data to the rules that best meet these specifications. All production systems require a form of this matching, and it is often necessary to write a unification algorithm in languages that don't provide one.

Forcing Evaluation with Operator is

An important difference between unification-based computing and the use of more traditional languages is that unification performs syntactic matches (with appropriate parameter substitution) on structures. It does not evaluate expressions.

Prolog provides an operator, is, that performs arithmetic evaluation. It evaluates the expression on its right-hand side and attempts to unify the result with the object on its left. Thus,

X is Y + Z

unifies X with the value of Y added to Z. Because it performs arythmetic evaluation:

  • If Y and Z do not have values (are not bound at execution time) evaluation of the is causes a run-time error. Therefore
  • X is Y + Z cannot (as one might think with a declarative programming language) assign a value to Y when X and Z are bound.
  • Programs must use is to evaluate expressions containing arithmetic operators, +, -, *, /, and mod.

Finally, as in the predicate calculus, variables in prolog may have one and only one binding. Once given a value, through local assignment or unification, they can never take on a new value, except through a backtrack in the and/or search space of the current interpretation. Thus, is does not function like a traditional assignment operator; an expression such as X is X + 1 will always fail.

Conjunction, disjunction, if-then

These are realized through operators ,, ;, and ->.

The Gory Details

Goal1 , Goal2 executes Goal1 and, in case of success, executes Goal2.

Goal1 ; Goal2 first creates a choice-point and executes Goal1. On backtracking Goal2 is executed.

Goal1 -> Goal2 first executes Goal1 and, in case of success, removes all choice-points created by Goal1 and executes Goal2. This control construct acts like an if-then (Goal1 is the test part and Goal2 the then part). Note that if Goal1 fails then ->/2 fails also. ->/2 is often combined with ;/2 to define an if-then-else as follows: Goal1 -> Goal2 ; Goal3. Note that Goal1 -> Goal2 is the first argument of the (;)/2 and Goal3 (the else part) is the second argument. Such an if-then-else control construct first creates a choice-point for the else-part (intuitively associated with ;/2) and then executes Goal1. In case of success, all choice-points created by Goal1 together with the choice-point for the else-part are removed and Goal2 is executed. If Goal1 fails then Goal3 is executed.

Negation by Failure in Prolog

A goal G fails (or not g succeeds), if G cannot be derived from the consulted database.

Here is how we might define operator not:

not X :- X, !, fail.
not X.

...

Prolog Programming Styles

There are two basic styles of developing logic programs: defining a logical database, and manipulating data structures.

A logic database comprises a set of facts and rules. A set of facts can define relations, as in relational databases. And rules can define complex relational queries, as in relational algebra.

Data structures in Prolog are based on lists.

Prolog Operators

Sometimes it is convenient to write some functors as operators. This is a form of syntax that makes some structures easier to read. For example, arithmetic operations are commonly written as operators. When we write the arithmetic expression x + y * z, we call the plus sign and the multiply sign operators. If we had to write the arithmetic expression x + y*z in the normal way for structures, it would look like this: +(x,*(y,z)), and this would be a legal Prolog term.

(The same would be expressed in functional notation as add(x,multiply(y,z)) given a previous definition of add and multiply.)

The operators are sometimes easier to use, however, because we have grown accustomed to using them in arithmetic expressions ever since our schooldays. Also, the structure form requires that round brackets be placed around the functor's components, which may be awkward at times.

It is important to note that the operators do not cause any arithmetic to be carried out. So in Prolog, 3+4 does not mean the same thing as 7. The term 3+4 is another way to write the term +(3,4), which is a data structure.

First we need to know how to read arithmetic expressions that have operators in them. To do this, we need to know three things about each operator: its position, its precedence, and its associativity. In this section we will describe how to use Prolog operators with these three things in mind, but we will not go into very much detail. Although many different kinds of operators can be made up, we shall deal only with the familiar atoms +, -, *, and /.

The precedence of an operator is used to indicate which operation is carried out first. Each operator that is used in Prolog has a precedence class associated with it. The precedence class is an integer that is associated with an operator. The exact value of the integer depends on the particular version of Prolog you are using. However, it is always true that an operator with a higher precedence has a precedence class that is closer to 1. If precedence classes range from 1 to 255, then an operator in the first precedence class is carried out first, before operators belonging to the 129th (say) precedence class. In Prolog the multiplication and division operators are in a higher precedence class than addition and subtraction, so the term a-b/c is the same as the term -(a,/(b,c)). The exact association of operators to precedence classes is not important at the moment, but it is worth remembering the relative order in which operations are carried out.

Finally, consider how different operators associate. How they associate comes to our attention when we have several operators of the same precedence. When we see the expression 8/2/2, does this mean (8/2)/2 or 8/(2/2)? In the first case, the expression could be interpreted to mean 2, and in the second case, 8. To be able to distinguish between these two cases, we must be able to tell whether an operator is left associative or right associative. A left associative operator must have the same or lower precedence operations on the left, and lower precedence operations on the right. For example, all the arithmetic operations (add, subtract, multiply, and divide) are left associative. This means that expressions like 8/4/4 are parsed as (8/4)/4. Also, 5+8/2/2 is read as 5+((8/2)/2).

Equality and Unification

There really is no equivalent of the assignment in prolog, and you should not use the dynamic database for this purpose.

One noteworthy built-in predicate is equality, which is an infix operator written as =. When an attempt is made to satisfy the goal

?- X = Y.

(pronounced X equals Y), Prolog attempts to unify X and Y, and the goal succeeds if they unify. We can think of this act as trying to make X and Y equal. The equality predicate works as though it were defined by the following fact:

X = X.

Strict Equality (X == Y)

The predicate == represents a much stricter equality test than =. That is, if X == Y ever succeeds then X = Y does as well. On the other hand, this is not so the other way round. The way that == is more strict is by the way it considers variables. The = predicate will consider an uninstantiated variable to be equal to anything, because it will match anything. On the other hand, == will only consider an uninstantiated variable to be equal to another uninstantiated variable that is already sharing with it. Otherwise the test will fail. So we get the following behaviour:

?- X == Y.
no
?- X == X.
X = _23
?- X=Y, X==Y.
X = _23, Y = _23
?- append([A|B], C) == append(X, Y).
no
?- append([A|B], C) == append([A|B], C).
A = _23, B = _24, C = _25
<kbd/>
<kbd/>

Arithmetic in Prolog

Arithmetic can be used for calculating. For example, if we know the population and ground area of a country, we can calculate the population density of the country. The population density tells us how crowded the country would be if all the people were evenly spread throughout the country. Consider the following database about the population and area of various countries in 1976. We will use the predicate population to represent the relationship between a country and its population. Nowadays, the populations of countries are generally quite large numbers. So, we will represent population figures in millions: population(X, Y) means the population of country X is about Y million people. The predicate area will denote the relationship between a country and its area (in millions of square miles). The numbers given here are not exact, but they will do for the purpose of demonstrating arithmetic:

population(usa, 203).
population(india, 548).
population(china, 800).
population(brazil, 108).
area(usa, 3).
area(india, 1).
area(china, 4).
area(brazil, 3).

Now to find the population density of a country, we must use the rule that the density is the population divided by the area. This can be represented as the predicate density, where the goal density(X,Y) succeeds for country X having Y as the population density of that country. A Prolog rule for this is:

density(C, Y) :-
  population(C, P),
  area(C, A),
  Y is P / A.

We need to use built-in infix predicator is to cause evaluation and then unification.

Standard Arithmetic Operators in Prolog

Depending on what computer you use, various arithmetic operators can be used

on the right-hand side of the is operator. All Standard Prolog systems, however, will have the following, as well as many more:
  • X + Y: the sum of X and Y
  • X - Y: the difference of X and Y
  • X * Y: the product of X and Y
  • X / Y: the quotient of X divided by Y
  • X // Y: the integer quotient of X divided by Y
  • X mod Y: the remainder of X divided by Y

Numerical Comparison Operators in Prolog

Given two numbers, we can tell whether one number is equal to the other, or less than the other, or greater than the other. Prolog provides certain built-in predicates for comparing numbers. Actually these predicates evaluate terms that are treated as arithmetic expressions. The arguments could be variables instantiated to integers, or they could be integers written as constants, or they could be more general expressions. Here we will use these predicates just for comparing numbers, but later we'll use them in general arithmetic expressions. Note that we are allowed to write them as infix operators:

  • X =:= Y: X and Y stand for the same number
  • X =\= Y: X and Y stand for different numbers
  • X < Y: X is less than Y
  • X > Y: X is greater than Y
  • X =< Y: X is less than or equal to Y
  • X >= Y: X is greater than or equal to Y

Note that the less than or equal to symbol is not written as <= as in many programming languages. This is done so that the Prolog programmer is free to use the <= atom, which looks like an arrow, for other purposes.

<kbd>X=Y</kbd>

The equality predicate, when applied on numbers, succeeds when two number arguments are the same. However, if one of the arguments is a variable, the equality predicate will cause the variable to be instantiated because the equality predicate performs a unification of its two arguments. In many numeric calculations, this is not desirable. Instead, Prolog makes available predicates specifically for comparing equality and inequality of numbers. In all of the following predicates, both arguments must be instantiated, or an error occurs. Using these predicates for numeric calculation can also cause the program to be executed more efficiently.

<kbd>=:=</kbd>

This numeric equality predicate succeeds when the left-hand number argument is equal to the right-hand number argument.

<kbd>=\=</kbd>

This numeric equality predicate succeeds when the left-hand number argument is not equal to the right-hand number argument.

Comparing Arbitrary Terms

They start with character @: @<, @>, @>=, and @=<.

Here are the principles that determine whether one term is considered less than another:

  • All uninstantiated variables are less than all floating-point numbers, which are less than all integers, which are less than all atoms, which are less than all structures.
  • For two non-sharing uninstantiated variables, one will be less than the other (which one is less may be different in different Prolog implementations). A floating point number is less than another floating point number or an integer is less than anothe integer in the expected way.
  • One atom is less than another if it would come earlier than it in the normal dictionary ordering. To be precise, the ordering depends on the character codes, but these are usually ordered as one would expect, at least for alphabetic characters.
  • One structure is less than another if its functor has a lower arity. If two structures have the same arity, one is less than the other if its functor is less than the other (using the ordering for atoms). If two structures have the same arity and functor, they are ordered by considering the arguments in turn – for the first corresponding arguments that differ, the order of the structures is the order of the relevant arguments.

Declaring Operators with Predicate op/3

In Prolog, if we wish to declare that an operator with a given position, precedence class, and associativity is to be recognised when terms are read and written, we use the built-in predicate op. If Name is the desired operator (the atom that we want to be an operator), Prec the precedence class (an integer within the appropriate range), and Spec the position/associativity specifier (one of the above atoms), then the operator is declared by providing the following goal:

?- op(Prec, Spec, Name).

As an example of declaring operators, the following is a list of the most important operators that are already defined in Standard Prolog:

?- op( 1200, xfx, &apos;:-&apos; ).
?- op( 1200, fx, '?-' ).
?- op( 1200, fx, ':-' ).
?- op( 1100, xfy, ';' ).
?- op( 1000, xfy, ',' ).
?- op( 900, fy, '\+' ).
?- op( 700, xfx, '=').
?- op( 700, xfx,\=).
?- op( 700, xfx, '==' ).
?- op( 700, xfx, \== ).
?- op( 700, xfx, '=..' ).
?- op( 700, xfx, '<' ).
?- op( 700, xfx, '>' ).
?- op( 700, xfx, '=<' ).
?- op( 700, xfx, '>=' ).
?- op( 700, xfx, '@<' ).
?- op( 700, xfx, '@=<' ).
?- op( 700, xfx, '@>' ).
?- op( 700, xfx, '@>=' ).
?- op( 700, xfx, 'is' ).
?- op( 500, yfx, '+' ).
?- op( 500, yfx, '-' ).
?- op( 400, yfx, '*' ).
?- op( 400, yfx, '//' ).
?- op( 400, yfx, '/' ).
?- op( 400, yfx, 'mod' ).
?- op( 200, fy, '-').

...

An operator declared yfx is left associative. Similarly, an operator declared xfy is right associative.

Note that the meanings of x and y (in terms of what other operators can appear unbracketed in the relevant position) are the same in all the other cases as well. This means that, for instance, the sequence

not not a

is legal syntactically if not is declared as fy, but is illegal if it is declared fx.

...

File I/O in Prolog

Prolog Input and Output

Here are some predicates for reading from the input stream and writing to the output stream:

Reading and writing one character

The goal get_char(X) succeeds if X can be matched with the next character encountered on the current input stream. get_char succeeds only once (it cannot be re-satisfied). The operation of moving to the next character is not undone on backtracking, because there is no way to put a character back onto the current input stream.

The goal put_char(X) writes the character X on the current output stream. put_char succeeds only once. An error occurs if X is not instantiated.

Reading and Writing a Term

The goal read(X) reads the next term from the current input stream and matches it with X. A read succeeds only once. The term must be followed by a dot ., which does not become a part of the term, and at least one non-printing character. The dot is removed from the current input stream

The goal write(X) writes the term X to the current output stream. write succeeds only once. Any uninstantiated variables in X are written as uniquely numbered variables beginning with an underscore, such as _239. Co-referring variables within the same argument to write have the same number when they are printed out. The predicate write takes account of current operator declarations when it prints a term. Thus an infix operator will be printed out between its arguments, for instance.

The predicate write_canonical works in exactly the same way as write, except that it ignores any operator declarations. When write_canonical is used, any structure is printed out in prefixed notation with the functor first and the arguments in brackets afterwards.

Effecting a New Line

The goal nl writes a control sequence to the current output stream that causes a new line. On a computer display, all characters after the use of nl appear on the next line of the page. nl succeeds only once.

Handling Files

The following predicates open and close files, get file handles, and set current input and output streams.

<kbd>open(X, Y, Z)</kbd>

This goal opens a file whose name is X (an atom). If Y is read then the file is opened for reading; otherwise if Y is write then the file is opened for writing. Z is instantiated to a special term naming the stream that must be referred to when the file is accessed later. An error occurs if X is not instantiated, or if X names a file that does not exist.

<kbd>close(X)</kbd>

This is used when X is a term naming a stream. The stream is closed and can no longer be used.

<kbd>set_input(X)</kbd>

Sets the current input to the stream whose name is provided by X. X will be a term returned in the third argument of open, or the atom user_input, which specifies that input is to come from the keyboard.

<kbd>set_output(X)</kbd>

Sets the current output to the stream whose name is provided by X. X will be a term returned in the third argument of open, or the atom user_output, which specifies that output is to go to the computer display.

<kbd>current_input(X)</kbd>

This goal succeeds if the name of the current input stream matches with X, and fails otherwise.

<kbd>current_output(X)</kbd>

This goal succeeds if X matches with the name of the current output stream, and fails otherwise.

Prolog Meta-Predicates

These predicates query and manipulate other predicates rather than the terms or objects that the other, non-meta predicates denote. Some of them are:

Next some of these predicates will be explained further:

<kbd>clause(X,Y)</kbd>

Satisfying a goal of the form clause(X, Y) causes X and Y to be matched with the head and body of an existing clause (for a public predicate) in the database. When an attempt is made to satisfy the goal, X must be instantiated enough so that the main predicate of the clause is known. If there are no clauses for the predicate, the goal just fails. If there is more than one clause that matches, Prolog will choose the first one. In this case, if an attempt is made to re-satisfy the goal, the other matching clauses will be chosen, one at a time.

Notice that, although clause always has an argument for the body of a clause, not every clause actually has a body. If a clause does not have a body, it is considered to have the dummy body true. We have been calling such clauses facts. By providing X's and Y's that are more or less instantiated, you can look for either all the clauses for a given predicate and number of arguments, or all the ones that match some pattern.

<kbd>functor(T, F, N)</kbd>

The predicate functor is defined in such a way that functor(T,F,N) means, T is a structure with functor [name] F and arity (number of arguments) N. It can be used in basically two ways. In the first way, T is already instantiated. The goal fails if T is not an atom or a structure. If T is an atom or structure, F is matched with the functor and N is matched with the integer giving the arity (number of arguments) of the functor. Note that in this context, an atom is considered to be like a structure with arity 0.

Here are some examples of goals involving functor:

?- functor(f(a, b, g(Z)), F, N).
Z = _23, F = f, N = 3
?- functor(a + b, F, N).
F = +, N = 2
?- functor([a, b, c], F, N).
F = ., N = 2
?- functor(apple, F, N).
F = apple, N = 0
?- functor([a, b, c], '.', 3).
no
?- functor([a, b, c], a, Z).
no

There is a second use to functor. This occurs when the first argument of the goal (T) is uninstantiated. In this case, both of the others must be instantiated: specifying a functor and a number of arguments respectively. A goal of this form will always succeed, and as a result T will become instantiated to a structure with the functor and number of arguments provided. So this is a way of constructing arbitrary structures, given a specification in terms of a functor [name] and its number of arguments. The arguments of such a structure constructed by functor are uninstantiated variables. Hence the structure will match any other structure with the same functor and number of arguments.

Copying an Existing Structure with function/3

A common use of functor to create a structure is when we wish to make a copy of an existing structure with new variables as the arguments of the principal functor. We can encapsulate this use in the definition of a predicate copy, as follows:

copy(Old, New) :- functor(Old, F, N), functor(New, F, N).

Here, two functor goals occur adjacently. If the copy goal has the first argument instantiated and the second uninstantiated, then the following will happen. The first functor goal will involve the first possible use of the predicate (because the first argument will be instantiated). Hence F and N will become instantiated to the functor and number of arguments of this existing structure. The second functor goal uses the predicate in the second way. This time the first argument is uninstantiated, and the information in F and N is used to construct the structure New. This is a structure involving the same functor and number of arguments as Old, but with variables as its components. Thus we would get interactions like:

?- copy(sentence(np(n(john)), v(eats)), X).
X = sentence(_23, _24)

Accessing Arguments in a Structure Through arg(N,T,A)

The predicate arg must always be used with its first two arguments instantiated. It is used to access a particular argument of a structure. The first argument of arg specifies which argument is required. The second specifies the structure that the argument is to be found inside. Prolog finds the appropriate argument and then tries to match it with the third argument. Thus arg(N, T, A) succeeds if the Nth argument of T is A. Let us look at some goals involving arg.

?- arg(2, related(john, mother(jane)), X).
X = mother(jane)
?- arg(1, a+(b+c), X).
X=a
?- arg(2, [a,b,c], X).
X = [b,c]
?- arg(1, a+(b+c), b).
no

Sometimes we will want to use functor and arg when the possible structures are known. This is because there may be so many arguments that it is inconvenient to specify them every time. Consider an example where we use structures to represent books. We might have a component for the title, the author, the publisher, the date of publication, and so on. Let us say that the resulting structures have fourteen components. We might write the following useful definitions:

is_a_book(book(_,_,_,_,_,_,_,_,_,_,_,_,_,_)).
title(book(T,_,_,_,_,_,_,_,_,_,_,_,_,_), T).
author(book(_,A,_,_,_,_,_,_,_,_,_,_,_,_), A).

In fact, we can write these much more compactly as:

is_a_book(X) :- functor(X, book, 14).
title(X, T) :- is_a_book(X), arg(1, X, T).
author(X, A) :- is_a_book(X), arg(2, X, T).
<kbd>X =.. L</kbd>

The predicates functor and arg provide one way of creating and accessing arguments of arbitrary structures. The predicate =.. (pronounced univ for historical reasons) provides an alternative way, which is useful if you want to obtain the arguments of a structure all together, or if you want to construct a structure, given a list of arguments.

The goal X =.. L means, L is the list consisting of the functor of X followed by the arguments of X.

Such a goal can be used in two ways, in the same way that a functor goal can. If X is instantiated, Prolog constructs the appropriate list and tries to match it with L. Alternatively, if X is uninstantiated, the list will be used to construct an appropriate structure for X to stand for. In this case, the head of L must be an atom (it will become the functor of X). Here are some examples of =.. goals:

?- foo(a,b,c) =.. X.
X = [foo,a,b,c]
?- [a,b,c,d] =.. L.
L = ['.',a,[b,c,d]].
?- (a+b) =.. L.
L = [+,a,b].
?- (a+b) =.. [+,X,Y].
X = a, Y = b.
?- [a,b,c,d] =.. [X|Y].
X = '.', Y = [a,[b,c,d]]
?- X =.. [a,b,c,d].
X = a(b,c,d)
<kbd>call(<var>X</var>)</kbd>

It is assumed that X is instantiated to a term that can be interpreted as a goal. The call(X) goal succeeds if an attempt to satisfy X succeeds. The call(X) goal fails if an attempt to satisfy X fails. At first sight, this predicate may seem redundant, because one might ask why the argument of call shouldn't simply appear by itself as a goal?

For instance, the goal

..., call(member(a, X)),...

can always be replaced by

..., member(a, X),...

However, if we are constructing goals by using the =.. predicate or functor and arg, then it is possible to call goals that have a functor that is unknown at the time you type in your program.

Assuming that P, X, and Y are instantiated to a functor and arguments appropriately, call can be used as follows:

..., Z =.. [P,X,Y], call(Z),...

Negating with \+ X

The \+ predicate (pronounced not) is declared as a prefix operator. It is assumed that X is instantiated to a term that can be interpreted as a goal. The \+ X goal succeeds if an attempt to satisfy X fails. The \+ X goal fails if an attempt to satisfy X succeeds. In this way, \+ is rather like call, except that the success or failure of the argument, interpreted as a goal, is reversed.

Adding and Removing Rules

You use assert, asserta, assertz and retract to add and remove rules. They will only work on the definitions of predicates which have been declared dynamic . The intention again is to prevent accidental unplanned changing of definitions. A predicate foo/4 can be declared as dynamic by including the following in the relevant program file (before the definition of foo/4 or, if there is no definition, before the code that uses asserta etc.):

:- dynamic foo/4.

Several predicates can be declared as dynamic on one line by separating them with commas, e.g:

:- dynamic foo/4, baz/3.

Warning: These commands are deprecated because they create and remove global structures, introduce side effects and may cause other problems associated with poorly structured programs.

Working with Characters

The following two predicates compose and decompose an atom into its characters.

atom_chars(A,L)

Predicate atom_chars relates an atom to the list of characters (atoms with one element) that make it up. This can be used either to find the characters for a given atom, or to find the atom that has some given characters. The goal atom_chars(A,L) means that the characters for the atom A are the members of the list L. If the argument A is instantiated, Prolog creates the list of characters and tries to match them with L. Otherwise Prolog uses the list L to make an atom for A to stand for. Example uses of atom_chars are as follows:

?- atom_chars(apple, X).
X = [a,p,p,l,e]
?- atom_chars(X, [a,p,p,l,e]).
X = apple
<kbd>number_chars(A,L)</kbd>

This predicate is just like atom_chars except that it works with numbers, rather than atoms. Notice that in:

?- atom_chars(X, ['1' ,'2', '3']).

the variable X will be instantiated to the atom '123'. If we want it to be a number instead, we need to use number_chars. Here are some uses of number_chars:

?- number_chars(123.5, X).
X = ['1', '2', '3', '.', '5']
?- number_chars(X, ['1', '2', '3']).
X = 123

Recursive Control Loop

The purity of logic programming is undermined by the asserts and retracts of the database. Using these makes code unpredictable, since the behavior of the code depends on global data whose value might change.

Because logical variables cannot have their values changed by assignment, the commands must take two arguments representing the old state and the new state. The repeat-fail control structure will not let us repeatedly change the state in this manner, so we need to write a recursive control structure that recursively sends the new state to itself.

This style of Prolog programming is logically purer, and lends itself to certain types of applications. It also avoids the difficulties often associated with global data. On the other hand, it requires more complexity in dealing with state information in arguments, and the multiple lists and recursive routines can be confusing to debug. You will have to decide which approach to use for each application you write.

There could be serious performance problems with this approach. Prolog uses a stack to keep track of the levels of predicate calls. In the case of a recursive predicate, the stack grows at each recursive call. The stack could easily be consumed in a short period of time by the recursive control structure.

Fortunately, there is a performance feature built into most Prologs that makes these programs behave efficiently.

Tail Recursion

There are actually two kinds of recursive routines. In a true recursive routine, each level must wait for the information from the lower levels in order to return an answer. This means that Prolog must build a stack with a new entry for each level.

This is in contrast to iteration, which is more common in conventional languages. Each pass through the iteration updates the variables and there is no need for building a stack.

There is a type of recursion called tail recursion that, while written recursively, behaves iteratively. In general, if the recursive call is the last call, and there are no computations based on the information from the lower levels, then a good Prolog interpreter can implement the predicate iteratively, without growing the stack.

One classic example of tail recursion is the factorial predicate. First we'll write it using normal recursion. Note that the variable FF, which is returned from the lower level, is used in the top level.

factorial_1 (1,1).
factorial_1 (N,F):-
  N > 1,
  NN is N - 1,
  factorial_1 (NN,FF),
  F is N * FF.

It works as expected.

?- factorial_1 (5,X).
x = 120

By introducing a new second argument to keep track of the result so far, we can rewrite factorial tail-recursively. The new argument is initially set to 1. Each recursive call builds on the second argument. When the boundary condition is reached, the third argument is bound to the second argument.

factorial_2(1,F,F).
factorial_2(N, T, F) :-
  N >1,
  TT is N * T,
  NN is N - 1,
  factorial_2(N N, TT, F).

It gives the same results as the previous version, but because the recursive call is the last call in the second clause, its arguments are not needed at each level.

7- factorial_2(5,1,X).
x = 120

By introducing a new second argument which will accumulate the partial answer through levels of recursion, we can rewrite reverse. It turns out that the partial answer is already reversed when it reaches the boundary condition.

reverse{[], Rev, Rev).
reverse([HIT], Temp, Rev) :-
  reverse(T ,[H ITemp], Rev).

We can now try the second reverse.

?- reverse([ants, mice, zebras], [], X).
X = [zebras, mice, ants]