From: rif
Subject: Combining &rest and &key
Date: 
Message-ID: <wj0wu3a4u8l.fsf@five-percent-nation.mit.edu>
Every few weeks, I find that I want to write a form (usually a macro)
that takes both &rest and &key parameters.  Invariably (for me), I
want the &key and &rest parameters to be "independent"; the keywords
are controlling aspects of the usage, and the &rest params get fed to
a call to apply or passed to another macro.  Of course, in CL, the
&rest list will itself contain the keyword pairs, which then to be
manually removed.  Is there a standard idiom for handling this sort of
thing?  I can think up various things that will work, but I'm
wondering if others have thought this through and have a Right Way.

Cheers,

rif

From: Barry Margolin
Subject: Re: Combining &rest and &key
Date: 
Message-ID: <barmar-97DD31.17480917052004@comcast.dca.giganews.com>
In article <···············@five-percent-nation.mit.edu>,
 rif <···@mit.edu> wrote:

> Every few weeks, I find that I want to write a form (usually a macro)
> that takes both &rest and &key parameters.  Invariably (for me), I
> want the &key and &rest parameters to be "independent"; the keywords
> are controlling aspects of the usage, and the &rest params get fed to
> a call to apply or passed to another macro.  Of course, in CL, the
> &rest list will itself contain the keyword pairs, which then to be
> manually removed.  Is there a standard idiom for handling this sort of
> thing?  I can think up various things that will work, but I'm
> wondering if others have thought this through and have a Right Way.

Typically the way this is handled is by having them at different levels 
of nesting:

(defmacro my-macro (((&key this that other) &rest list-of-things)
                    &body body))

-- 
Barry Margolin, ······@alum.mit.edu
Arlington, MA
*** PLEASE post questions in newsgroups, not directly to me ***
From: Ari Johnson
Subject: Re: Combining &rest and &key
Date: 
Message-ID: <g3eqc.69221$Fl5.48939@okepread04>
Barry Margolin wrote:
> In article <···············@five-percent-nation.mit.edu>,
>  rif <···@mit.edu> wrote:
> 
> 
>>Every few weeks, I find that I want to write a form (usually a macro)
>>that takes both &rest and &key parameters.  Invariably (for me), I
>>want the &key and &rest parameters to be "independent"; the keywords
>>are controlling aspects of the usage, and the &rest params get fed to
>>a call to apply or passed to another macro.  Of course, in CL, the
>>&rest list will itself contain the keyword pairs, which then to be
>>manually removed.  Is there a standard idiom for handling this sort of
>>thing?  I can think up various things that will work, but I'm
>>wondering if others have thought this through and have a Right Way.
> 
> 
> Typically the way this is handled is by having them at different levels 
> of nesting:
> 
> (defmacro my-macro (((&key this that other) &rest list-of-things)
>                     &body body))
> 

That's a good idea.  How about the macro lambda list (&key this that 
other (&rest list-of-things) &body body), or will the &body and &key 
separated by (&rest) cause confusion?  My thought is that you basically 
get the caller to just pass you a list, but they don't have to quote it.
From: Barry Margolin
Subject: Re: Combining &rest and &key
Date: 
Message-ID: <barmar-569528.01351918052004@comcast.dca.giganews.com>
In article <·····················@okepread04>,
 Ari Johnson <·····@hotmail.com> wrote:
> That's a good idea.  How about the macro lambda list (&key this that 
> other (&rest list-of-things) &body body), or will the &body and &key 
> separated by (&rest) cause confusion?  My thought is that you basically 
> get the caller to just pass you a list, but they don't have to quote it.

That won't work.  Once you use &key at a particular level, all the 
remaining arguments at that level must be keywords.

-- 
Barry Margolin, ······@alum.mit.edu
Arlington, MA
*** PLEASE post questions in newsgroups, not directly to me ***
From: Ari Johnson
Subject: Re: Combining &rest and &key
Date: 
Message-ID: <ynhqc.69383$Fl5.4048@okepread04>
Barry Margolin wrote:
> In article <·····················@okepread04>,
>  Ari Johnson <·····@hotmail.com> wrote:
> 
>>That's a good idea.  How about the macro lambda list (&key this that 
>>other (&rest list-of-things) &body body), or will the &body and &key 
>>separated by (&rest) cause confusion?  My thought is that you basically 
>>get the caller to just pass you a list, but they don't have to quote it.
> 
> 
> That won't work.  Once you use &key at a particular level, all the 
> remaining arguments at that level must be keywords.

Figures.  Is there a design reason for this or did it just get 
overlooked?  I'm finding that the latter is rarely the case with Common 
Lisp, so it makes me wonder as to the motivations for the former. :)
From: Barry Margolin
Subject: Re: Combining &rest and &key
Date: 
Message-ID: <barmar-95ACC0.01560518052004@comcast.dca.giganews.com>
In article <····················@okepread04>,
 Ari Johnson <·····@hotmail.com> wrote:

> Barry Margolin wrote:
> > In article <·····················@okepread04>,
> >  Ari Johnson <·····@hotmail.com> wrote:
> > 
> >>That's a good idea.  How about the macro lambda list (&key this that 
> >>other (&rest list-of-things) &body body), or will the &body and &key 
> >>separated by (&rest) cause confusion?  My thought is that you basically 
> >>get the caller to just pass you a list, but they don't have to quote it.
> > 
> > 
> > That won't work.  Once you use &key at a particular level, all the 
> > remaining arguments at that level must be keywords.
> 
> Figures.  Is there a design reason for this or did it just get 
> overlooked?  I'm finding that the latter is rarely the case with Common 
> Lisp, so it makes me wonder as to the motivations for the former. :)

Allowing it would make parsing argument lists harder.

-- 
Barry Margolin, ······@alum.mit.edu
Arlington, MA
*** PLEASE post questions in newsgroups, not directly to me ***
From: Ari Johnson
Subject: Re: Combining &rest and &key
Date: 
Message-ID: <vJhqc.69388$Fl5.6860@okepread04>
Barry Margolin wrote:

> In article <····················@okepread04>,
>  Ari Johnson <·····@hotmail.com> wrote:
> 
> 
>>Barry Margolin wrote:
>>
>>>In article <·····················@okepread04>,
>>> Ari Johnson <·····@hotmail.com> wrote:
>>>
>>>
>>>>That's a good idea.  How about the macro lambda list (&key this that 
>>>>other (&rest list-of-things) &body body), or will the &body and &key 
>>>>separated by (&rest) cause confusion?  My thought is that you basically 
>>>>get the caller to just pass you a list, but they don't have to quote it.
>>>
>>>
>>>That won't work.  Once you use &key at a particular level, all the 
>>>remaining arguments at that level must be keywords.
>>
>>Figures.  Is there a design reason for this or did it just get 
>>overlooked?  I'm finding that the latter is rarely the case with Common 
>>Lisp, so it makes me wonder as to the motivations for the former. :)
> 
> 
> Allowing it would make parsing argument lists harder.

I don't see why...when you are expecting to see (and (keywordp (car 
argsleft)) (cdr argsleft)) and see anything else, you're done with 
keyword argument parsing and can dump the rest to &rest, or am I wrong? 
  I like being wrong, it's a good way to learn. :D
From: Barry Margolin
Subject: Re: Combining &rest and &key
Date: 
Message-ID: <barmar-9FD9AF.03355518052004@comcast.dca.giganews.com>
In article <····················@okepread04>,
 Ari Johnson <·····@hotmail.com> wrote:

> Barry Margolin wrote:
> 
> > In article <····················@okepread04>,
> >  Ari Johnson <·····@hotmail.com> wrote:
> > 
> > 
> >>Barry Margolin wrote:
> >>
> >>>In article <·····················@okepread04>,
> >>> Ari Johnson <·····@hotmail.com> wrote:
> >>>
> >>>
> >>>>That's a good idea.  How about the macro lambda list (&key this that 
> >>>>other (&rest list-of-things) &body body), or will the &body and &key 
> >>>>separated by (&rest) cause confusion?  My thought is that you basically 
> >>>>get the caller to just pass you a list, but they don't have to quote it.
> >>>
> >>>
> >>>That won't work.  Once you use &key at a particular level, all the 
> >>>remaining arguments at that level must be keywords.
> >>
> >>Figures.  Is there a design reason for this or did it just get 
> >>overlooked?  I'm finding that the latter is rarely the case with Common 
> >>Lisp, so it makes me wonder as to the motivations for the former. :)
> > 
> > 
> > Allowing it would make parsing argument lists harder.
> 
> I don't see why...when you are expecting to see (and (keywordp (car 
> argsleft)) (cdr argsleft)) and see anything else, you're done with 
> keyword argument parsing and can dump the rest to &rest, or am I wrong? 
>   I like being wrong, it's a good way to learn. :D

But what's the benefit?  It's easy enough to just nest things a level 
deeper, as in the example I posted earlier.

-- 
Barry Margolin, ······@alum.mit.edu
Arlington, MA
*** PLEASE post questions in newsgroups, not directly to me ***
From: Edi Weitz
Subject: Re: Combining &rest and &key
Date: 
Message-ID: <m33c5yuqdi.fsf@ella.agharta.de>
On Mon, 17 May 2004 23:06:29 -0700, Ari Johnson <·····@hotmail.com> wrote:

> I don't see why...when you are expecting to see (and (keywordp (car
> argsleft)) (cdr argsleft)) and see anything else, you're done with
> keyword argument parsing and can dump the rest to &rest, or am I
> wrong? I like being wrong, it's a good way to learn. :D

Keyword arguments don't have to be keywords (i.e. they don't have to
be symbols from the KEYWORD package), so checking with KEYWORDP won't
suffice.

  * (defun foo (&key ((bar baz) 42)) baz)

  FOO
  * (foo)

  42
  * (foo 'bar 12)

  12

Edi.
From: rif
Subject: Re: Combining &rest and &key
Date: 
Message-ID: <wj0ad05q4qu.fsf@five-percent-nation.mit.edu>
> Typically the way this is handled is by having them at different levels 
> of nesting:
> 
> (defmacro my-macro (((&key this that other) &rest list-of-things)
>                     &body body))


This seems like a reasonable compromise.  However, it's not totally
ideal.  I feel like the default case, which is quite frequently used,
is that no keyword arguments are specified.  In this case, one has to
specify an empty list when one would prefer to specify nothing.  It's
a minor wart, but a wart nonetheless, especially because I suspect the
macro in question will go into very heavy rotation for me, including
at the interactive level.

I guess I started from the "little language" viewpoint of "first
imagine the syntax you'd want to use to say something, then write the
little language that lets you say that."  And the way I'd *want* to
say it is to just put the keywords first (actually second after a
single required arg) and then have everything else be &rest.  But your
suggestion does seem like it's quite close.  I guess I could also
split it into two macros, one that takes keyword args and one that
doesn't, if the more common default is to use no arguments.

rif
From: Pascal Costanza
Subject: Re: Combining &rest and &key
Date: 
Message-ID: <c8d146$vn4$1@f1node01.rhrz.uni-bonn.de>
rif wrote:

>>Typically the way this is handled is by having them at different levels 
>>of nesting:
>>
>>(defmacro my-macro (((&key this that other) &rest list-of-things)
>>                    &body body))
> 
> This seems like a reasonable compromise.  However, it's not totally
> ideal.

Maybe it's a good idea to give a concrete example. I find it is often 
easier to suggest a good solution when one doesn't discuss things in the 
abstract. And with an example I don't mean a toy example to illustrate 
your point, but rather a concrete example that you have actually 
encountered.


Pascal

-- 
ECOOP 2004 Workshops - Oslo, Norway
*1st European Lisp and Scheme Workshop, June 13*
http://www.cs.uni-bonn.de/~costanza/lisp-ecoop/
*2nd Post-Java Workshop, June 14*
http://prog.vub.ac.be/~wdmeuter/PostJava04/
From: rif
Subject: Re: Combining &rest and &key
Date: 
Message-ID: <wj0k6z9u4f6.fsf@five-percent-nation.mit.edu>
> Maybe it's a good idea to give a concrete example. I find it is often
> easier to suggest a good solution when one doesn't discuss things in
> the abstract. And with an example I don't mean a toy example to
> illustrate your point, but rather a concrete example that you have
> actually encountered.
> 
> 
> Pascal

The specific example I've been working with is a prototype of a Common
Lisp to R (www.r-project.org) gateway.  There is a macro, r, which
calls an R function.  The first argument is the name of the function
to call (not evaluated), and remaining arguments are the args to the
function (they are evaluated).  The r macro returns two values, the
first is the value of the R computation and the second is its "names"
attribute.  For example:

CL-USER> (r seq 0 4 :by .5)
#(0.0d0 0.5d0 1.0d0 1.5d0 2.0d0 2.5d0 3.0d0 3.5d0 4.0d0)
NIL
CL-USER> (r rnorm 10 :mean 6 :sd .2)
#(5.7688447001937d0 5.8903617980810346d0 6.127080083035044d0
  5.881668565738477d0 5.977100857577786d0 6.102434798305742d0
  6.16275100133376d0 6.0857938503336735d0 5.830860775537588d0
  6.310009168997096d0)
NIL
CL-USER> (r capabilities)
#(T T NIL T NIL T T T T T T T T T)
#("jpeg" "png" "tcltk" "X11" "GNOME" "libz" "http/ftp" "sockets" "libxml"
  "fifo" "cledit" "IEEE754" "bzip2" "PCRE")

At this point, I have the low-level communications hair pretty much
working, and it's a question of figuring out the best face to put on
top of it.  One issue that comes up is that in "normal" use, you
generally want the result of the R computation converted back to CL
objects, as above.  However, not infrequently, you only want to do one
R computation in order to turn around and use the same object in
another R computation, and converting it to CL in order to convert it
back to R is very wasteful, especially for large matrices (I'm
planning on using this gateway mostly for scientific graphics).  So
the first "natural" syntax I thought of for this would be to have the
above behavior by default, but be able to add a :back keyword, which,
if specified NIL, would return a pointer to the foreign R object (an
alien in CMUCL, but it's built on uffi), which could then be passed
directly to another function.  So I would *want* to say

(r rnorm :back nil 10 :mean 6 :sd .2)

However, I'm not saying it's right to want this, only that my first
intuition is to want it.  The thing I like about it is that it doesn't
require saying anything different in the most common, default,
interactive case.  But I don't like that you'd be screwed if the R
function also had a parameter named :back that you wanted to access.
At this point, I'm leaning towards having two macros, one called r
which has the functionality above, and another (maybe r-ext, for r
extended), which includes an extra level with any control parameters,
a la Barry Margolin's suggestion.

Cheers,

rif
From: Pascal Costanza
Subject: Re: Combining &rest and &key
Date: 
Message-ID: <c8dbt8$q6k$1@f1node01.rhrz.uni-bonn.de>
rif wrote:

>>Maybe it's a good idea to give a concrete example. I find it is often
>>easier to suggest a good solution when one doesn't discuss things in
>>the abstract. And with an example I don't mean a toy example to
>>illustrate your point, but rather a concrete example that you have
>>actually encountered.
>>
>>
>>Pascal
> 
> The specific example I've been working with is a prototype of a Common
> Lisp to R (www.r-project.org) gateway.

Of course, I don't have the whole picture, but this sounds to me as if 
it would be better to control the behavior with a special variable, 
roughly like this:

(defvar *with-conversion* t)

Then you can say, for a sequence of calls to r:

(let ((*with-conversion* nil))
   (r ...)
   (r ...)
   ...)

You can also add two macros with-conversion and without-conversion to 
make this look nicer.

Would this solve your problem?


Pascal

-- 
ECOOP 2004 Workshops - Oslo, Norway
*1st European Lisp and Scheme Workshop, June 13*
http://www.cs.uni-bonn.de/~costanza/lisp-ecoop/
*2nd Post-Java Workshop, June 14*
http://prog.vub.ac.be/~wdmeuter/PostJava04/
From: rif
Subject: Re: Combining &rest and &key
Date: 
Message-ID: <wj0fz9xu3ei.fsf@five-percent-nation.mit.edu>
> Of course, I don't have the whole picture, but this sounds to me as if
> it would be better to control the behavior with a special variable,
> roughly like this:
> 
> (defvar *with-conversion* t)
> 
> Then you can say, for a sequence of calls to r:
> 
> (let ((*with-conversion* nil))
>    (r ...)
>    (r ...)
>    ...)
> 
> You can also add two macros with-conversion and without-conversion to
> make this look nicer.
> 
> Would this solve your problem?
> 
> 
> Pascal
> 

I'll have to play with it, but this certainly seems like a good idea.  Thanks!

rif
From: Kalle Olavi Niemitalo
Subject: Re: Combining &rest and &key
Date: 
Message-ID: <87ad053axy.fsf@Astalo.kon.iki.fi>
rif <···@mit.edu> writes:

> But I don't like that you'd be screwed if the R function also
> had a parameter named :back that you wanted to access.

How about:

  (defmacro r (name-and-options &rest args)
    (destructuring-bind (name &key (back t))
        (if (listp name-and-options) name-and-options (list name-and-options))
      ...))

  (r (rnorm :back nil) 10 :mean 6 :sd .2)

Are you going to evaluate the :back parameter? If not, keeping it
separate from the others aids clarity, I think.
From: Lars Brinkhoff
Subject: Re: Combining &rest and &key
Date: 
Message-ID: <85k6z9u8y2.fsf@junk.nocrew.org>
rif <···@mit.edu> writes:
> > Typically the way this is handled is by having them at different levels 
> > of nesting:
> > (defmacro my-macro (((&key this that other) &rest list-of-things)
> >                     &body body))
> This seems like a reasonable compromise.  However, it's not totally
> ideal.  [...]  I guess I started from the "little language"
> viewpoint of "first imagine the syntax you'd want to use to say
> something, then write the little language that lets you say that."
> And the way I'd *want* to say it is to just put the keywords first
> (actually second after a single required arg) and then have
> everything else be &rest.

Nothing is stopping you, you just can't use &key and &rest to do it.

  (defmacro my-macro (&rest keys-and-rest)
    (multiple-value-bind (keys rest) (parse-my-args keys-and-rest)
      ...))

-- 
Lars Brinkhoff,         Services for Unix, Linux, GCC, HTTP
Brinkhoff Consulting    http://www.brinkhoff.se/
From: Duane Rettig
Subject: Re: Combining &rest and &key
Date: 
Message-ID: <4hdud92oe.fsf@franz.com>
Lars Brinkhoff <·········@nocrew.org> writes:

> rif <···@mit.edu> writes:
> > > Typically the way this is handled is by having them at different levels 
> > > of nesting:
> > > (defmacro my-macro (((&key this that other) &rest list-of-things)
> > >                     &body body))
> > This seems like a reasonable compromise.  However, it's not totally
> > ideal.  [...]  I guess I started from the "little language"
> > viewpoint of "first imagine the syntax you'd want to use to say
> > something, then write the little language that lets you say that."
> > And the way I'd *want* to say it is to just put the keywords first
> > (actually second after a single required arg) and then have
> > everything else be &rest.
> 
> Nothing is stopping you, you just can't use &key and &rest to do it.
> 
>   (defmacro my-macro (&rest keys-and-rest)
>     (multiple-value-bind (keys rest) (parse-my-args keys-and-rest)
>       ...))

You've given precisely the right answer to this question.  If you
don't like the parser that CL gives you, you write your own.  It
may even be faster.

Parsing keywords is a slow task, compared to other function-entry
tasks.  That is why there are some restrictions on it, although I
am surprised that noboody mentioned the one that is run across most
often: if the &rest list does not have an even number of elements,
the parse will fail even if the whole list is an otherwise properly
formed key list.

Many lisp problems (including CLOS implementation) use the following
paradigm:

 (def* foo (... &rest rest &key key1 key2 ... &allow-other-keys)
   ...)

usually for the purpose still of sending keywords, but ones that
otherwise _this_ definition doesn't care about.  If your problem
can restrict the &rest list to otherwise look like a keyword list,
you can use a-o-k to get around some of the parssing restrictions
on keywords in a &rest list.

-- 
Duane Rettig    ·····@franz.com    Franz Inc.  http://www.franz.com/
555 12th St., Suite 1450               http://www.555citycenter.com/
Oakland, Ca. 94607        Phone: (510) 452-2000; Fax: (510) 452-0182   
From: Svein Ove Aas
Subject: Re: Combining &rest and &key
Date: 
Message-ID: <%haqc.2102$RL3.55810@news2.e.nsc.no>
rif wrote:

> 
> Every few weeks, I find that I want to write a form (usually a macro)
> that takes both &rest and &key parameters.  Invariably (for me), I
> want the &key and &rest parameters to be "independent"; the keywords
> are controlling aspects of the usage, and the &rest params get fed to
> a call to apply or passed to another macro.  Of course, in CL, the
> &rest list will itself contain the keyword pairs, which then to be
> manually removed.  Is there a standard idiom for handling this sort of
> thing?  I can think up various things that will work, but I'm
> wondering if others have thought this through and have a Right Way.

Maybe I'm being naive, but I'd probably just wrap the stuff destined for
&rest in a list, and give the caller slightly more work.

I don't think mixing &key and &rest is a terribly good idea; I can't
figure out how you'd parse it.
From: Pascal Costanza
Subject: Re: Combining &rest and &key
Date: 
Message-ID: <c8bc9p$qrm$1@newsreader2.netcologne.de>
rif wrote:

> Every few weeks, I find that I want to write a form (usually a macro)
> that takes both &rest and &key parameters.  Invariably (for me), I
> want the &key and &rest parameters to be "independent"; the keywords
> are controlling aspects of the usage, and the &rest params get fed to
> a call to apply or passed to another macro.  Of course, in CL, the
> &rest list will itself contain the keyword pairs, which then to be
> manually removed.  Is there a standard idiom for handling this sort of
> thing?  I can think up various things that will work, but I'm
> wondering if others have thought this through and have a Right Way.

If the receiving function accepts any keyword parameters via 
&allow-other-keys, it doesn't matter much passing the whole rest list 
anyway. I don't think it hurts too much to pass them, since they usually 
don't grow too big. To the contrary, if you can declare the rest list to 
have dynamic extent, it should be cheaper.

If you really need to remove certain keyword pairs, you can do this with 
a loop:

(loop for (key value) on rest by #'cddr
       unless (member key black-list)
       append `(,key ,value))

(I haven't tested this, but it should work. Maybe use nconc instead of 
append for efficiency improvement?!?)

Another common idiom is that you want to modify the value for one or 
more keyword parameters. It's very handy that the left-most value for a 
keyword parameter overrides any other values that may come after it, as 
specified by ANSI CL. So you can just do the following:

(defun something (a b c
                   &rest rest
                   &key param
                   &allow-other-keys)
   (declare (dynamic-extent rest))
   (apply #'another-function
          a b c
          :param (modify param)
          rest))

I find this pretty cool.


Pascal

-- 
1st European Lisp and Scheme Workshop
June 13 - Oslo, Norway - co-located with ECOOP 2004
http://www.cs.uni-bonn.de/~costanza/lisp-ecoop/
From: Edi Weitz
Subject: Re: Combining &rest and &key
Date: 
Message-ID: <m3u0yeocjy.fsf@ella.agharta.de>
On Mon, 17 May 2004 23:54:01 +0200, Pascal Costanza <········@web.de> wrote:

> If you really need to remove certain keyword pairs, you can do this
> with a loop:
>
> (loop for (key value) on rest by #'cddr
>        unless (member key black-list)
>        append `(,key ,value))
>
> (I haven't tested this, but it should work. Maybe use nconc instead
> of append for efficiency improvement?!?)

Or like so:

  <http://www.google.com/groups?selm=3247672165664225%40naggum.no&output=gplain>

Edi.
From: Marco Baringer
Subject: Re: Combining &rest and &key
Date: 
Message-ID: <m265atg8hl.fsf@bese.it>
rif <···@mit.edu> writes:

> Every few weeks, I find that I want to write a form (usually a macro)
> that takes both &rest and &key parameters.  Invariably (for me), I
> want the &key and &rest parameters to be "independent"; the keywords
> are controlling aspects of the usage, and the &rest params get fed to
> a call to apply or passed to another macro.  Of course, in CL, the
> &rest list will itself contain the keyword pairs, which then to be
> manually removed.  Is there a standard idiom for handling this sort of
> thing?  I can think up various things that will work, but I'm
> wondering if others have thought this through and have a Right Way.

personally i find this confusing for regular common lisp code, but
since i wrote it for the html generating stuff, use it like this:

(attribute-bind (a &attribute b &body c)
    (1 :b 2 3 4)
  (values a b c)
==> 1
    2
    (3 4)

;;;; -*- lisp -*-

(in-package :it.bese.yaclml)

(defun parse-attribute-spec (attribute-spec)
  "Parse an attribute spec into required args, attribute args,
  other args and the body arg."
  (let* ((required '())
         (attrs '())
         (flags '())
         (body-var nil)
         (allow-other-attributes nil)
         (put (lambda (item)
                (push item required))))
    (dolist (attr attribute-spec)
      ;; the #'string= tom-follery (god i love that word) is so that
      ;; the & symbols can be read in from any package. we're kinda
      ;; faking keywords...
      (if (symbolp attr)
	  (cond
	    ((string= attr '&attribute)
	     (setf put (lambda (item)
			 (if (listp item)
			     (case (length item)
			       (1 (push (list (first item) nil) attrs))
			       (2 (push item attrs))
			       (t (error "Bad &attribute spec: ~S" item)))
			     (push (list item nil) attrs)))))
	    ((string= attr '&flag)
	     (setf put (lambda (item)
			 (push item flags))))
	    ((string= attr '&body)
	     (setf put (lambda (item)
			 (setf body-var item))))
	    ((string= attr '&allow-other-attributes)
	     (setf put (lambda (item)
			 (setf allow-other-attributes item))))
	    (t (funcall put attr)))
	  (funcall put attr)))
    (list (nreverse required) (nreverse attrs) (nreverse flags) allow-other-attributes body-var)))

(defmacro attribute-bind (attribute-spec list &body body)
  "Evaluate BODY with the values in LIST bound according to ATTRIBUTE-SPEC.

ATTRIBUTE-SPEC has the following form:

 ( required-args* [ &attribute attributes* ] 
                  [ &allow-other-attributes others ]
                  [ &body body ] )

  the symbols in REQUIRED-ARGS will be positionaly bound to the
  values in LIST. After the required args have been consumed any
  keyword value pairs will be consumed and bound to the
  corresponding attributes (binding form is just like &key in
  regular lambda lists, but only keyword symbols are allowed).

  if &allow-other-attributes is present than OTHERS will be bound
  to a list containing all the attributes in LIST which don't
  have a corresponding &attribute variable.

  if &body is present then BODY will be bound to anything
  remaining in LIST after attribute parsing is complete.

"
  (destructuring-bind (locals attrs flags allow-other-attributes body-var)
      (parse-attribute-spec attribute-spec)
    (let ((list-sym (gensym))
          (element-sym (gensym)))
      `(let ,(remove-if #'null (append locals
                                       attrs
                                       flags
                                       (list allow-other-attributes)
                                       (list body-var)
                                       (list (list list-sym list))))
	 ,(when body-var
	    `(declare (ignorable ,body-var)))
         ,@(loop
              for local in locals
              collect `(setf ,local (pop ,list-sym)))
         (iterate
          (while (and (consp ,list-sym)
                      (keywordp (car ,list-sym))))
          (let ((,element-sym (pop ,list-sym)))
            (case ,element-sym
              ,@(loop
                   for attr in attrs
                   ;; NB: ATTR is (symbol-to-bind-to default-value),
                   ;; we want to match against the keyword whose
                   ;; string name is (symbol-name symbol-to-bind-to),
                   ;; hence the intern.
                   collect `(,(intern (string (car attr)) :keyword) (setf ,(car attr) (pop ,list-sym))))
              ,@(loop
                   for flag in flags
                   collect `(,(intern (string flag) :keyword) (setf ,flag t)))
              (t
               ,(if allow-other-attributes
                    `(push (cons ,element-sym (pop ,list-sym)) ,allow-other-attributes)
                    `(error "Unrecognized attribute ~S in ~S" ,element-sym ,list-sym))))))
         ,(when (null body-var)
            `(when ,list-sym
               (warn "Ignoring extra elements in body: ~S" ,list-sym)))
         ,(when body-var
	    `(setf ,body-var ,list-sym))
         ,(when allow-other-attributes
	    `(setf ,allow-other-attributes (nreverse ,allow-other-attributes)))
         ,@(if (and (consp body)
                    (consp (car body))
                    (eql 'declare (car (car body))))
               `((locally ,@body))
               body)))))

-- 
-Marco
Ring the bells that still can ring.
Forget your perfect offering.
There is a crack in everything.
That's how the light gets in.
     -Leonard Cohen