A Second Look At Prolog - University of Wisconsin–Milwaukee

Download Report

Transcript A Second Look At Prolog - University of Wisconsin–Milwaukee

Implementing Prolog
Chapter Twenty
Modern Programming Languages
1
Outline

Parts of Chapter 20, with implementation
–
–
–
Chapter Twenty
20.2 Unification
20.4 Implementing Prolog
From 20.5: Variable renaming
Modern Programming Languages
2
Substitutions
A substitution is a function that maps
variables to terms:
 = {Xa, Yf(a,b)}
 This  maps X to a and Y to f(a,b)
 The result of applying a substitution to a
term is an instance of the term
 (g(X,Y)) = g(a,f(a,b)) so
g(a,f(a,b)) is an instance of g(X,Y)

Chapter Twenty
Modern Programming Languages
3
Unification

Two Prolog terms t1 and t2 unify if there is some
substitution  (their unifier) that makes them
identical: (t1) = (t2)
–
–
–
–
–
–
Chapter Twenty
a and b do not unify
f(X,b) and f(a,Y) unify: a unifier is {Xa, Yb}
f(X,b) and g(X,b) do not unify
a(X,X,b) and a(b,X,X) unify: a unifier is {Xb}
a(X,X,b) and a(c,X,X) do not unify
a(X,f) and a(X,f) do unify: a unifier is {}
Modern Programming Languages
4
Multiple Unifiers

parent(X,Y) and parent(fred,Y):
–
–
one unifier is 1 = {Xfred}
another is 2 = {Xfred, Ymary}
Prolog chooses unifiers like 1 that do just
enough substitution to unify, and no more
 That is, it chooses the MGU—the Most
General Unifier

Chapter Twenty
Modern Programming Languages
5
MGU

Term x1 is more general than x2 if x2 is an
instance of x1 but x1 is not an instance of x2
–
Example: parent(fred,Y) is more general
than parent(fred,mary)
A unifier 1 of two terms t1 and t2 is an
MGU if there is no other unifier 2 such
that 2(t1) is more general than 1(t1)
 MGU is unique up to variable renaming

Chapter Twenty
Modern Programming Languages
6
Unification For Everything

Parameter passing
–

Binding
–

X=0
Data construction
–

reverse([1,2,3],X)
X=.(1,[2,3])
Data selection
–
Chapter Twenty
[1,2,3]=.(X,Y)
Modern Programming Languages
7
The Occurs Check

Any variable X and term t unify with {Xt}:
–
–
–

X and b unify: an MGU is {Xb}
X and f(a,g(b,c)) unify: an MGU is
{Xf(a,g(b,c))}
X and f(a,Y) unify: an MGU is {Xf(a,Y)}
Unless X occurs in t:
–
Chapter Twenty
X and f(a,X) do not unify, in particular not by
{Xf(a,X)}
Modern Programming Languages
8
Occurs Check Example
append([], B, B).
append([Head|TailA], B,
[Head|TailC]) :append(TailA, B, TailC).
?- append([], X, [a | X]).
X = [a, a, a, a, a, a, a, a, a|...]
Yes
Most Prologs omit the occurs check
 ISO standard says the result of Prolog
“unification” is undefined in cases that
should fail the occurs check

Chapter Twenty
Modern Programming Languages
9
ML Implementation
We will look at an implementation of
Prolog in ML
 First, some code for representing terms

Chapter Twenty
Modern Programming Languages
10
Representing Terms
(*
A datatype for Prolog terms. A term can be an
Atom, a Variable, or a Compound term. We
represent all the lexemes using strings.
*)
datatype term =
Atom of string |
Variable of string |
Compound of string * term list;
Chapter Twenty
Modern Programming Languages
11
Example Prolog Clause
(* p(f(Y)) :- q(Y),r(Y). *)
val c1 = [Compound("p",[Compound("f",[Variable("Y")])]),
Compound("q",[Variable("Y")]),
Compound("r",[Variable("Y")])];
Chapter Twenty
Modern Programming Languages
12
Substitution
(*
A special mapping function for terms. We convert a term into the
new term formed by applying the function f to all the variables
(but leaving all the atoms and predicate names unaltered).
*)
fun mapVariable f (Atom x) = Atom(x)
| mapVariable f (Variable n) = f n
| mapVariable f (Compound(n, terms)) =
Compound(n, map (mapVariable f) terms);
(*
Create a substitution. We take a variable name and a term, and
return a function that can be used to apply that substitution to
any term.
*)
fun sub name term =
mapVariable (fn n => if n=name then term else Variable n);
Chapter Twenty
Modern Programming Languages
13
Unification
(*
Find a most general unifier of two terms. We return a pair of
values, like this:
val (unifiable, unifier) = mgu(a,b)
The first value we return is a boolean, true iff the two terms
are unifiable. If they are, the second value we return is a
most general unifier: a function that, when applied to a and b,
makes them identical. We do not perform the occurs check.
*)
fun mgu(a,b) =
let fun ut([], [], unifier) =
(true, unifier)
| ut(term::t1, Variable(name)::t2, unifier) =
let val r = (sub name term) o unifier in
ut(map r t1, map r t2, r)
end
| ut(Variable(name)::t1, term::t2, unifier) =
let val r = (sub name term) o unifier in
ut(map r t1, map r t2, r)
end
Chapter Twenty
Modern Programming Languages
14
More Unification
| ut(Atom(n)::t1, Atom(m)::t2, unifier) =
if n=m then ut(t1,t2,unifier) else (false, unifier)
| ut(Compound(n1,xt1)::t1, Compound(n2,xt2)::t2, unifier) =
if n1=n2 andalso length xt1 = length xt2
then ut(xt1@t1, xt2@t2, unifier)
else (false, unifier)
| ut(_,_,unifier) =
(false, unifier);
in
ut([a],[b], (fn x => x))
end;
Chapter Twenty
Modern Programming Languages
15
About Unification Algorithms
The one just shown takes exponential time
in the worst case
 In fact, any algorithm that linearly
represents the unified term, without shared
structure, takes exponential time and space
 Using fancier techniques, it can be done in
linear time

Chapter Twenty
Modern Programming Languages
16
Outline

Parts of Chapter 20, with implementation
–
–
–
Chapter Twenty
20.2 Unification
20.4 Implementing Prolog
From 20.5: Variable renaming
Modern Programming Languages
17
Resolution
The hardwired inference step
 A clause is represented as a list of terms (a
list of one term, if it is a fact)
 Resolution step applies one clause, once, to
make progress on a list of goal terms

function resolution(clause, goals):
let sub = the MGU of head(clause) and head(goals)
return sub(tail(clause) concatenated with tail(goals))
Chapter Twenty
Modern Programming Languages
18
Resolution Example
Given this list of goal terms:
[p(X),s(X)]
And this rule to apply:
p(f(Y)) :- q(Y), r(Y).
The MGU of the heads is {Xf(Y)}, and we get:
resolution([p(f(Y)),q(Y),r(Y)], [p(X),s(X)])
= [q(Y),r(Y),s(f(Y))]
function resolution(clause, goals):
let sub = the MGU of head(clause) and head(goals)
return sub(tail(clause) concatenated with tail(goals))
Chapter Twenty
Modern Programming Languages
19
Resolution in ML
fun resolution(head::conds,goal::goals) =
let
val (unifiable, unifier) = mgu(head,goal)
in
map unifier (conds@goals)
end;
function resolution(clause, goals):
let sub = the MGU of head(clause) and head(goals)
return sub(tail(clause) concatenated with tail(goals))
Chapter Twenty
Modern Programming Languages
20
A Prolog Interpreter
function solve(goals)
if goals is empty then succeed()
else for each clause c in the program, in order
if head(c) does not unify with head(goals) then do nothing
else solve(resolution(c, goals))
Chapter Twenty
Modern Programming Languages
21
Program:
A partial trace for query p(X):
1.
solve([p(X)])
1. solve([q(Y),r(Y)])
…
2. nothing
3. nothing
4. nothing
p(f(Y)) :q(Y),r(Y).
q(g(Z)).
q(h(Z)).
r(h(a)).
2.
3.
4.

solve tries each of the four clauses in turn
–
–
Chapter Twenty
The first works, so it calls itself recursively on
the result of the resolution step (not shown yet)
The other three do not work: heads do not unify
with the first goal term
Modern Programming Languages
22
Program:
A partial trace for query p(X), expanded:
1.
solve([p(X)])
1. solve([q(Y),r(Y)])
1. nothing
2. solve([r(g(Z))])
…
3. solve([r(h(Z))])
…
4. nothing
2. nothing
3. nothing
4. nothing
2.
3.
4.
p(f(Y)) :q(Y),r(Y).
q(g(Z)).
q(h(Z)).
r(h(a)).
Chapter Twenty
Modern Programming Languages
23
Program:
A complete trace for query p(X):
1.
solve([p(X)])
1. solve([q(Y),r(Y)])
1. nothing
2. solve([r(g(Z))])
1. nothing
2. nothing
3. nothing
4. nothing
3. solve([r(h(Z))])
1. nothing
2. nothing
3. nothing
4. solve([]) —success!
4. nothing
2. nothing
3. nothing
4. nothing
2.
3.
4.
p(f(Y)) :q(Y),r(Y).
q(g(Z)).
q(h(Z)).
r(h(a)).
Chapter Twenty
Modern Programming Languages
24
Collecting The Substitutions
function resolution(clause, goals, query):
let sub = the MGU of head(clause) and head(goals)
return (sub(tail(clause) concatenated with tail(goals)), sub(query))
function solve(goals, query)
if goals is empty then succeed(query)
else for each clause c in the program, in order
if head(c) does not unify with head(goals) then do nothing
else solve(resolution(c, goals, query))
Modified to pass original query along and
apply all substitutions to it
 Proved instance is passed to succeed

Chapter Twenty
Modern Programming Languages
25
Program:
1.
2.
3.
4.
A complete trace for query p(X):
p(f(Y)) :solve([p(X)],p(X))
q(Y),r(Y). 1. solve([q(Y),r(Y)],p(f(Y)))
q(g(Z)).
1. nothing
q(h(Z)).
2. solve([r(g(Z))],p(f(g(Z))))
r(h(a)).
1. nothing
2. nothing
3. nothing
4. nothing
3. solve([r(h(Z))],p(f(h(Z))))
1. nothing
2. nothing
3. nothing
4. solve([],p(f(h(Z))))
4. nothing
2. nothing
3. nothing
4. nothing
Chapter Twenty
Modern Programming Languages
26
Prolog in ML
fun solve(_,nil,query) = succeed query
| solve(program,goal::goals,query) =
let fun onestep (head::conds, _) =
let
val (unifiable, unifier) = mgu(head,goal)
in
if unifiable
then solve(program,
map unifier (conds@goals),
unifier query)
else true
end
in
foldl onestep true program
end;
fun prolog(program,query) = solve(program,[query],query);
Chapter Twenty
Modern Programming Languages
27
Outline

Parts of Chapter 20, with implementation
–
–
–
Chapter Twenty
20.2 Unification
20.4 Implementing Prolog
From 20.5: Variable renaming
Modern Programming Languages
28
A Problem
Model of Prolog execution just shown is
flawed, as is the ML Prolog implementation
 It works on some examples
 On others it does not agree with common
sense, or with the actual behavior of a
Prolog language system
 For instance, reverse([1,2],X)

Chapter Twenty
Modern Programming Languages
29
Program:
1.
2.
reverse([],[]).
reverse([Head|Tail],X) :reverse(Tail,Y),
append(Y,[Head],X).
A trace for query reverse([1,2],X)
solve([reverse([1,2],X)])
1. nothing
2. solve([reverse([2],Y),append(Y,[1],X)])
1. nothing
2. solve([reverse([],X),
append(X,[2],X),
append(X,[1],X)])
1. solve([append([],[2],[]),
append([],[1],[])])
…no solution: what went wrong?
2. nothing
Chapter Twenty
Modern Programming Languages
30
Program:
1.
2.
reverse([],[]).
reverse([Head|Tail],X) :reverse(Tail,Y),
append(Y,[Head],X).
A trace for query reverse([1,2],X)
This step was wrong;
we substituted X for Y,
but there is already an
X in the goal list
solve([reverse([1,2],X)])
1. nothing
2. solve([reverse([2],Y),append(Y,[1],X)])
1. nothing
2. solve([reverse([],X),
append(X,[2],X),
append(X,[1],X)])
1. solve([append([],[2],[]),
append([],[1],[])])
…no solution
2. nothing
Chapter Twenty
Modern Programming Languages
31
Capture
Recall Prolog’s scope rule: the scope of a
definition of a variable is the clause
containing it
 Our resolution step mashes two scopes
together, incorrectly identifying any
variables that happen to have the same
names
 Another kind of capture

Chapter Twenty
Modern Programming Languages
32
Variable Renaming
To avoid capture, use fresh variable names
for each clause, every time you apply it
 The first application of reverse might be:

reverse([Head1|Tail1],X1) :reverse(Tail1,Y1),
append(Y1,[Head1],X1).

And the next might be:
reverse([Head2|Tail2],X2) :reverse(Tail2,Y2),
append(Y2,[Head2],X2).

And so on…
Chapter Twenty
Modern Programming Languages
33
Program:
1.
2.
reverse([],[]).
reverse([Head|Tail],X) :reverse(Tail,Y),
append(Y,[Head],X).
A trace for query reverse([1,2],X)
solve([reverse([1,2],X)])
1. nothing
2. solve([reverse([2],Y1),append(Y1,[1],X1)])
1. nothing
2. solve([reverse([],Y2),
append(Y2,[2],X2),
append(X2,[1],X1)])
1. solve([append([],[2],X2),
append(X2,[1],X1)])
…solution with X2=[2], X1=[2,1]
2. nothing
Chapter Twenty
Modern Programming Languages
34
ML Renaming
This ML implementation solves the problem
by renaming
 It adds “#n” to the end of every variable
name whenever a clause from the program
is used, where n is the depth of the
recursion
 Real Prologs find a more efficient way to do
this, of course

Chapter Twenty
Modern Programming Languages
35
fun rename iter term =
mapVariable (fn n => Variable(n^"#"^iter)) term;
fun solve(_,nil,query,_) = succeed query
| solve(program,goal::goals,query,depth) =
let fun onestep (clause, _) =
let
val head::conds =
map (rename (Int.toString depth)) clause
val (unifiable, unifier) = mgu(head,goal)
in
if unifiable
then solve(program, map unifier (conds@goals),
unifier query, depth+1)
else true
end
in
foldl onestep true program
end;
fun prolog(program,query) = solve(program,[query],query,1);
Chapter Twenty
Modern Programming Languages
36
Conclusion

Prolog has a simple model of execution
–
–
–

Compare with implementations of similar
interpreters for Java or ML
(We’ll use Prolog to write some interpreters for
ML-like languages in Chapter 23)
Interestingly: using Prolog is tricky, in spite of
its simple interpreter and simple syntax
ML is a good language for this kind of thing
Chapter Twenty
Modern Programming Languages
37