Prolog

Prolog programming is an example of declarative programming. In declarative programming, the programmer writes down the facts relevant to the problem. The system automatically deduces the solution. This is a database interpretation. Prolog has its roots in logic. So it is also an example of a logic programming language.

A quick example

Consider the simple problem of getting the directions between cities. What are the facts relevant to this problem? We can list some of facts and rules as
  1. Los Angeles is to the north of San Diego.
  2. If city X is to the south of city Y, then city Y is to the north of city X.
  3. other facts and rules.
The following is a sample Prolog program based on the above facts and rules.
north(sd, la). /* north of SD is LA */
north(la, sf). /* north of LA is SF */
north(X, Y) :- south(Y, X). /* Y is to the north of X
                               if X is to the south of Y*/
north(X, Y) :- north(X, Z), north(Z, Y). /* Y is to the north of X
                                    if Z is to the north of X and
                                       Y is to the north of Y */
south(sd, mc). /* MC is to the south of SD */
Assume that the above program is stored in a file directions. The following is the transcript of a sesssion with the interpreter.
ieng9.ucsd.edu% sbprolog
| ?- consult(directions).  /* load the program */
yes
| ?- north(sd, sf).  /* query */
yes
| ?- north(mc, sf).
yes
| ?- north(mc, X).

X = sd; /* user types ";" */

X = la;

X = sf;
Heap overflow

Running Prolog on ieng9

Type sbprolog at the prompt to start the interpreter. If your prolog code is in a file named prologCode, type consult(prologCode) to load your program. It is better to avoid . character in file names.(That is, avoid file extensions for Prolog programs.) Type control-D to exit the interpreter.

Analyzing the program

A Prolog program consists of constants, variables, predicates, facts, and rules.

A constant can be numeric, like 1, 3.14159, etc. or symbolic, like sd, sf, etc. Note that symbolic constants start with lower case alphabets.

A variable starts with an upper case letter. Examples of variables are X, City, etc. There is one special anonymous variable _.

While constants and variables are used to represent objects, predicates are used to represent relations. The difference between functions and relations are

  1. Functions are sets of tuples like <x, y> such that for each x, there is a unique y. Relations are also sets of tuples but there is no uniqueness requirement. For example, while the set
     {<rose, red>, <leaf, green>, <sky, blue>, ...}
    
    is a function, the set
     {<rose, red>, <rose, white>, <leaf, brown>, <sky, blue>, <sky, dark>, ...}
    
    is not a function. It is a relation. As you can see, it is sometimes easier to model real world observations as relations.
  2. Functions have a strong sense of input vs. output. In relations do not have this distinction.
In Prolog, relations are modeled by predicates. For example, in Prolog, the above relation is written as
color(rose, red).
color(rose, white).
color(leaf, brown).
/* others */
Here color is a predicate of two arguments. Relations listed in this form are called facts. Relations between relations can be expressed in the form of rules. For example, we can say that the color of an object is the color of its dominant part. This can be expressed as
color(X, Y) :- dominantPart(X, Z), color(Z, Y).
:- is read as "if" and , as "and". Note the use of variables to propagate bindings across predicates.

Thus a Prolog program consists of facts and rules. Rules have a head and a body. The prolog interpreter accepts query about the program and finds the solution through search.

Tracing the flow

As you can see, a Prolog program just mentions the relevant relations in the problem. How's problem solving done? According to logic programming paradigm,
       algorithm = logic + control
The programmer specifies the "logic" part (facts and rules pertaining to the problem) and the system supplies the "control" part (figuring out which rules to use logic to solve the problem). This is done by reading the rules in two ways. Suppose we have a rule north(X, Y) :- south(Y, X). There are two ways of interpreting this.
  1. "Y is to the north of X if X is to the south of Y". This is called the declarative interpretation and this is the programmer's reading of the rule.
  2. "If you want to prove/show Y is to the north of X, show that X is to the south of Y". This is the procedural interpretation. This is the reading by the system.
Given a query, the system goes on a proving spree. For this a Prolog interpreter uses essentially two ideas: pattern matching and depth first search.

Unification

The pattern matching part of Prolog is formally known as unification. The idea is simple. The pattern color(X, Y) matches with color(rose, red) with the binding X=rose, Y=red. The pattern color(X, X) does not match with the same predicate. (This requires a predicate of the form color(rose,rose).)

Pattern matching is done between a goal and the head of a rule. The bindings created by pattern matching are propagated to the body of the rule.

Search

A prolog interpreter has a database of facts and rules. It then accepts queries. Queries are made of goals. These goals are proved one by one. Each goals is matched with heads of rules in the database through unification. The unified body is now added to the front of the goal list and the interpreter proceeds recursively. The interpreter also keeps track of untried rules. If the current goals cannot be satisfied, then it tries to prove one of the tried rules. This is called backtracking.

If the query matches the head of a rule, the body of the rule is taken for proving. The following figure shows the complete search process for the query north(sd, sf).

Queries can contain variables. In this case, bindings of the variable which satisfy the query are returned. For example, the query north(sd, X) returns the bindings X = la and X = sf. (The system gets into a loop after that!)

Mere database search?

The above example appears like a database search. It is. But the power of Prolog is not restricted to database searches. It provides a general computing tool as can be seen from the following example.
append([], Y, Y).
append([H|X], Y, [H|Z]) :- append(X, Y, Z).
The following are some queries to this program.
| ?- append([1,2],[3,4],X).
X = [1,2,3,4];
no
| ?- append([1,2],[3,4],[1,2,3,4]).
yes
| ?- append(X,[3,4], [1,2,3,4]).
X = [1,2];
no
| ?- append([1],[2],[1,2,3]).
no
| ?- append(X, Y, [1, 2]).
X = []
Y = [1,2];

X = [1]
Y = [2];

X = [1,2]
Y = [];
no
This is an example of a predicate which you can use in more ways than the number of lines it contains!

Note that the order in which the facts and rules are listed and the order in which the predicates are ordered in the rule body influences the search.

Procedural constructs

The only control mechanism in Prolog is DFS. While pure DFS is works well for simple problems, it can be inefficient for solving large problems. Realworld problem solving always needs some heuristics. There are times you do not want to go forward and there are times you do not want to go backward.

Suppose you are searching a sorted list. If the head of the list is greater than the key, there is no need to continue.

search(X, [Y|Z]) :- X = Y.
search(X, [Y|Z]) :- X>Y, search(X, Z).
search(X, [Y|Z]) :- X<Y, fail.
Consider the Treasure Hunt example. If you find the Treasure, you do not want to backtrack. In this case, you can use a cut to prevent backtracking.
 findTreasure(X) :- ..., whatIs(i, X), X = treasure,!, ...
In this case, control never backtracks past cut. If for some reason the body after the cut fails, whatIs is never called again. In terms of search tree, Prolog interpreter does not keep the control information required for backtracking.

For loops

The following is an implementation of for loop.
for_loop (Index, Final, Result)
  :- Index < Final,
     body_of_loop(Result),
     NewIndex = Index + 1, !,  /* cut prevents unnecessary backtracking */
     for_loop(NewIndex, Final, Result). /* Tail recursion: can be optimized
                                           by the interpreter */
for_loop (Index, Final, Result) :- Index >= Final.

repeat loop

The following is an implementation of repeat loop.
repeat.
repeat :- repeat;

Open lists

The following example illustrates the power of unification. Consider the rule
lookup(K, V, [(K,W)|_]) :- V=W.
Suppose we give the goal
lookup(a, 1, X).
What's the result? Variable X unifies with the list [(a,1)|_]. This is a list whose last element is a variable! Such lists are said to be open. Note that the effect of this rule is to add a new element to the list. Consider the following program.
lookup(K, V, [(K,W)|_]) :- !, V=W.
lookup(K, V, [_|Z]) :- lookup(K, V, Z).
Now consider its uses.
| ?- lookup(a,1,X).

X = [(a , 1)|_1492136]
yes
| ?- lookup(a,1,X),lookup(b,2,X).

X = [(a , 1),(b , 2)|_1492368]
yes
| ?- lookup(a,1,X),lookup(b,2,X),lookup(a,Y,X).  

X = [(a , 1),(b , 2)|_1492584]
Y = 1
yes
| ?-
The interesting aspects of Prolog are
  1. Computing with relations: no input variable vs. output variable distinction for predicates.
  2. Finding constraints through unification and propagating constraints through variable bindings
  3. Builtin DFS