## COS 441 - Logic Programming - April 30, 1996

### Logic Programming

Logic programming, as represented as Prolog has its roots in Horn clause logic. The Horn clauses are a subset of the first order predicate calculus. A theory of a first order predicate calculus consists of:
• an alphabet of constants (c), variables (v), function symbols (f), predicates (p), connectives, and qualifiers;
• a first order language;
• a set of axioms; and
• a set of inference rules.
A first order language has:
```               terms:   t ::= c | x | f( t[1] ... t[n] )
atomic formulae:   a ::= p( t[1] ... t[n] )
well-formed formulae: wff ::= a | wff AND wff | wff OR wff | ~wff | wff -> wff
| FORALL x . wff | EXISTS x . wff
```
We don't need all of these connectives and qualifiers:
```EXISTS x F = ~ FORALL x . ~F
A AND B = ~(~A OR ~B)
A -> B = ~A OR B
```
In fact it is possible to transform any wff into clausal form (provided that sufficient Skolem functions exist):
```FORALL x[1] ... x[n] . L[1] OR L[2] OR ... L[m]
```
where literals `L[i]` are `a | ~a` and `x[1] ... x[n]` are all the variables of `L[1] ... L[m]` (i.e. clauses have no free variables).

The Horn clauses are a subset of these where a clause has at most one non-negated literal. For example,

```A OR ~B OR ~C or ~D   (equivalent to)  (B AND C AND D) -> A.
```
We write the non-negated literal first. Since all Horn clauses enjoy this regular structure we write them as:
```A <- B C D   (or)    A :- B C D
```
`A` is the head of the clause and `B, C, D` are its sub-clauses (or subgoals). A Prolog program consists of:
• a set of facts (horn clauses with no non-negated literals);
• a set of rules relating various facts (horn clauses);
• a query (horn clause with no non-negated literal).
A solution to a Prolog program is a satisfying assignment to variables of the query that makes the query consistent with the facts and rules of the database. By convention uppercase indicates variables, lowercase everything else. An example follows.
```female(ann).
female(mary).
female(diane).
parents(ann,fred,mary).
parents(diane,fred,mary).
parents(mary,john,liz).
sister(X,Y) :- female(X), parents(X,M,F), parents(Y,M,F).

?sister(ann,diane). (true)
?sister(ann,mary).  (false)
?sister(ann,X).     (true, X = {ann,diane})
?sister(X,diane).   (true, X = {ann,diane})
```
Executing a Prolog query is done in the following manner.
• Find first rule of database whose head has the same predicate as the head head of the goal (query).
• If a fact, recursively match corresponding formulae.
• If a rule, match corresponding formulae of head and goal, and when they all match, recursively attempt to satisfy each subgoal in RHS of rule.
Match is described as: `Match: term x term -> assignment`
```match   X, X       empty set
c, c       empty set
c1, c2     fail       (c1 != c2)
X, c1      [X |-> c1]
c1, X      [X |-> c1]
X, Y       [X |-> Y]
```
Look familiar? Its unify! When we fail, we go back to step 1 and try the next rule. If there are no more rules, fail the most recent subgoal.

To implement a prolog interpreter we keep a stack of goals that records how to continue the search in step 1 and how to reset variables bindings as they were where the search must be resumed.

```    lhs: rule -> atomic
rhs: rule -> list of atomic
vars-of: (rule or atomic) -> set of vars

(define stk (#f))
(define variables '())

(define search
(lambda (goal)
(letrec (loop ((lambda (db)
(cond ((null? db) (fail))
(if (let/cc k
(set! variables (filter var? variables))
(push! stk (cons k variables))
(let ((rule (instance (car db))))
(set! variables (append vars-of rule
variables))
(unify goal (lhs rule))
(for-each search (rhs rule))
#t))
#t
(loop (car db)))
(else (loop (cdr db)))))))
(loop db))))

(define fail
(lambda ()
(let ((backtrack (pop! stk)))
(set! variables (cdr backtrack))
(reset-variables!)
((car backtrack) #f))))

(define query
(lambda (q)
(set! stk (empty-stack))
(if (let/cc k
(push! stk (cons k '()))
(set! variables (vars-of q))
(search q)
#t)
#t
(printf "no solution"))))
```
The list data type is represented in Prolog as `[X | Y]`. The first element is called the head and the last is called the tail. We can define member in the following way:
```member(X,[X | _]).
member(X,[_ | Y]) :- member(X,Y)

?member(ann,[ann,diane,mary]). (true)
```
We can also define append:
```append([],L,L).
append([X,L1],L2,[X | L3]) :- append(L1,L2,L3).

?append([a,b],[c,d],X).            (yes. X = [a, b, c, d])
?append(X,[c, d], [a, b, c, d]).   (yes. X = [a, b])
```

Prolog is NOT logic. One reason is that the order of rules in the database matters.

```g(foo).
p(X) :- p(X).
p(X) :- q(X).
?p(Z).
```
This yields an infinite loop. But swapping the two rules for `p(X)` it will terminate with the correct answer.

It is not always possible to arrange rules in such a way as to ensure termination. Hence Prolog adds a "cut" operator, denoted by the exclamation point. It controls search order. A clause of the form:

```h(X) :- f(X), !, g(X)
```
indicates that once f(X) is satisfied, the binding for X is fixed and can not be undone by backtracking. The cut operator cuts off backtracking through the proceeding subgoals. Cut has at best tenuous connections with logic.

#### Exercise

• Complete the interpreter.