Here is a version of Abelson and Sussman's Scheme version of unification
Note that the copyright remains with A&S, since it's but a translation.
/*
let.p Robin Popplestone MAY 1996
The following macro definition provides a "let" statement.
let
jane = 7,
joy = 6
in
jane+joy
endlet
which would seem reasonably natural to users of Scheme and ML,
controls the scope of the declared variables, and avoids the
perennial pitfall of "lvars" namely that you can easily write:
lvars jane = 4; joy = 34;
when you mean
lvars jane = 4, joy = 34;
(the kind of feature regarded as a virtue in C...).
/* (C) Copyright, University of Massachusetts, June 1994, All Rights Reserved
*
* This program carries no warranty.
*
* send bug reports and suggestions to pop@cs.umass.edu
*
This program may reproduced for academic and experimental purposes, provided
the above attribution is preserved and extended as appropriate.
Commercial rights are reserved.
*/
====================================================================
*/
;;; let.p Robin Popplestone, spring 1995
define lconstant check_no_semicolon();
lvars l,L = proglist, d=0;
repeat; hd(L) -> l;
if member(l, vedopeners) then d+1->d
endif;
if l == ";" and d == 0 then
mishap('";" encountered in let binding', [^proglist]);
endif;
if member(l,vedclosers) then d-1->d;
endif;
if l = "in" and d==0 then ";" -> hd(L); quitloop;
endif;
/*
if d==0 ;;; e.g. let + = Add in ....
and isword(l)
then lvars n = identprops(l);
if isnumber(n) and n>0
and hd(tl(L)) == "="
then
identprops(l) -> hd(L);
l::tl(L) -> tl(L);
tl(L) -> L;
endif
endif;
*/
tl(L) -> L;
endrepeat;
enddefine;
define macro let;
check_no_semicolon();
"lblock", "lvars"
enddefine;
define macro endlet;
"endlblock"
enddefine;
"let"::vedopeners -> vedopeners;
"endlet"::vedclosers -> vedclosers;
"in"::vedbackers->vedbackers;
/*
define test(x);
let y = 1+x, z=3
in y+z
endlet
enddefine;
test()=>
*/
define AnExample(p);
unless p then mishap('Example failed',[])
endunless;
enddefine;
define assoc_scm(x,l);
if null(l) then false
elseif front(hd(l)) = x then hd(l)
else assoc_scm(x,tl(l))
endif
enddefine;
<h3>2.1 Distinguishing Variables from Constants</h3>
<p>
The range of possible constants can include Scheme symbols, so we have a
problem in distinguishing between a symbol acting as a constant and as
variable, just as we did in Scheme itself. In the logic paradigm, however,
rather than use quotation to distinguish symbols acting as constants, it is
more common to use some other convention. In the Lisp community it has been
common to use a prefixed question mark. In this implementation we will treat a
variable as a list <tt>[? v )</tt> where <tt>v</tt>
is (conventionally) a symbol.
<pre><code>
define var_?(e);
ispair( e) and hd(e) = "?"
enddefine;
</code></pre>
<p>
This means that any Scheme atom is a <em>constant</em>
in our representation of logic:
<pre><code>
define constant_? = atom;
enddefine;
</code></pre>
<p>
and we may use <tt>equal?</tt> to determine if two constants are the same:
<pre><code>
define =_constant_? = nonop =;
enddefine;
</code></pre>
<a name = "unification">
<h2>3 Implementing the Unification Algorithm</h2>
<h3>3.1 Binding variables to values; making frames</h3>
<p> To unify two terms <tt>t1</tt> and <tt>t2</tt> we must <em>determining
a mapping f from variables to constants under which</em> <tt>f(t1) =
f(t2)</tt>. We will refer to our representation of such a mapping as a <em>
frame</em>. However let us take an abstract approach, and suppose that we
have the following operations:
<pre>
make_binding Makes a "binding" that is a pair associating
a variable with a value.
binding_in_frame Finds a binding for a given variable in a frame
binding_value Extracts the value component of a binding.
extend Adds a new variable-value binding to a frame.
</pre>
<p>
We may implement these operations using a-lists for frames as follows:
<pre><code>
define make_binding = conspair;
enddefine;
define binding_in_frame = assoc_scm;
enddefine;
define binding_value = back;
enddefine;
define extend(var, val, frame);
make_binding(var, val) :: frame
enddefine;
</code></pre>
<h3>3.2 The function unify</h3>
<p> To unify two terms <tt>p1</tt> and <tt>p2</tt> we write the function
<tt>unify</tt>. It takes 3 arguments, <tt>p1, p2 frame</tt>. Here the
<tt>frame</tt> serves the same role as a frame in the environment in our
Scheme interpreter _ it remembers what variables have been bound to and so
avoids having to perform excessive substitution. However in the case of
unification there are extra possibilities that have to be taken into
account
<ul>
<li>
Unification may not be possible. For example if we cannot unify 2 with 3
To deal with this possibility, we allow the frame to take the value
<tt>false</tt>. This should be distinguished from the empty frame
<tt>[)</tt> which binds no
variables. For example we may unify 2 with 2, and the resulting frame is
<tt>[)</tt>. Note that here we are depending on the use of Scheme
being implemented to the IEEE standard in which the empty list and the
false truth value are distinct.
<li>
Variables may be bound to variables. Thus <tt>[? x)</tt>
unifies with <tt>[? y)</tt> as
specified in the frame which we can think of as
<tt>( ([? x) . [? y))).</tt> but which will actually print as
<tt>( ([? x) ? y)).</tt>
</ul>
<p>
So let us consider the unification function. Essentially there are the
following cases to consider
<ul>
<li>
[1] <tt>frame</tt>
may be <tt>false</tt>.
This case can arise because the unification has already
failed but the code has not yet checked that this had happened. Checking here
simplifies case [5] below. (As well as it being good practice to check for all
types of values an argument can have)
<li> [2] <tt>p1</tt> may be a variable. This case turns out to be quite
complicated, since <tt>p1</tt> may actually be bound in the <tt>frame</tt>
and so is not free to be rebound. For example if we try to unify <tt>(+ [?
x) [? x))</tt> with <tt>(+ 2 3)</tt> we will first
unify the variable <tt>[? x)</tt> with <tt>2</tt> obtaining the frame
<tt>(([? x) . 2))</tt>. Then we will need to unify <tt>[? x)</tt> with
<tt>3</tt>. At this point the unification must fail. So we "pass the buck"
to an auxiliary function unify_var.
<li> [3] <tt>p2</tt> may be a variable. Again we call <tt>unify_var</tt>,
but with its arguments reversed. This means that the first argument of
<tt>unify_var</tt> is always guaranteed to be a variable, simplifying its
implementation.
<li> [4] If <tt>p1</tt> is a constant, then <tt>p2</tt> must also be a
constant, and it must be the <em>same</em> constant.
<li> [5] If <tt>p2</tt> is a constant then <tt>p1</tt> could not have been
a constant, so unification fails.
<li> [6] Otherwise both <tt>p1</tt> and <tt>p2</tt> must be lists which
represent complex terms and not variables. So we call <tt>unify</tt>
recursively on the <tt>hd</tt> and <tt>cdr</tt> of both. However we have
to treat <tt>frame</tt> as a kind of accumulator, in much the same way as
we needed an accumulator when we were making a list of the nodes of a
ordered tree. So we call <tt>unify</tt> on the <tt>hd</tt>'s of the two
terms [6.2] obtaining a new frame which embodies any variable bindings
necessary to unify the <tt>cdr</tt>'s, and then we pass this frame to
another call of <tt>unify</tt> [6.1] which proceeds to unify the
<tt>cdr</tt>'s, keeping the unification consistent with the bindings made
in the <tt>hd</tt>'s. </ul>
<pre><code>
define unify(p1, p2, frame);
if not(frame) then false ;;;[1]
elseif var_?(p1) then unify_var(p1, p2, frame) ;;;[2]
elseif var_?(p2) then unify_var(p2, p1, frame) ;;;[3]
elseif constant_?(p1) then ;;;[4]
if constant_?(p2) then
if =_constant_?(p1, p2) then frame
else false
endif
else false
endif
elseif constant_?(p2) then false ;;;[5]
else ;;;[6]
unify (
tl(p1), ;;;[6.1]
tl(p2),
unify( ;;;[6.2]
hd(p1),
hd(p2),
frame))
endif
enddefine;
</code></pre>
<h3>3.3 The unify_var function unifies a variable with a term</h3>
<p> Now
we come to the definition of <tt>unify_var</tt>. There is a
subtlety here that needs to be dealt with. What happens if we try to unify
a variable with a term that <em>already contains the variable</em>. Suppose
we want to unify a variable <tt>[? x)</tt> with the term <tt>(f [?
x))</tt>; we can only do this if we systematically replace the variable
<tt>[? x)</tt> with <tt>(f [? x))</tt> everywhere in our terms, including
<em>inside</em> the term <tt>(f [? x))</tt>. But this involves an infinite
amount of work generating an infinite term. This kind of thing can happen
if we try to unify:
<pre>
[g [? x] [f [? x]]]
[g [? y] [? y]]
</pre>
<p> Standard logic does not allow such a circular substitution to happen,
and so we have to put an <em>occurs check</em> into <tt>unify_var</tt>
which makes sure that we do not unify a variable with a term in which it
occurs. [It should be noted that the best known computer language based on
the Logic Paradigm, Prolog, does not perform the occurs check because it is
computationally expensive].
<p> So let us now consider the definition of <tt>unify_var</tt>. Firstly
[1] we have to allow for the possiblity that <tt>val</tt> is a variable,
and indeed is the same variable as <tt>var</tt>. In this case, unification
is trivial with the existing <tt>frame</tt> [1.1].
<p> Otherwise we find the value_cell for the value of the variable in the
<tt>frame</tt> [2].
<p> If [3] it does not exist, then we may bind <tt>var</tt> to be
<tt>val</tt> in a new frame made by <tt>extend</tt> [3.2]. However this is
only legal if the "occurs_check" implemented by <tt>freefor_?</tt> succeeds
[3.1] - otherwise unification fails [3.3].
<p>
If [4] the variable <tt>var</tt> <em>was</em>
bound in the <tt>frame</tt>, then we call <tt>unify</tt> to see if
the value of <tt>var</tt> in <tt>frame</tt> and <tt>val</tt> can be unified.
<pre><code>
define unify_var(var,val,frame);
if var = val then ;;;[1]
frame ;;;[1.1]
else
let value_cell = binding_in_frame(var, frame) ;;;[2]
in
if not(value_cell) then ;;;[3]
if freefor_?(var, val, frame) ;;;[3.1]
then extend(var, val, frame) ;;;[3.2]
else false ;;;[3.3]
endif
else
unify (binding_value(value_cell), ;;;[4]
val,
frame)
endif
endlet
endif
enddefine;
</code></pre>
<h3>3.4 Implementing the occurs check with freefor_?</h3>
<p>
Finally let us look at <tt>freefor_?</tt>.
We write this with an internal recursive
function <tt>freewalk</tt>
to save us the trouble of passing more than one argument.
So, we are asking, is <tt>expr</tt>
free of occurrences of <tt>var</tt>? We recurse through
<tt>freewalk</tt>,
examining the various possible types of the expression <tt>e</tt>.
<ul>
<li> [1] If <tt>e</tt> is a constant, then <tt>var</tt> does not occur in
it!
<li> [2] If <tt>e</tt> is a variable, and is the same variable as
<tt>var</tt> then
[2.1] <tt>var</tt> does occur in <tt>e</tt>, so we return
<tt>false</tt>. Othewise, we look up <tt>e</tt> in <tt>frame</tt> [2.2]. If
<tt>e</tt> is not bound, then we conclude that <tt>e</tt> is free of
<tt>var</tt>, so we return <tt>true</tt> [2.3]. If it is bound, then we look
to see if <tt>var</tt> occurs in the value to which <tt>e</tt> was bound
[2.4].
<li> [3] Otherwise <tt>e</tt> must be a complex term, so we look to see if
the <tt>hd</tt> of <tt>e</tt> is free of <tt>var</tt>. If it is, the
<tt>cdr</tt> of <tt>e</tt> must also be free of <tt>e</tt>.
<li> [4] Finally, if <tt>(hd e)</tt>
was not free of <tt>var</tt> then <tt>e</tt> itself
is not free of <tt>var</tt>.
</ul>
<pre><code>
define freefor_?(var, expr, frame);
define freewalk(e);
if constant_?(e) then true ;;;[1]
elseif var_?(e) then ;;;[2]
if var = e then false ;;;[2.1]
else
let b = binding_in_frame(e, frame) ;;;[2.2]
in
if not(b) then true ;;;[2.3]
else freewalk(binding_value(b)) ;;;[2.4]
endif
endlet
endif
elseif freewalk(hd(e)) then
freewalk (tl(e)) ;;;[3]
else false
endif
enddefine;
freewalk(expr)
enddefine;
</code></pre>
<h3>3.5 Examples of the use of unify</h3>
<p>
And here are some examples of the use of <tt>unify</tt>
and associated functions.
<pre><code>
AnExample(
freefor_?( [? x], [f [? x]], []) = false);
AnExample(
freefor_?([? x], [f [? x]], []) = false);
AnExample(
unify("Liz", "Phil", []) = false);
AnExample(
unify([+ a b], [+ a b], []) = []);
AnExample(
unify([+ a 2], [+ a b], []) = false);
AnExample(
unify([+ [? a] 4], [+ b 4], []) =
[% conspair([? a],"b") %]);
AnExample(
unify([+ [? a] [? a]], [+ b b], []) =
[% conspair([? a],"b") %]);
AnExample(
unify([+ [? a] [? a]], [+ 4 3], []) =
false)
AnExample(
unify( [+ [? a] 7], [+ 4 [? b]], []) =
[%
conspair([? b],7),
conspair([? a],4)
%]);
</code></pre>
</body>
</html>
|