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:
= {Xa, Yf(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 {Xa, Yb}
f(X,b) and g(X,b) do not unify
a(X,X,b) and a(b,X,X) unify: a unifier is {Xb}
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 = {Xfred}
another is 2 = {Xfred, Ymary}
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 {Xt}:
–
–
–
X and b unify: an MGU is {Xb}
X and f(a,g(b,c)) unify: an MGU is
{Xf(a,g(b,c))}
X and f(a,Y) unify: an MGU is {Xf(a,Y)}
Unless X occurs in t:
–
Chapter Twenty
X and f(a,X) do not unify, in particular not by
{Xf(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 {Xf(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