|
The following notes aim 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. 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:
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
∀-Elimination:
------------------- B
∀X.A(X)
∃-Introduction:
-------------- (where t can be any term) A(t)
A(t)
∧-Introduction :
--------------- ( where t can be any term) ∃X.A(X)
A B
------------------- A ∧ B 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 only formulas with the following shape
(A1 ∧ A2 ∧ .... ∧ An) → B
or with the shape
C
where
Notice that instead of considering the above quantifications, we can consider all the variables in C and (A1 ∧ A2 ∧ .... ∧ An) → B to be universally quantified (since in first order logic, when Y is not a free variable in Q, ∀X.( (∃Y.P)→Q ) is logically equivalent to ∀X,Y.( P→Q) ). In Prolog, a formula (A1 ∧ A2 ∧ .... ∧ An) → B is represented as B :- A1, A2,..., AnAn 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
∀X,Y.( ∃Z. (a(X,Y) ∧ b(g(Z),s) ∧ d(Z,Z) ) → c(X,f(Y)) )
If one takes a set of Horn clauses H1,..,Hn, and a formula of the form
∃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 Algorithm). Even more, it can find out all the possible proofs if there is more than one. In Prolog the formula ∃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, through an example, how Prolog looks for a proof of a Goal.
The following is a program (a set of Horn Clauses) intended to produce, by means of
Prolog proof search, all the possible
permutations of a given list.
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.
The predicate 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.
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 that there exists a list P such that ∃P.permutation([a,b],P) is derivable 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.
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 conjunction of 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.
The interpreter now remembers that the elements E1 and R1 such that select(E1, [a,b], R1) holds are the following ones E1 = a R1 = [b]Now, since we have to prove the conjunction of sG1 and sG2 (namely ∃E1,R1,P1.(select(E1, [a,b], R1) ∧ permutation(R1, P1)) ) and we have proved the subgoal ?- select(E1, [a,b], R1)using [b] for R1, we are necessarily forced to prove also sG2 using this particular value for R1, that is we have to prove ∃P1.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). The unification is possible by means of the following substitution L2 = [b] P1 = [E2 | P2] The conjuction of the following subgoals has 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 ∃P2.permutation([], P2), i.e. we need to prove that
there exists a list P2 such that permutation([], P2) holds. The interpreter remembers that the element P2 such that permutation([], P2) holds is the following one P2 = [] Since there is no other subgoal to be proved, we have a proof of the fact that ∃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
∃P.permutation([a,b],P).
Now, since there is no other proof of ∃E2,R2.select(E2, [b], R2),
there is also no other proof for ∃P1.permutation([b], P1). 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 ∃P1.permutation([a], P1) The interpreter proceeds as it did before, finding a proof for the conjunction of the following subgoals
sG5: select(E2, [a], R2)
sG6: permutation(R2, P2) and hence for the statement ∃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. 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 different viewpoint for how the interpreter looks for the
first proof of our goal:
In the following we use s for select, p for permutation, L for List, R for Rest and P for Permutation. So, the first clause of the program is (1): p([],[]) and the second one is (2): ∀E,L,R,P. s(E,L,R) ∧ p(R,P) → p(L,[E|P]) (as discussed previously, we are considering all the variables in a clause to be universally quantified).
===================================
About the use of is The following is a Prolog program that looks apparently correct but does not work. The predicate h(_,_) is intended to hold whenever the second argument is the height of the first argument, which is an unlabelled 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. |