In article <······················@cs.cornell.edu> ·····@cs.cornell.edu (T. V. Raman) writes:
Hi!
I need to supply a keyword argument or at least an optional argument
with a default value to a macro.
Lisp does not permit
(defmacro (&key (..) &body body)
What is the correct way of writing this?
(defmacro foo ((&key ...) &body body) ...)
Thanks,
--Raman
--
Tim Moore ·····@cs.utah.edu {bellcore,hplabs}!utah-cs!moore
"Wind in my hair - Shifting and drifting - Mechanical music - Adrenaline surge"
- Rush
Date: Thu, 27 Aug 1992 13:55 EDT
From: Tim Moore <·····@cs.utah.edu>
In article <······················@cs.cornell.edu> ·····@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.