macro flow from inside to outside

From: Joerg Hoehle (hoehle_at_users.sourceforge.net)
Date: 11/26/04


Date: 26 Nov 2004 16:47:14 +0100

Hi,

I wonder whether it's possible to avoid a full code walker[1] when
implementing the following pattern somewhat similar to several
COLLECTOR macros[2] out there.

Syntax: (bag ... (containing item &optional name) ...)

CONTAINING may occur anywhere (at any depth) within the body of BAG
and will add the given item to a possibly named container. The result
is the list of items in the unnamed container, if any.

What makes this "exercise" interesting is
1. that the NAME is obviously not known until the CONTAINING form is
   analyzed.
2. that the return value also depends on whether an optional name was
   used for any container

The hope is that such code can be compiled using lexical variables for
efficient access instead of using an internal a-list or such. The
straightforward solution would involve having the bag macro code-walk
the complete body, looking for containing forms and creating according
code.

My idea below is to use MACROLET. The difficulty is to have
information (e.g. names of bags) flow between the different parts.

[1] I wish to avoid a full code walker because of complications with
macroexpansion and special forms etc. and because of possible poor
compiler analysis when given fully macroexpanded forms. See thread
<URL http://groups.google.com/groups?hl=en&lr=&ie=UTF-8&threadm=u4qjv6z3n.fsf%40users.sourceforge.net&rnum=1&prev=/groups%3Fas_q%3Doptimization%26safe%3Dimages%26ie%3DISO-8859-1%26as_ugroup%3Dcomp.lang.lisp%26as_uauthors%3Dhoehle%26lr%3D%26hl%3Den >

Here's an simple implementation based on a dynamically-built alist.
(defmacro bag (&body body)
  `(let ((*bags* ()))
     (declare (type list *bags*)) ; need not be special
     (macrolet ((containing (x &optional (name :default))
                  (format *trace-output* "Expanding(~S)... " name)
                  `((lambda (value)
                      (push value (cdr (or (assoc ',name *bags* :test #'eq)
                                           (let ((loc (list ',name)))
                                             (push loc *bags*) loc))))
                      value) ,x))
                (result-expansion ()
                  (format *trace-output* "Stored: ~S~%" *bags*)
                  '(cdr (assoc :default *bags* :test #'eq))))
       ,@body
       (result-expansion))))
I'm not even sure it's correct w.r.t. environment restrictions.
 It seems to work in interpreted mode in CLISP.
(bag) -> nil
(bag (containing 1)) -> (1)
(bag (containing 1) (containing 2)) -> (2 1)
(bag (prin1 (list (containing 1) (containing 2)))) -> (2 1)
(bag (containing 'a) (containing 2 evens) (prin1 *bags*)) -> (a)
(bag (containing 2 evens)) -> nil

We'll ignore the missing accessor for named bags. It's a detail. If
lexical variables could be used (as with LOOP, Iterate or various
collector packages), this would be a non-issue.
We'll ignore ordering of the elements.

(defun zot(n) (bag (containing n)))
(zot 1) -> (1)

Trying to go further
(defmacro bag (&body body)
  ;; Attempt to do more work at macroexpansion time
  `(let ((*bags* ()))
     (declare (type list *bags*)) ; need not be special
     (macrolet ((containing (x &optional (name :default))
                  (format *trace-output* "Expanding(~S)... " name)
                  (or (assoc :default *bags* :test #'eq)
                      (let ((loc (list :default)))
                        (push loc *bags*) loc))
                  `((lambda (value)
                      (push value (cdr (assoc :default *bags* :test #'eq)))
                      value) ,x))
                (result-expansion ()
                  (format *trace-output* "Stored: ~S~%" *bags*)
                  '(cdr (assoc :default *bags* :test #'eq))))
       ,@body
       (result-expansion))))
causes (zot 1) to fail in CLISP because macrolet-expansion occurs when
(defun zot #) is entered at the REPL. Therefore, *bags* is nil when
zot is finally invoked, and setf cdr fails.

[13]> (defun zot(n) (bag (containing n)))
Expanding(:DEFAULT)... Stored: ((:DEFAULT))
ZOT
[14]> (zot 2)
*** - SYSTEM::%RPLACD: NIL is not a pair

Is this approach a dead end, or possible to correct?

Should macrolet be avoided, because of environment issues, and regular
macros be used instead? How to communicate information (i.e. bag
names) between the parts?

[2] I'm investigating this possibility because I feel the current
implementation of Iterate is severely impacted by the requirements and
effects of a full code walker (cf. recent bug reports in
iterate-devel, among others), and I wonder whether this is necessary.

Thanks for your help,
        Jorg Hohle
Telekom/T-Systems Technology Center



Relevant Pages

  • Re: Sean Lennons "Friendly Fire"
    ... He doesn't get in his parents *bag* nor does he adopt ... I don't think they avoid him. ... Do you think Sean's name appearing in the gossip columns is due to ...
    (rec.music.beatles)
  • Re: Luggage advice please
    ... > bag, but there's some padding in the laptop bag, and I didn't bother. ... I suppose I conciously avoid jumping kerbs etc with it on board, ... > Panniers would be fine too, I just don't bother using mine. ...
    (uk.rec.cycling)
  • Re: Sean Lennons "Friendly Fire"
    ... He doesn't get in his parents *bag* nor does he adopt ... I don't think they avoid him. ... Do you think Sean's name appearing in the gossip columns is due to ...
    (rec.music.beatles)
  • Re: Sean Lennons "Friendly Fire"
    ... He doesn't get in his parents *bag* nor does he adopt ... I don't think they avoid him. ...
    (rec.music.beatles)
  • Re: Comments on King KC-2105C Dust Collector
    ... Yes it does have a 1 micron bag. ... I have a small shop 25' x 15' and I'm looking for a dust collector. ...
    (rec.woodworking)