From: KMP@stony-brook.scrc.symbolics.com (Kent M Pitman)
Newsgroups: comp.lang.lisp
Subject: Re: Macro lambda lists: &key and &body
Date: 2 Sep 92 21:49:21 GMT
Organization: LCS news/mail gateway
X-Unparseable-Date: Wed


    Date: Thu, 27 Aug 1992 13:55 EDT
    From: Tim Moore <moore@cs.utah.edu>

    In article <1992Aug27.192017.18279@cs.cornell.edu> raman@cs.cornell.edu (T. V. Raman) writes:
    ...
       Lisp does not permit
       (defmacro (&key (..) &body body)
...)

       What is the correct way of writing this?

    (defmacro foo ((&key ...) &body body) ...)

Tim's suggestion involves changing the problem specification, since you need
to write (FOO (:key1 val1 :key2 val2) ..body..) instead of 
         (FOO  :key1 val1 :key2 val2  ..body..).
If you can get away with doing that, I recommend his approach as cleanest and
simplest.  However, there are a few cases where you perhaps won't want to do
this change, and a few others where perhaps you can't (due to externally imposed
constraints.)  If you can't change the problem description, what you have to do
is something like the following:

 (defmacro foo (&rest keys-and-body)
   (let ((body keys-and-body)
	 (my-key-1)
	 (my-key-1-p nil)
	 (my-key-2)
	 (my-key-2-p nil))
     (loop
       (unless (and body (cdr body)) (return))
       (case (car body)
         ((:my-key-1)
	  (unless my-key-1-p 
	    (setq my-key-1 (cadr body) my-key-1-p t)))
	 ((:my-key-2)
	  (unless my-key-2-p 
	    (setq my-key-2 (cadr body) my-key-2-p t)))
	 (otherwise (return)))
       (setq body (cddr body)))
     (unless my-key-1-p (setq my-key-1 <my-key-1-default>))
     (unless my-key-2-p (setq my-key-2 <my-key-2-default>))
     ; Beyond here, pretend arglist was effectively
     ;   (&key (my-key-1 <my-key-1-default> my-key-1-p)
     ;         (my-key-2 <my-key-2-default> my-key-2-p)
     ;    &body body)
     ...))

My point here is that Lisp doesn't forbid you from having things with
this calling convention.  It just happens not to provide you with
built-in support for parsing those things.  But that doesn't mean you
can't write the parsing support yourself.  This technology is effectively
what is needed in the RESTART-CASE macro, which takes keywords in this way
in its clauses.

Note that if you do this a lot, you could imagine modularizing it more so
that all you could share the work needed to do the parsing among all the
clients. e.g.,

;;; General parsing utility
(defun call-with-body-and-prefix-keywords (continuation body keys)
  (declare (dynamic-extent continuation))
  (let ((parsed-keys '()))
    (loop
      (unless (and body (cdr body)
			(member (car body) keys))
        (return))
      (let ((key (car body)))
	;; Assure left-most element supersedes right in case of duplication
        (setf (getf parsed-keys key) (getf parsed-keys key (cadr body))))
        (setq body (cddr body)))
    (apply continuation body parsed-keys)))

;;; Sample client
(defmacro foo (&rest keys-and-body)
  (call-with-body-and-prefix-keywords
    #'(lambda (body &key my-key-1 my-key-2)
	`(list 1 ,my-key-1 2 ,my-key-2 :body ',body))
    keys-and-body
    '(:my-key-1 :my-key-2)))

;;; Sample call
(foo :my-key-2 7 :my-key-1 3 :my-key-1 4 foo bar baz)
=> (1 3 2 7 :BODY (FOO BAR BAZ))

Note that this solution piggy-backs off of the supplied-p and default value
mechanism already present in the langauge, and doesn't require explicit support
from the CALL-WITH-BODY-AND-PREFIX-KEYWORDS routine in order to do that parsing.

You could even write a definer that inferred the call to this subroutine,
such that all you had to write was:

(defmacro-with-body-and-prefix-keywords foo (body &key my-key-1 my-key-2)
  `(list 1 ,my-key-1 2 ,my-key-2 :body ',body))

and it would infer the rest.  I'll leave the definition of 
DEFMACRO-WITH-BODY-AND-PREFIX-KEYWORDS as an exercise to the reader.