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 . wffWe 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 BIn 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.