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:
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.
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
head: atomic -> predicate
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))
((eq? (head goal) (head (lhs (car db)))))
(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)
(display answer)
#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.