Logic Programming

N. Wirth: Program = data structure + algorithm
R. Kowalski: Algorithm = logic + control
J. A. Robinson: A program is a theory (in some logic) and computation is deduction from the theory.

Logic programming is characterized by programming with relations and inference.

Keywords and phrases: Horn clause, Logic programming, inference, modus ponens, modus tollens, logic variable, unification, unifier, most general unifier, occurs-check, backtracking, closed world assumption, meta programming, pattern matching. set, relation, tuple, atom, constant, variable, predicate, functor, arity, term, compound term, ground, nonground, substitution, instance, instantiation, existential quantification, universal quantification, unification, modus ponens, proof tree, goal, resolvent.


A logic program consists of a set of axioms and a goal statement. The rules of inference are applied to determine whether the axioms are sufficient to ensure the truth of the goal statement. The execution of a logic program corresponds to the construction of a proof of the goal statement from the axioms.

In the logic programming model the programmer is responsible for specifying the basic logical relationships and does not specify the manner in which the inference rules are applied. Thus

Logic + Control = Algorithms

Logic programming is based on tuples. Predicates are abstractions and generalization of the data type of tuples. Recall, a tuple is an element of

S0 × S1 × ... × Sn

The squaring function for natural numbers may be written as a set of tuples as follows:

{(0,0), (1,1), (2,4) ...}

Such a set of tuples is called a relation and in this case the tuples define the squaring relation.

sqr = {(0,0), (1,1), (2,4) ...}

Abstracting to the name sqr and generalizing an individual tuple we can define the squaring relation as:

sqr = (x,x2)

Parameterizing the name gives:

sqr(X,Y) <-- Y is } X*X

In the logic programming language Prolog this would be written as:

sqr(X,Y) <-- Y is } X*X.

Note that the set of tuples is named sqr and that the parameters are X and Y. Prolog does not evaluate its arguments unless required, so the expression Y is X*X forces the evaluation of X*X and unifies the answer with Y. The Prolog code

P <-- Q.

may be read in a number of ways; it could be read P where Q or P if Q. In this latter form it is a variant of the first-order predicate calculus known as Horn clause logic. A complete reading of the sqr predicate the point of view of logic is: for every X and Y, Y is the sqr of X if Y is X*X. From the point of view of logic, we say that the variables are universally quantified. Horn clause logic has a particularly simple inference rule which permits its use as a model of computation. This computational paradigm is called Logic programming and deals with relations rather than functions or assignments. It uses facts and rules to represent information and deduction to answer queries. Prolog is the most widely available programming language to implement this computational paradigm.

Relations may be composed. For example, suppose we have the predicates, male(X), siblingof(X,Y), and parentof(Y,Z) which define the obvious relations, then we can define the predicate uncleof(X,Z) which implements the obvious relation as follows:

uncleof(X,Z) <-- male(X), siblingof(X,Y), parentof(Y,Z).
The logical reading of this rule is as follows: ``for every X,Y and Z, X is the uncle of Z, if X is a male who has a sibling Y which is the parent of Z.'' Alternately, ``X is the uncle of Z, if X is a male and X is a sibing of Y and Y is a parent of Z.'' %fatherof(X,Y),fatherof(Y,Z) defines paternalgrandfather(X,Z)

The difference between logic programming and functional programming may be illustrated as follows. The logic program

f(X,Y) <-- Y = X*3+4
is an abreviation for
\forall X,Y (f(X,Y) <-- Y = X*3+4)

which asserts a condition that must hold between the corresponding domain and range elements of the function. In contrast, a functional definition introduces a functional object to which functional operations such as functional composition may be applied.

Logic programming has many application areas:

Syntax

There are just four constructs: constants, variables, function symbols, predicate symbols, and two logical connectives, the comma (and) and the implication symbol.
Core Prolog
P in Programs
C in Clauses
Q in Queries
A in Atoms
T in Terms
X in Variables
Program ::= Clause... Query | Query
Clause ::= Predicate . | Predicate :- PredicateList .
PredicateList ::= Predicate | PredicateList , Predicate
Predicate ::= Atom | Atom( TermList )
TermList ::= Term | TermList , Term
Term ::= Numeral | Atom | Variable | Structure
Structure ::= Atom ( TermList )
Query ::= ?- PredicateList .
Numeral ::= an integer or real number
Atom ::= string of characters beginning with a lowercase letter or encluded in apostrophes.
Variable ::= string of characters beginning with an uppercase letter or underscore

Terminals = {Numeral, Atom, Variable, :-, ?-, comma, period, left and right parentheses }

While there is no standard syntax for Prolog, most implementations recognize the grammar in Figure M.N. 



 
Figure M.N: Prolog grammar
P in Programs
C in Clauses
Q in Query
H in Head
B in Body
A in Atoms
T in Terms
X in Variable 
 

P ::= C... Q...
C ::= H [ :- B ] .
H ::= A [ ( T [,T]... ) ] 
B ::= G [, G]... 
G ::= A [ ( [ X | T ]... ) ] 
T ::= X | A [ ( T... ) ] 
Q ::= ?- B .


CLAUSE, FACT, RULE, QUERY, FUNCTOR, ARITY, ORDER, UNIVERSAL QUANTIFICATION, EXISTENTIAL QUANTIFICATION, RELATIONS

In logic, relations are named by predicate symbols chosen from a prescribed vocabulary. Knowledge about the relations is then expressed by sentences constructed from predicates, connectives, and formulas. An n-ary predicate is constructed from prefixing an n-tuple with an n-ary predicate symbol.

A logic program is a set of axioms, or rules, defining relationships 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 basic constructs of logic programming, terms and statements are inherited from logic. There are three basic statements: facts, rules and queries. There is a single data structure: the logical term.

Facts, Predicates and Atoms

Facts are a means of stating that a relationship holds between objects.
father(bill,mary).
plus(2,3,5).
...
This fact states that the relation father holds between bill and mary. Another name for a relationship is predicate.

Queries

A query is the means of retrieving information from a logic program.
?- father(bill,mary).
?- father(bill,jim).
Note that the text terminates queries with a question mark rather than preceding.

Semantics

The operational semantics of logic programs correspond to logical inference. The declarative semantics of logic programs are derived from the term model commonly referred to as the Herbrand base. The denotational semantics of logic programs are defined in terms of a function which assigns meaning to the program.

There is a close relation between the axiomatic semantics of imperative programs and logic programs. A logic program to sum the elements of a list could be written as follows.

sum([Nth],Nth).
sum([Ith|Rest],Ith + Sum_Rest) <-- sum(Rest,Sum_Rest).
A proof of its correctness is trivial since the logic program is but a statement of the mathematical properties of the sum.
A[N] = sum_{i=N}^N A[i]}
sum([A[N]],A[N]).
sum_{i=I}^N A[i] = A[I] + S if 0 < I, sum_{i=I+1}^N A[i] = S}
sum([A[I],...,A[N]], A[I]+S) <--  sum([A[I+1],...,A[N]],S).

Operational Semantics

Definition: The meaning of a logic program P, M(P), is the set of unit goals deducible from P. The operational semantics of a logic program can be described in terms of logical inference using unification and the inference rule resolution. The following logic program illustrates logical inference.
a.
b <--  a.
b?
We can conclude b by modus ponens given that b <-- a and a. Alternatively, if b is assume to be false then from b <-- a and modus tollens we infer ¬ a but since a is given we have a contradiction and b must hold. The following program illustrates unification.
parent_of(a,b).
parent_of(b,c).
ancestor_of(Anc,Desc) <-- parent_of(Anc,Desc).
ancestor_of(Anc,Desc) <-- parent_of(Anc,Interm) \wedge
   ancestor_of(Interm,Desc).
parent_of(a,b)?
ancestor_of(a,b)?
ancestor_of(a,c)?
ancestor_of(X,Y)?
Consider the query `ancestor_of(a,b)?'. To answer the question ``is a an ancestor of b'', we must select the second rule for the ancestor relation and unify a with Anc and b with Desc. Interm then unifies with c in the relation parent_of(b,c). The query, ancestor_of(b,c)? is answered by the first rule for the ancestor_of relation. The last query is asking the question, ``Are there two persons such that the first is an ancestor of the second.'' The variables in queries are said to be existentially quantified. In this case the X unifies with a and the Y unifies with b through the parent_of relation. Formally,


Definition M.N:

A unifier of two terms is a substitution making the terms identical. If two terms have a unifier, we say they unify.


For example, two identical terms unify with the identity substitution. concat([1,2,3],[3,4],List) and concat([X|Xs],Ys,[X|Zs]) unify with the substitutions {X = 1, Xs = [2,3], Ys = [3,4], List = [1|Zs]}

There is just one rule of inference which is resolution. Resolution is much like proof by contradiction. An instance of a relation is ``computed'' by constructing a refutation. During the course of the proof, a tree is constructed with the statement to be proved at the root. When we construct proofs we will use the symbol ¬ to mark formulas which we either assume are false or infer are false and the symbol [] for contradiction. Resolution is based on the inference rule modus tollens and unification. This is the modus tollens inference rule.

From  ¬ B
and  B <-- A0,...,An
infer ¬ A0 or...or ¬ An
Notice that as a result of the inference there are several choices. Each ¬ A_{i} is a formula marking a new branch in the proof tree. A contradiction occurs when both a formula and its negation appear on the same path through the proof tree. A path is said to be closed when it contains a contradiction otherwise a path is said to be open. A formula has a proof if and only if each path in the proof tree is closed. The following is a proof tree for the formula B under the hypothesises A0 and B <-- A0,A_{1}.
1  From   ¬ B
2  and   A0
3  and   B <-- A0,A_{1}
4  infer   ¬ A0   or}  ¬ A_{1}
5  choose   ¬ A0
6  contradiction   []
7  choose   ¬ A_{1}
8  no further possibilities}  open}
There are two paths through the proof tree, 1-4, 5, 6 and 1-4, 7, 8. The first path contains a contradiction while the second does not. The contradiction is marked with [].

As an example of computing in this system of logic suppose we have defined the relations parent and ancestor as follows:

1.  parent_of(ogden,anthony)
2.  parent_of(anthony,mikko)
3.  parent_of(anthony,andra)
4.  ancestor_of(A,D) <-- parent_of(A,D) 
5.  ancestor_of(A,D) <-- parent_of(A,X) 
6.  ancestor_of(X,D)
where identifiers beginning with lower case letters designate constants and identifiers beginning with an upper case letter designate variables. We can infer that ogden is an ancestor of mikko as follows.
    ¬ ancestor(ogden,mikko)  the assumption
    ¬ parent(ogden,X)  or}  ¬ ancestor(X,mikko)
         resolution}
    ¬ parent(ogden,X)  first choice 
    ¬ parent(ogden,anthony)  unification with first entry 
    []  produces a contradiction
    ¬ ancestor(anthony,mikko)  second choice
    ¬ parent(anthony,mikko)  resolution
    []  A contradiction of a
      fact.}
Notice that all choices result in contradictions and so this proof tree is a proof of the proposition that ogden is an ancestor of mikko. In a proof, when unification occurs, the result is a substitution. In the first branch of the previous example, the term anthonoy is unified with the variable X and anthony is substituted for all occurences of the variable X.

UNIVERSAL QUANTIFICATION, EXISTENTIAL QUANTIFICATION

The unification algorithm can be defined in Prolog. Figure~\ref{lp:unify} contains a formal definition of unification in Prolog
 

Figure MN: Unification Algoririthm
unify(X,Y) <-- X == Y.
unify(X,Y) <-- var(X), var(Y), X=Y.
unify(X,Y) <-- var(X), nonvar(Y), \+ occurs(X,Y), X=Y.
unify(X,Y) <-- var(Y), nonvar(X), \+ occurs(Y,X), Y=X. 
unify(X,Y) <-- nonvar(X), nonvar(Y), functor(X,F,N), functor(Y,F,N),
X =..[F|R], Y =..[F|T], unify_lists(R,T).

unify_lists([ ],[ ]).
unify_lists([X|R],[H|T]) <-- unify(X,H), unify_lists(R,T).

occurs(X,Y) <-- X==Y.
occurs(X,T) <-- functor(T,F,N), T =..[F|Ts], occurs_list(X,Ts).

occurs_list(X,[Y|R]) <-- occurs(X,Y).
occurs_list(X,[Y|R]) <-- occurs_list(X,R).

Unification subsumes
\begin{array}{l}
\frac{A1 <-- B.},  ?- A1, A2,...,An.}}{?- B, A2,...,An.}} 

\frac{?- true, A1, A2,...,An.}}{?- A1, A2,...,An.}} 
\caption{Inference Rules\label{lp:ir}}
To illustrate the inference rules, consider the following program consisting of a rule, two facts and a query:
a <-- b \wedge c . b <-- d . b <-- e . ?- a .
By applying the inference rules to the program we derive the following additional queries:
?- b \wedge c . ?- d \wedge c . ?- e \wedge c. ?- c. ?-
Among the queries is an empty query. The presence of the empty query indicates that the original query is satisfiable, that is, the answer to the query is yes. Alternatively, the query is a theorem, provable from the given facts and rules.

Inference and Unification

Definition: The law of universal modus ponens says that from
R = (A :- B1,...,Bn) and
B'1.
...
B'n.
A' can be deduced, if A' :- B'1,...,B'n is an instance of R.

Definition: A logic program is a finite set of rules.

Definition: An existentially quantified goal G is a logical consequence of a program P iff there is a clause in P with an instance A :- B1,...,Bn, n \geq 0 such that B1,...,Bn are logical consequences of P and A is an instance of G.

The control portion of the the equation is provide by an inference engine whose role is to derive theorems based on the set of axioms provided by the programmer. The inference engine uses the operations of resolution and unification to construct proofs.

Resolution says that given the axioms

f if a0, ..., a_m.

g if f, b0, ..., bn.
the fact
g if a0, ..., a_m, b0, ..., bn.
can be derived.

Unification is the binding of variables. For example

A query containing a variable asks whether there is a value for the variable that makes the query a logical consequence of the program.

?- father(bill,X).
?- father(X,mary).
?- father(X,Y).
Note that variables do not denote a specified storage location, but denote an unspecified but single entity.

Definition: Constants and variables are terms. A compound term is comprised of a functor and a sequence of terms. A functor is characterized by its name, which is an atom and its arity, or number of arguments.

X, 3, mary, fatherof(F,mary), ...
Definition: Queries, facts and terms which do not contain variables are called ground. Where variables do occur they are called nonground.

Definition: A substitution is a finite set (possibly empty) of pairs of the form Xi=ti, where Xi is a variable and ti is a term, and Xi\neq Xj for every i \neq j, and Xi does not occur in tj, for any i and j.

p(a,X,t(Z)), p(Y,m,Q); theta = { X=m,Y=a,Q=t(Z) }
Definition: A is an instance of B if there is a substitution theta such that A = Btheta.

Definition: Two terms A and B are said to have a common instance C iff there are substitutions theta1 and theta2 such that C = Atheta1 and C = Btheta2.

A = plus(0,3,Y), B = plus(0,X,X). C = plus(0,3,3)
since C = A{ Y=3} and C = B{ X=3}.
Definition: A unifier of two terms A and B is a substitution making the two terms identical. If two terms have a unifer they are said to unify.
p(a,X,t(Z))theta = p(Y,m,Q)theta where theta = { X=m,Y=a,Q=t(Z) }
Definition: A most general unifier or mgu of two terms is a unifier such that the associated common instance is most general.
unify(A,B) :- unify1(A,B).

unify1(X,Y) :- X == Y.
unify1(X,Y) :- var(X), var(Y), X=Y. % The substitution
unify1(X,Y) :- var(X), nonvar(Y), \+ occurs(X,Y), X=Y. % The substitution
unify1(X,Y) :- var(Y), nonvar(X), \+ occurs(Y,X), Y=X. % The substitution
unify1(X,Y) :- nonvar(X), nonvar(Y), functor(X,F,N), functor(Y,F,N),
X =..[F|R], Y =..[F|T], match_list(R,T).

match_list([],[]).
match_list([X|R],[H|T]) :- unify(X,H), match_list(R,T).

occurs(A,B) :- A == B.
occurs(A,B) :- nonvar(B), functor(B,F,N), occurs(A,B,N).

occurs(A,B,N) :- N > 0, arg(N,B,AN), occurs(A,AN),!. % RED
occurs(A,B,M) :- M > 0, N is M - 1, occurs(A,B,N).

A Simple Interpreter for Pure Prolog

An interpreter for pure Prolog can be written in Prolog.


Figure M.N: A simple interpreter for pure Prolog
is_true( Goals ) <-- resolved( Goals ).
is_true( Goals ) <-- write( no ), nl.

resolved([]).
resolved(Goals) <-- select(Goal,Goals,RestofGoals),
                       % Goal unifies with head of some rule 
                       clause(Head,Body), unify( Goal, Head ),
                       add(Body,RestofGoals,NewGoals),
                       resolved(NewGoals).

prove(true).
prove((A,B)) <-- prove(A), prove(B). % select first goal
prove(A) <-- clause(A,B), prove(B). % select only goal and find a rule

is the Prolog code for an interpreter.

The interpreter can be used as the starting point for the construction of a debugger for Prolog programs and a starting point for the construction of an inference engine for an expert system.

The operational semantics for Prolog are given in Figure~\ref{lp:opsem}


Logic Programming (Horn Clause Logic) -- Operational Semantics
Abstract Syntax:

   P in Programs
   C in Clauses
   Q in Queries
   T in Terms
   A in Atoms
   X in Variables 

P ::= (C | Q)...
C ::= G [ <-- G_{1} [ \wedge G_{2}]...] . 
G ::= A [ ( T [,T]... ) ] 
T ::=  X | A [ ( T [,T]... ) ] 
Q ::= G [,G]... ?

Semantic Domains:

   beta in  {\bf B} = Bindings
   \epsilon in  {\bf E} = Environment

Semantic Functions: 

   R in Q} --> B} -->  B } + (B} × }yes }) + } no } }
   U in C } × C } --> B } --> B }

Semantic Equations: 

R[ ?} ] beta , \epsilon &=&  (beta, yes})
R[ G ] beta , \epsilon &=&  beta'
&  where }
&&G' in \epsilon ,   U [ G, G' ] beta = beta'
R[ G ] beta , \epsilon &=& R[ B ] beta' , \epsilon &  where }
&&(G' <-- B) in \epsilon ,   U [ G, G' ] beta = beta'
R[ G1,G2 ] beta , \epsilon &=& R[ B,G2 ] ( R [ G1 ] beta , \epsilon ), \epsilon
R[ G } ] beta , \epsilon &=&  no }
&  where no other rule applies}

\caption{Operational semantics\label{lp:opsem}}

Declarative Semantics

The declarative semantics of logic programs is based on the standard model-theoretic semantics of first-order logic.


Definition M.N:

Let P be a logic program. The Herbrand universe of P, denoted by U(P) is the set of ground terms that can be formed from the constants and function symbols appearing in P



Definition M.N:

The Herbrand base, denoted by {\cal B}(P), is the set of all ground goals that can be formed from the predicates in P and the terms in the Herbrand universe.


The Herbrand base is infinite if the Herbrand universe is.


Definition M.N:

An interpretation for a logic program is a subset of the Herbrand base.


An interpretation assigns truth and falsity to the elements of the Herbrand base. A goal in the Herbrand base is true with respect to an interpretation if it is a member of it, false otherwise.


Definition M.N:

An interpretation I is a model for a logic program if for each ground instance of a clause in the program A <-- B1, ... , Bn A is in I if B1, ... , Bn are in I.


This approach to the semantics is often called the term model.

Denotational Semantics

Denotational semantics assignes meanings to programs based on associating with the program a function over the domain computed by the program. The meaning of the program is defined as the least fixed point 0f the function, if it exists.

Pragmatics

Logic Programming and Software Engineering

Programs are theories and computation is deduction from the theory. Thus the process of software engineering becomes: Pros and Cons

The Logical Variable

The logical variable, terms and lists are the basic data structures in logic programming.

Here is a definition of the relation between the prefix and suffixes of a list. The relation is named concat because it may be viewed as defining the result of appending two lists to get the third list.

{l} concat([ ],[ ]) concat([H|T],L,[H|TL]) <-- concat(T,L,TL)

Logical variables operate in a way much different than variables in traditional programming languages. By way of illustration, consider the following instances of the concat relation.

  1. ?- concat([a,b,c],[d,e],L). L = [a, b, c, d, e] the expected use of the concat operation.
  2. ?- concat([a,b,c],S,[a,b,c,d,e]). S = [d, e] the suffix of L.
  3. ?- concat(P,[d,e],[a,b,c,d,e]). P = [a, b, c] the prefix of L.
  4. ?- concat(P,S,[a,b,c,d,e]). P = [ ], S = [a,b,c,d,e] P = [a], S = [b,c,d,e] P = [a,b], S = [c,d,e] P = [a,b,c], S = [d,e] P = [a,b,c,d], S = [e] P = [a,b,c,d,e], S = [ ] the prefixes and sufixes of L.
  5. ?- concat(_,[c|_],[a,b,c,d,e]). answers Yes since c is the first element of some suffix of L.
Thus concat gives us 5 predicates for the price of one.
concat(L1,L2,L)
prefix(Pre,L) <-- concat(Pre,_,L).
sufix(Suf,L) <-- concat(_,Suf,L).
split(L,Pre,Suf) <-- concat(Pre,Suf,L).
member(X,L) <-- concat(_,[X|_],L).
The underscore _ designates an anonymous variable, it matches anything.

There two simple types of constants, string and numeric. Arrays may be represented as a relation. For example, the two dimensional matrix

data} = \left( \begin{array}{lr} mary 18.47 john 34.6 jane 64.4 \end{array} \right)

may be written as {ll} data(1,1,mary)&data(1,2,18.47) data(2,1,john)&data(2,2,34.6) data(3,1,jane)&data(3,2,64.4)

Records may be represented as terms and the fields accessed through pattern matching.

book(author( last(aaby), first(anthony), mi(a)),
   title('programming language concepts),
   pub(wadsworth),
   date(1991))

book(A,T,pub(W),D)
Lists are written between brackets [ and ], so [ ] is the empty list and [b, c] is the list of two symbols b and c. If H is a symbol and T is a list then [H|T] is a list with head H and tail T. Stacks may then be represented as a list. Trees may be represented as lists of lists or as terms.

Lists may be used to simulate stacks, queues and trees. In addition, the logical variable may be used to implement incomplete data structures.

Incomplete Data Structures

The following code implements a binary search tree as an incomplete data structure. It may be used both to construct the tree by inserting items into the tree and to search the tree for a particular key and associated data.
lookup(Key,Data,bt(Key,Data,LT,RT)) 
lookup(Key,Data,bt(Key0,Data0,LT,RT)) <-- Key @< Key0,
   lookup(Key,Data,LT) 
lookup(Key,Data,bt(Key0,Data0,LT,RT)) <-- Key @> Key0,
   lookup(Key,Data,RT)
This is a sequence of calls. Note that the initial call is with the variable BT.
lookup(john,46,BT), lookup(jane,35,BT), lookup(allen,49,BT), lookup(jane,Age,BT).
The first three calls initialize the dictionary to contain those entries while the last call extracts janes's age from the dictionary.

The logical and the incomplete data structure can be used to append lists in constant time. The programming technique is known as difference lists. The empty difference list is X/X. The concat relation for difference lists is defined as follows:

concat_dl(Xs/Ys, Ys/Zs, Xs/Zs)
Here is an example of a use of the definition.
?- concat_dl([1,2,3|X]/X,[4,5,6|Y]/Y,Z).
 
   _X = [4,5,6 | 11]
   _Y = 11
   _Z = [1,2,3,4,5,6 | 11] / 11
Yes
The relation between ordinary lists and difference lists is defined as follows:
ol_dl([ ],X/X) <-- var(X)
ol_dl([F|R],[F|DL]/Y) <-- ol_dl(R,DL/Y)

Arithmetic

Terms are simply patterns they may not have a value in and of themselves. For example, here is a definition of the relation between two numbers and their product.
times(X,Y,X×Y)
However, the product is a pattern rather than a value. In order to force the evaluation of an expression, a Prolog definition of the same relation would be written
times(X,Y,Z) <-- Z is X×Y

Iteration vs Recursion

Not all recursive definitions require the runtime support usually associated with recursive subprogram calls. Consider the following elegant mathematical definition of the factorial function.
 n! = 1 if n = 0
      n× (n-1)! if n > 0
Here is a direct restatement of the definition in a relational form.
factorial(0,1)
factorial(N,N×F) <-- factorial(N-1,F)
In Prolog this definition does not evaluate either of the expressions N-1 or N×F thus the value 0 will not occur. To force evaluation of the expressions we rewrite the definition as follows.
factorial(0,1)
factorial(N,F)<-- M is N-1, factorial(M,Fm), F is N×Fm
Note that in this last version, the call to the factorial predicate is not the last call on the right-hand side of the definition. When the last call on the right-hand side is a recursive call ({\it tail recursion}) then the definition is said to be an iterative definition. An iterative version of the factorial relation may be defined using an accumulator and tail recursion.
fac(N,F) <-- fac(N,1,F)
fac(0,F,F)
fac(N,P,F) <-- NP is N×P, M is N-1, fac(M,NP,F)
In this definition, there are two different fac relations, the first is a 2-ary relation, and the second is a 3-ary relation.

As a further example of the relation between recursive and iterative definitions, here is a recursive version of the relation between a list and its reverse.

reverse([ ],[ ])
reverse([H|T],R) <-- reverse(T,Tr), concat(Tr,[H],R)
and here is an iterative version.
rev(L,R)<-- rev(L,[ ],R)
rev([ ],R,R)
rev([H|T],L,R) <-- rev(T,[H|L],R)
Efficient implementation of recursion is possible when the recursion is tail recursion. Tail recursion is implementable as iteration provided no backtracking may be required (the only other predicate in the body are builtin predicates).

Backtracking

When there are multiple clauses defining a relation it is possible that either some of the clauses defining the relation are not applicable in a particular instance or that there are multiple solutions. The selection of alternate paths during the construction of a proof tree is called backtracking.

Exceptions

Logic programming provides an unusually simple method for handling exception conditions. Exceptions are handled by backtracking.

Logic Programming vs Functional Programming

Functional composition vs composition of relations, backtracking, type checking

Prolog and Logic

The Logic of Prolog

Horn Clauses

Translation of first-order predicate logic to horn clause logic:
Replace Each disjunction is of the form: not A_1\/...\/not A_m\/B_1\/...\/B_n
which is equivalent to: A_1/\.../\A_m --> B_1\/...\/B_n If n always is 1 then the logic is called Horn Clause Logic which is equivalent in computational power to the Universal Turing Machine.

Resolution and unification, forward and backward chaining

The resolution rule combines clauses when a negated and a non-negated literal match.

If Aj and B_y `match' then by resolution: ...not Ai \/ not Aj \/ B_k...
...not A_x \/ B_y \/ B_z...
----------------------------
...not Ai \/...\/ A_x \/ B_z...\/ B_k
Matching is called unification.

Direction of Proof

The Illogic of Prolog

Prolog (for efficiency reasons) departs from the logic programming model in several ways. Prolog does not perform the ``occurs check''. Prolog is implemented as a sequential programming language by processing goals from left to right and selecting rules in textual order (depth-first search).

Prolog is not logic programming. The execution model for Prolog omits the occurs check, searches the rules sequentially, and selects goals sequentially. Backtracking, infinite search trees ...

As in functional programming, lists are an important data structure logic programming. The empty list is represented by [ ], a list of n elements by [X1, ... , Xn] and the first i elements of a list and the rest of the list by [X1,...,Xi|R]. In addition, data structures of arbitrary complexity may be constructed from the terms.

Incompleteness

Incompleteness occurs when there is a solution but it cannot be found. The depth first search of Prolog will never answer the query in the following logic program.
p( a, b ).
p( c, b ).
p( X, Z ) <-- p( X, Y ), p( Y, Z).
p( X, Y ) <-- p( Y, X ).
?- p( a, c ).
The result is an infinite loop. The first and fourth clauses imply p( b, c ). The first and third clauses with the p( b, c) imply the query. Prolog gets lost in an infinite branch no matter how the clauses are ordered, how the literals in the bodies are ordered or what search rule with a fixed order for trying the clauses is used. Thus logical completeness requires a breadth-first search which is too inefficient to be practical.

Unfairness

Unfairness occurs when a permissible value cannot be found.
concat( [ ], L, L ).
concat( [H|L1], L2, [X|L] ) <-- concat( L1, L2, L ).
concat3( L1, L2, L3, L ) <-- concat( L1, L2, L12 ),
   concat( L12, L3 L ).
?- concat3( X, Y, [2], L).
Result is that X is always [ ]. Prologs depth-first search prevents it from finding other values.

Unsoundness

Unsoundness occurs when there is a successful computation of a goal which is not a logical consequence of the logic program.
test <-- p( X, X ).
p( Y, f( Y )).
?- test.
Lacking the occur check Prolog will succeed but \verb+test+ is not a logical consequence of the logic program.

The execution of this logic program results in the construction of an infinite data structure.

concat( [ ], L, L ).
concat( [H|L1], L2, [X|L] ) <-- concat( L1, L2, L ).
?- concat( [ ], L, [1|L] ).
In this instance Prolog will succeed (with some trouble printing the answer). There are two solutions, the first is to change the logic and permit infinite terms, the second is to introduce the occur check with the resulting loss of efficiency.

Negation

Negative information cannot be expressed in Horn clause logic. However, Prolog provides the negation operator not and defines negation as failure to find a proof.
p( a ).
r( b ) <-- not p( Y ).
?- not p(b).
The goal succeeds but is not a logical consequence of the logic program.
q( a ) <-- r( a ).
q( a ) <-- not r( a ).
r( X ) <-- r( f( X ) ).
?- q ( a ).
The query is a logical consequence of the first two clauses but Prolog cannot determine that fact and enters an infinite derivation tree. However the closed world assumption is useful from a pragmatic point of view.

Control Information

Cut (!): prunes the proof tree.
a(1).
a(2).
a(3).
p <-- a(I),!,print(I),nl,fail.
?- p.
1
 
No

Extralogical Features

Input-output primitives cannot be fully described in first-order logic. These primitives produce input-output by side-effects.

Some other extralogical primitives include bagof, setof, assert, retract, univ. These are outside the scope of first-order logic.

Input and output introduce side effects.

The extralogical primitives \verb+bagof+, \verb+setof+, \verb+assert+, and \verb+retract+ are outside the scope of first-order logic but are useful from the pragmatic point of view.

In Prolog there are builtin predicates to test for the various syntactic types, lists, numbers, atoms, clauses. Some predicates which are commonly available are the following.

{ll} var(X)&X is a variable atomic(A)&A is an atom or a numeric constant functor(P,F,N)&P is an N-ary predicate with functor F clause(Head,Body)&Head <-- Body is a formula. L =..List, call(C), assert(C), retract(C), bagof(X,P,B), setof(X,P,B)


Figure M.N:
trace(Q) <-- trace1([Q])
trace1([])
trace1([true|R]) <-- !, trace1(R).
trace1([fail|R]) <-- !, print('< '), print(fail), nl, fail.
trace1([B|R]) <-- B =..[','|BL], !, concat(BL,R,NR), trace1(NR).

trace1([F|R]) <-- builtin(F),
      print('> '), print([F|R]), nl,
      F,
      trace1(R),
      print('< '), print(F), nl

trace1([F|R]) <-- clause(F,B),
      print('> '), print([F|R]),nl,
      trace1([B|R]),
      print('< '), print(F), nl

trace1([F|R]) <--  \+ builtin(F), \+ clause(F,B),
      print('> '), print([F|R]),nl,
      print('< '), print(F), print(' '), print(fail), nl, fail

\caption{Program tracer for Prolog\label{lp:trace}}

contains an example of meta programming. The code implements a facility for tracing the execution of a Prolog program. To trace a Prolog program, instead of entering {\tt ?- P.} enter {\tt ?- trace(P).}

Multidirectionality

Computation of the inverse function must be restricted for efficiency and undecidability reasons. For example consider the query {l} ?- factorial(N,5678). An implementation must either generate and test possible values for N (which is much too inefficient) or if there is no such N the undecidability of first-order logic implies that termination may not occur.

Rule Order

Rule order affects the order of search and thus the shape of the proof tree. In the following program
concat([ ],L,L).
concat([H|T],L,[H|R]) <-- concat(T,L,R).
?- concat(L1,[2],L).
the query results in the sequence of answers.
L1 = [ ], L = [2] 
L1 = [V1], L = [V1,2] 
L1 = [V1,V2], L = [V1,V2,2] 
...
However, if the order of the rules defining $append$ are interchanged,
append([H|T],L,[H|R]) :- append(T,L,R).
append([ ],L,L).
?- append(L1,[2],L).
then the execution fails to terminate, entering an infinite loop since the first rule is always applicable.

Historical Perspectives and Further Reading

Logic programming languages are abstractions and generalization of tuples (relations). History Future Integration of Database management systems and logic programming and parallel programming languages based on the logic paradigm. References
Clocksin & Mellish, Programming in Prolog 4th ed. Springer-Verlag 1994.
Hill, P. & Lloyd, J. W., The Gödel Programming Language MIT Press 1994.
Hogger, C. J., Introduction to Logic Programming Academic Press 1984.
Lloyd, J. W., Foundations of Logic Programming 2nd ed. Springer-Verlag 1987.
Nerode, A. & Shore, R. A., Logic for Applications Springer-Verlag 1993.
Robinson, J. A., Logic: Form and Function North-Holland 1979.
1969 J Robinson and Resolution 1972 Alain Colmerauer History Kowalski's paper\cite{Kowalski79} Logic programming techniques Implementation of Prolog SQL DCG

Exercises

  1. Modify concat to include an explicit occurs check.
  2. Construct a Prolog based family database. Include the following relations: parentof, grandparentof, ancestorof, uncleof, auntof, and any others of your choice.
  3. The relational algebra is ... query languages of relational database management systems is another approach to the logic model.
  4. The fundamental entity in a relational database is a relation which is viewed as a table of rows and columns, where each row, called a tuple, is an object and each column is an attribute or propery of the object.

    A database consists of one or more relations. The data stored in the relations is manipulated using commands written in a query language. The operations provided the query language include union, set difference, cartesian product, projection, and selection. The first-order predicate logic can be used to represent knowledge and as a language for expressing operations on relations. -- Ullman (Principles of Database and Knowledge-base Systems) CSP 1988.

  5. Construct a family data base f_db(f,m,c,sex) and define the following relations, f_of, m_of, son_of, dau_of, gf, gm, aunt, uncle, ancestor, half_sis, half_bro.
  6. Business Data base
  7. Blocks World
  8. CS Degree requirements; course(dept,name,prereq). don't forget w1 and w2 requirements.
  9. Circuit analysis
  10. Tail recursion
  11. Compiler
  12. Interpreter
  13. Tic-Tac-Toe
  14. DCG
  15. Construct Prolog analogues of the relational operators for union, set difference, cartesian product, projection and selection.
  16. Airline reservation system

Objective:

The logical variable, substitutions and instances

Rules

          
A :- B1, B2, ..., Bn.
A is the head. B1, B2, ..., Bn. is the body. Facts, rules, and queries are also called Horn clauses or just clauses. Facts are also called unit clauses.
ancestor(X,Y) :- father(X,Y).
ancestor(X,Z) :- father(X,Y), ancestor(Y,Z).
How to read rules. For every X and Y, if X is the father of Y and Y is an ancestor of Z, X is the ancestor of Z. For every X and Y, X is the ancestor of Z, if X is the father of Y and Y is an ancestor of Z.
father(bill,jim).
father(jim,jane).
?- father(bill,Y), father(Y,jane).
Operationally, to solve a conjunctive query, a single substitution must be found applicable to each conjunct.
For A1,...,An and theta
A1theta,...,Antheta each is deducible.

Quantifiers

Variables in queries are existentially quantified. Operationally, to answer a query, using a program, is to perform a computation whose output is the substitution that unifies the query with an instance of the query which is deducible from the program. Note that it may be possible to compute more than one substitution. Variables occurring in facts are universally quantified.
father(adam,X).
Variables in the body of a rule are read as universally quantified out side the rule and read as existentially quantified inside the rule. For every X and Y, if X is the father of Y and Y is an ancestor of Z, X is the ancestor of Z. if there exist X and Y such that X is the father of Y and Y is an ancestor of Z, then X is the ancestor of Z.
father(bill,jim).
father(jim,jane).
?- father(bill,Y), father(Y,jane).
Operationally, to solve a conjunctive query, a single substitution must be found applicable to each conjunct.
For A1,...,An and theta
A1theta,...,Antheta each is deducible.