[Date Prev] [Date Next] [Thread Prev] [Thread Next] Date Index Thread Index Search archive:
Date:Mon Nov 18 12:29:21 1992 
Subject:Re: A little Pop history 
From:Steve Knight 
Volume-ID:921119.01 

Nice to hear from Jonathan again.  So as a greeting I'll stick the boot
in on his implementation of recursivemember :-)

Jonathan implements recursivemember like this:
> 	define recursivemember(item, list);
> 	var item, list, element;
> 		if list = [] then
> 			return(false)
> 		endif;
> 		for el in list do
> 			if el = item then
> 				return(true)
> 			elseif islist(el) and recursivemember(item, el) then
> 				return(true);
> 			endif
> 		endfor;
> 		return(false);
> 	enddefine;

I still don't much like this as a solution.  For example, you can never
find [] in a tree -- but you can find all other lists.  We need to take
a systematic view of the meaning of lists representing tress.  We can 
either adopt the view that the trees are built up from "dotted pairs", to 
borrow Lisp-speak, or that the lists simply represent linear 
collections.

The first view leads to this solution.  But look how horribly [] gets
treated again.

define recmem( x, tree );
    x = tree or
    tree.islist and not( tree.null ) and
    ( recmem( x, tree.hd) or recmem( x, tree.tl ) )
enddefine;

Ikk.  The more typical Pop-style solution is to treat the lists as being
linear collections rather than binary trees.  This gives us the following
solution

define recmem( x, tree ); 

    define recmemlist( x, list );
        not( list.null ) and
        ( recmem( x, list.hd ) or recmemlist( x, list.tl )
    enddefine;
            
    x = tree or tree.islist and recmemlist( x, tree )
enddefine;

This is obviously an inefficient solution -- it chews up stack space
like there is no tomorrow, because no implementation of Pop does
tail-call optimisation.  So you need to modify the solution like this

define recmem( x, tree );

    define recmemlist( x, list );
        not( list.null ) and
        ( recmem( x, list.hd ) or chain( x, list.tl, recmemlist ) )
    enddefine;

    x = tree or tree.islist and chain( x, tree, recmemlist )
enddefine;

The call to "chain" forces the currently active procedure to exit and
invoke the procedure on top of the stack.  In other words this is a manual
implementation of tail-call optimisation. 

As an alternative, you might choose to expand the recursion into a
loop.  But that's a detail and there would be only a minor 
performance gain.  And you would use up more stack space.  So take
your pick.  Here's the above routine converted to use a loop.

define recmem( x, tree );
    x = tree or
    tree.islist and
    lblock                               ;;; begin new lexical block.
        lvars result = false;            ;;; tree might be null.
        until tree.null do
            ;;; Nice Pop idiom.  "dest" returns head and tail of a 
            ;;; non-empty list.  So E.dest -> E is an expression that
            ;;; steps E down the list and leaves the head on the stack.
            recmem( x, tree.dest -> tree ) -> result;    
            quitif( result );            ;;; shortcut when we find true.
        enduntil;
        result                           ;;; push the result.
    endlblock
enddefine;

Since this version was created from the recursive version, it retains all
the behaviour we want.  In particular, it treats [] as a legitimate search
target and doesn't make it a special case.  One criticism, however, would 
be that the algorithm doesn't immediately generalise to a representation 
of trees as vectors (1D arrays).  

To remedy that, we can introduce the appropriate for loop:

define recmem( x, tree );
    x = tree or
    x.isvector and
    lblock
        lvars result = false;
        for i in_vector tree do
            ;;; Another cute idiom.  ->> is an assignment that duplicates
            ;;; the value on the stack before the assignment.
            quitif( recmem( x, t ) ->> result )
        endfor;
        result
    endlblock
enddefine;

This version can be trivially modified to cope with any data type
that provides a loop or iteration procedure.  It has no special treatment
for any element.  It is efficient in both space and time, although not 
quite optimal in either.  And a mere 11 lines of non-commented code.

Yow!

Steve