[Date Prev] [Date Next] [Thread Prev] [Thread Next] Date Index Thread Index Search archive:
Date:Mon Nov 2 16:55:21 1999 
Subject:news-relayFrom: pop@edlab.cs.umass.edu (Robin Popplestone) 
From:PP User 
Volume-ID:991102.02 

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>