<html>

The following notes aims just at giving a rough (and somewhat not formal)
idea of how and why the programming language Prolog works.


Prolog is a programming language based on a fragment of 
first-order predicate logic.


Formulas of first-order logic are formed using basic predicates (and terms), 
logical connectives and quantifiers.
A basic predicate is used to represent a basic statement
about terms denoting individual elements of the domain we are taking 
into account, for instance: even(2), divisible(15,3), greaterThan(5*7,1+1) etc. .
In Prolog, basic predicates are denoted by identifiers beginning with
lower case letters.
Terms denoting elements of the domain are formed using variables,
constants and function symbols. 
In Prolog, variables are represended by identifiers
beginning with a upper-case letter, whereas constants and function symbols
are represented by identifiers starting with lower-case letters.
The following is a possible term in Prolog notation: f(Y,g(X,c)).
An example of basic statement formed using the basic binary predicate pred(_,_)
and the terms f(Y,g,(X,c)) and Z is
 pred(f(Y,g,(X,c)),Z)     (formulas like this one are called atomic formulas)

In first-order logic more complex formulas can be defined by means of Logical Connectives:
 AND ( /\ ), OR ( \/ ), IMPLIES (->) etc.


In the following a generic formula will be denoted by A,B, etc.
We write A(X) to represent that the formula A contains a predicate which uses
a variable X.

In first-order logic formulas can be universally or existentially quantified:
- the formula   forall X.A(X)   means that the formula A(X) is valid
for any possible element X of the domain.
- the formula   exists X.A(X)  means that there exists an element t of
the domain such that A(t) is valid.
The quantifiers are binders for the quantified variables, like the
lambda for lambda-terms. So quantified formulas have to be considered
considered modulo alpha-conversion (forall X.A(X) is the very same formula
as forall Y.A(Y)).

Given a formula A it is not possible, in general, to decide whether
there is a proof for A (i.e. A is derivable) in first-order logic.
In other words, there is no algorithm whatsoever which, given a formula A,
terminates telling us if A is derivable or not (or, equivalently, if
A is valid or not).

The following are some of the rules of first-order logic that can be used to 
prove formulas:

Modus Ponens:

              A   A->B
            ------------ 
                 B


Forall Elimination:

             forall X.A(X)
            --------------    where t can be any term
                 A(t)


Exists Introduction: 

                 A(t)
            ---------------   where t can be any term
             exists X.A(X)
           
                
 

Even if first-order logic is undecidable, there are however fragments 
of it that are decidable. Other ones, even if not decidable, allow for
semi-decision algorithms that are extremely efficient.
One of these is the fragment where we consider formulas of the
form

   A1  and A2 and .... and An  -> B

or of the form

   B

where A1,..An,B are just atomic formulas and where the variables occurring 
in B are to be considered universally quantified, 
whereas the variables occurring in A1,..,An but not in B
are to be considered existentially quantified.
Formulas of these form are called Horn Clauses.

In Prolog, a formula A1  and A2 and .... and An  -> B is represented as

B :- A1, A2,..., An

An example of Horn Clause in Prolog is 

c(X,f(Y)) :- a(X,Y), b(g(Z),s), d(Z,Z).

(Horn Clauses in Prolog always terminate with a dot '.')

In the above formula all the variables are considered to be quantified
as explained before.
So the corresponding formula in logic notation would be

(forall X,Y.( exists Z. a(X,Y) and b(g(Z),s) and d(Z,Z) ) -> c(X,f(Y)) )


If one takes a set of Horn clauses H1,..,Hn, and a formula of the form

exists X1,..,Xk.B

where B is an atomic formula
there exists an algorithm which, in case this formula is derivable from 
the hypotheses H1,..,Hn, finds a proof for it in an efficient way
(such algorithm is called Resolution).
Even more, it can find out all the possible proofs if there
is more than one.

In Prolog the formula exists X1,..,Xk.B is represented simply by B
and it is usually written after the prompt of the interpreter '?-', as
follows

?- B.

B is called Goal.


Let us see with an example how Prolog looks for a proof of a Goal.


The following is a program intended to produce all the possible
permutations of a given list.

The predicate permutation/2 is introduced with the intended meaning that it
holds whenever the second argument is a permutation of the first one.



permutation([], []).                                          (1)

permutation(List, [Element | Permutation]) :-                 (2)

                                 select(Element, List, Rest),

                                 permutation(Rest, Permutation).


The program works correctly thanks to the predefined Prolog predicate
select/3.
select/3 holds whenever the first argument is an element of the
second argument (that has to be a list) and the third argument 
is the list without the element.
In fact, if we give the goal  select(E,[1,2,3],R)  to the Prolog interpreter,
we are asking whether there exists an element E and a list R such
that select(E,[1,2,3],R) holds.
The interpreter will tell us that there are many possibilities to
prove such a statement, and for each of these possibilities it 
returns us the different E and R used in the different proofs.
In particular, the interpreter returns us 
E = 1,
R = [2, 3] ;
E = 2,
R = [1, 3] ;
E = 3,
R = [1, 2] ;

Let us see now, roughly, how the interpreter works when we give to it
the following goal:

?- permutation([a,b],P).     (G)

By writing that we are asking the interpreter whether it is possible to prove 
there exists a list P such that  exists P.permutation([a,b],P) 
from the clauses (1) and (2) that forms our program. 

The interpreter first tries to use clause (1) to prove the goal (G).
It is not possible, so it tries and use clause (2). 
Remember that in Prolog clauses, the variables (present in the left-hand
side) are assumed to be universally quantified. 
If one assumes forall x.A(x) then, by using  the logical rule Forall Elimination,
we can derive that A(t) for any term t.
So, we could get a proof of (G) from clause (2) if we managed to unify
the goal (G) with the head of clause (2) and then if we can prove all
the resulting subgoals. The proof of (G) would then be obtained using the rule
Modus Ponens.

permutation([a,b],P)   and   permutation(L1, [E1 | P1])
are unifiable by means of the following substitution
L1 = [a,b]
P  = [E1 | P1]
(we used L1, E1 and P1 since Element and Permutation
are actually bound variables and we can rename them).

We need then to prove the following subgoals:

sG1: select(E1, [a,b], R1)

sG2: permutation(R1, P1)

The subgoal sG1, that amounts to asking whether there exist E1
and R1 such that select(E1, [a,b], R1) holds, can be proved directly
by the interpreter since select/3 is a predefined predicate. 
A proof of exists E1,R1.select(E1, [a,b], R1) is obtained using rule
Exist Introduction, since the interpreter knows that 
select(a, [a,b], [b]) holds.
The interpreter remembers that a known statement can be obtained from
select(E1, [a,b], R1) using the following substitution
E1 = a
R1 = [b].
Now, since we have R1 also in sG2, and we have proved the subgoal
?- select(E1, [a,b], R1)
using [b] for R1, we are forced to prove also sG2 using this particular value
for R1, that is we have to prove permutation([b], P1).


The interpreter now proceeds as it did previously: tries to use clause (1),
but it does not succeeds, and hence tries clauses (2).
Clauses (2) can be used to try and prove permutation([b], P1) if it is possible
to unify permutation([b], P1) with permutation(L2, [E2 | P2])
(again, we renamed bound variables, and we used different names in order to
avoid confusion).
The unification is possible by means of the following substitution
L2 = [b]
P1 = [E2 | P2]
The following subgoals have now to be proved

sG3: select(E2, [b], R2)

sG4: permutation(R2, P2)

The existence of E2 and R2 such that select(E2, [b], R2) holds can be
proved by the interpreter using
E2 = b
R2 = []
We are left then to prove permutation([], P2), i.e. we need to prove that 
there exists a list P2 such that permutation([], P2) holds.
This can be done immediately, since clause (1) states that 
permutation([],[]) holds, and hence our subgoal 
exists P2. permutation([], P2) can be proved using clause (1) and the rule
Exists Introduction. The interpreter remembers that clause (1) can be obtained
form permutation([], P2) using the following substitution
P2 = [].
Since there is no other subgoal to be proved, we have a proof of the
fact that 
exists P.permutation([a,b],P)
Moreover, in such a proof we have used the following substitutions:
L1 = [a,b]
P  = [E1 | P1]
E1 = a
R1 = [b]
L2 = [b]
P1 = [E2 | P2]
E2 = b
R2 = []
P2 = []
This means that an element P for which permutation([a,b],P) holds is
P = [E1|P1] = [a|[E2|P2] = [a|[b|[]]] = [a,b]

The Prolog interpreter (if asked to) can look for ALL the possible proofs of 
exists P.permutation([a,b],P).
In order to do so, it can backtrack to sG3, looking for an alternate proof of 
exists E2,R2.select(E2, [b], R2)
different from the one we already used. 
So, if there is no other proof of exists E2,R2.select(E2, [b], R2), 
there is also no other proof for permutation([b], P1).
Hence the interpreter backtracks to sG1, looking for another proof for
exists E1,R1.select(E1, [a,b], R1)
different form the one we already used.
There is such a proof, in particular the proof that uses
E1 = b
R1 = [a]
Using such a different proof of sG1, we can find a different proof
of our goal, but in order to obtain that we need a proof of
permutation(R1, P1) using [a] as R1.
We have to prove then
exists P1.permutation([a], P1)
The interpreter proceeds as it did before, finding a proof for the
following subgoals

sG5: select(E2, [a], R2)

sG6: permutation(R2, P2)

and hence for the statement
exists P2.permutation([],P2)
using clause (1).

We have then found another proof for our goal, this time using the
following substitutions:
L1 = [a,b]
P  = [E1 | P1]
E1 = b
R1 = [a]
L2 = [a]
P1 = [E2 | P2]
E2 = a
R2 = []
P2 = []
This means that another P for which permutation([a,b],P) holds is
P = [E1|P1] = [b|[E2|P2] = [b|[a|[]]] = [b,a].
In order to find such a proof we backtracked to sG1.
Now there is no other possible proof for sG1 and there is no
possibility to backtrack anymore. We are done and we have found
all the possible permutations (just 2 in such a simple case).


We can give a summary of how Prolog has proceeded as follows:


G    permutation([a,b],P)     
         |                   |--> (First backtrack)
sG1  select(E1, [a,b], R1)   |sG1 select(E1, [a,b], R1) 
     E1 = a                  |    E1 = b       
     R1 = [b]                |    R1 = [a]
         |                   |        |
sG2  permutation([b],P1)     |sG2 permutation([a],P1)   
         |                   |        |
         |                   |        |
         |                   |        |
sG3  select(E2,[b],R2)       |sG5 select(E2,[a],R2)
     E2 = b                  |    E2 = a
     R2 = []                 |    R2 = []
         |                   |        |
sG4  permutation([],P2)      |sG6 permutation([],P2)  
     P2 = []                 |    P2 = []
                             |
     OK-First proof!! -------|    OK-Second proof!!  ---| No other possible backtrack! 





===================================

Let us present a program that looks apparently correct but does not work.

The predicate h(_,_) holds whenever the second argument is the height of the
first argument, which is a binary tree 


h(emptyBT,0).

h(join(T1,T2),M+1) :- h(T1,N1), h(T2,N2), max(N1,N2,M).

max(N1,N2,N1) :- N1>N2.
max(N1,N2,N2) :- N2>=N1.



If we ask

?- h(join(join(emptyBT,emptyBT),emptyBT),H).

the interpreter returns 

H = 0+1+1 ;

We would have expected 2.

This is because M+1 is treated as a normal term, with + considered as an
uninterpreted function symbol.

If we wish M to be associated to a number, we need to modify the program as
follows:

h(emptyBT,0).

h(join(T1,T2),N) :- h(T1,N1), h(T2,N2), max(N1,N2,M), N is M+1.

See [PL] for the meaning of the clause "N is M+1".

</html>










