Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

help w/ &key in macros

133 views
Skip to first unread message

David Bakhash

unread,
May 30, 1998, 3:00:00 AM5/30/98
to

I am trying to do something very simple, and it just doesn't seem to
work.

I wrote this macro to define a `node'. here it is:


(defmacro defnode (name &optional question &key yes no)
`(prog1
(setq ,name (make-instance 'tree-node
:yes ,yes
:no ,no
:question (or ,question (make-instance 'question))))
(when ,yes
(setf (slot-value ,yes 'parent) ,name))
(when ,no
(setf (slot-value ,no 'parent) ,name))))

simple enough, right?

so why doesn't this work, at the toplevel:

[5] USER(21): (defnode t0
:yes (defnode t1)
:no (defnode t2))
Error: keyword list ((DEFNODE T1) :NO (DEFNODE T2)) is not an even number of items
[condition type: PROGRAM-ERROR]


I just can't seem to figure. Is there something strange about
&optional and/or &key arguments in macros? I'm lost here.

dave

{

p.s. in case it's important:

(defclass tree-node ()
((parent
:type tree-node
:accessor parent
:initarg :parent)
(yes
:type tree-node
:accessor yes
:initarg :yes)
(no
:type tree-node
:accessor no
:initarg :no)
(question
:documentation "Question"
:type question
:accessor question
:initarg :question
:initform (make-instance 'question))))


}

David Bakhash

unread,
May 31, 1998, 3:00:00 AM5/31/98
to

Basically, what I think might be happening is that if I define a
function like:

(defun f (x &optional y &key k1 k2)
(..))

and then call it like this:

(f 10 :k1 3 :k2 4)

then the &optional argument `y' gets the keyword value `:k1', and I
somehow don't agree that this is how it should be, though I can also
see how this is a subtle point, where someone had to make a decision.

So, the question is how do be able to pass the args the way I did,
such that it still works.

thanks,
dave

Erik Naggum

unread,
May 31, 1998, 3:00:00 AM5/31/98
to

* David Bakhash

| Basically, what I think might be happening is that if I define a
| function like:
|
| (defun f (x &optional y &key k1 k2)
| (..))
|
| and then call it like this:
|
| (f 10 :k1 3 :k2 4)
|
| then the &optional argument `y' gets the keyword value `:k1', and I
| somehow don't agree that this is how it should be, though I can also
| see how this is a subtle point, where someone had to make a decision.

you cannot disallow passing the keyword :K1 to the optional argument Y
via the standard argument-parsing machinery, which also cannot force the
argument list to match keywords at particular positions, such as ending
in a keyword plist. for the whole story on the ordinary lambda list:
http://www.harlequin.com/education/books/HyperSpec/Body/sec_3-4-1.html

| So, the question is how do be able to pass the args the way I did,
| such that it still works.

taking the easy way out and disallowing keywords as values of optional
arguments, which is not a brilliant solution, this could be a start.

(defun f (x &rest .arguments.)
(declare (dynamic-extent .arguments.))
(let ((y (unless (keywordp (first .arguments.)) (pop .arguments.))))
(destructuring-bind (&key k1 k2) .arguments.
...)))

this is obviously a macro-expansion... it is also not tested.

#:Erik
--
"Where do you want to go to jail today?"
-- U.S. Department of Justice Windows 98 slogan

David Bakhash

unread,
May 31, 1998, 3:00:00 AM5/31/98
to

Erik Naggum <c...@naggum.no> writes:

> taking the easy way out and disallowing keywords as values of optional
> arguments, which is not a brilliant solution, this could be a start.
>
> (defun f (x &rest .arguments.)
> (declare (dynamic-extent .arguments.))
> (let ((y (unless (keywordp (first .arguments.)) (pop .arguments.))))
> (destructuring-bind (&key k1 k2) .arguments.
> ...)))
>

is there some significance to the dot `.' before and after the
`arguments'? i.e. what are the dots for?

dave

David Bakhash

unread,
May 31, 1998, 3:00:00 AM5/31/98
to

Erik Naggum <c...@naggum.no> writes:

> * David Bakhash
> | Basically, what I think might be happening is that if I define a
> | function like:
> |
> | (defun f (x &optional y &key k1 k2)
> | (..))
> |
> | and then call it like this:
> |
> | (f 10 :k1 3 :k2 4)
> |
> | then the &optional argument `y' gets the keyword value `:k1', and I
> | somehow don't agree that this is how it should be, though I can also
> | see how this is a subtle point, where someone had to make a decision.
>
> you cannot disallow passing the keyword :K1 to the optional argument Y
> via the standard argument-parsing machinery, which also cannot force the
> argument list to match keywords at particular positions, such as ending
> in a keyword plist. for the whole story on the ordinary lambda list:
> http://www.harlequin.com/education/books/HyperSpec/Body/sec_3-4-1.html

I was able to get passed this by doing the following, MUCH simpler
trick. basically, I used &rest instead of &optional and it works,
oddly enough:

(defmacro f (x &rest y-list &key k1 k2 &aux (y (first y-list)))
(...))

and no destructuring-bind needed. I guess &rest and &optional read
parameters differently. Anyway, just wanted to let you know. It took
me a while to figure that out, and I only got there by
trial-and-error.

dave


Scott L. Burson

unread,
May 31, 1998, 3:00:00 AM5/31/98
to

David Bakhash wrote:
>
> So, the question is how do be able to pass the args the way I did,
> such that it still works.

Heh -- no, there doesn't seem to be any built-in way to do what you
want. But it's not too hard to do manually:

(defun f (x &rest stuff)
(let ((optarg (and (not (member (car stuff) '(:k1 :k2)))
(pop stuff)))
(k1 (getf stuff :k1))
(k2 (getf stuff :k2)))
...))

-- Scott

* * * * *

To use the email address, remove all occurrences of the letter "q".

Erik Naggum

unread,
Jun 1, 1998, 3:00:00 AM6/1/98
to

* David Bakhash

| is there some significance to the dot `.' before and after the
| `arguments'? i.e. what are the dots for?

to me, such dots communicate "internal use". in a macro, you would
typically have used an uninterned symbol to avoid problems, except that
this would make the argument list less than useful. "&rest .arguments."
at least says something moderately useful.

(remember that all characters can be part of symbol names, and that
people adopt many conventions to deal with cognitive overload. *foo* for
specials is a pretty solid convention, many use +foo+ for constants, some
use .foo. for internal use, I used class names like <foo> for a while,
and perhaps there are some even less widely used conventions. :)

Scott L. Burson

unread,
Jun 1, 1998, 3:00:00 AM6/1/98
to

David Bakhash wrote:
>
> I was able to get passed this by doing the following, MUCH simpler
> trick. basically, I used &rest instead of &optional and it works,
> oddly enough:
>
> (defmacro f (x &rest y-list &key k1 k2 &aux (y (first y-list)))
> (...))

I thought of this too, but it's not correct. If you supply a value for
Y, then Lisp's attempt to extract the keyword arguments gets an error
(at least, it does in ACL 4.3, and I believe this is correct behavior).
The problem is that Lisp expects the entire &REST argument to be of the
form (keyword value ...), but it has the value for Y on the front of
it. E.g.:

[1] (f 3 4 :k2 5)

Error: &key list isn't even: (4 :K2 5)

Erik is right: there really can't be a solution to this problem that
doesn't require you to provide the information that symbols :K1 and :K2
are not acceptable values for Y. Lisp can't assume that on its own.

Erik Naggum

unread,
Jun 1, 1998, 3:00:00 AM6/1/98
to

* David Bakhash

| I was able to get passed this by doing the following, MUCH simpler trick.
| basically, I used &rest instead of &optional and it works, oddly enough:
|
| (defmacro f (x &rest y-list &key k1 k2 &aux (y (first y-list)))
| (...))
|
| and no destructuring-bind needed. I guess &rest and &optional read
| parameters differently. Anyway, just wanted to let you know. It took me
| a while to figure that out, and I only got there by trial-and-error.

this shouldn't work. (f 1 2 :k1 3 :k2 4) should signal an error because
the number of elements in the list (2 :k1 3 :k2 4) is not a valid plist,
i.e., does not have an even number of elements, as per the definition of
&REST with &KEY. (f 1 :k1 2 :k2 3) should bind Y to :K1. Allegro CL
4.3, 4.3.1, and 5.0 concur. I wonder how you determined that it works.

you're _much_ better off reading and understanding the standard (or any
language specification) than spending time on random stabs in any problem
space -- you'll never know what you hit, nor whether you hit something by
accident or by design. worse, more often than not what happens to "work"
is a special case. please see the HyperSpec. if it's too hard to read,
you need to back off and realize that you shouldn't try to solve the
problem at hand until you understand what you're doing. you'll get all
the help you need here in comp.lang.lisp if you want to understand the
language (I know I have benefited thusly) instead of just randomly trying
something and then get yourself in need of help when you're confused.
more often than not, some minor expectation can block understanding and
lead you down weird paths (it has happened to me several times), and the
further you go down that path, especially with code you don't understand
why doesn't work, the harder it is to recover where you came from and
give you the guidance you need to choose the right path.

Erik Naggum

unread,
Jun 1, 1998, 3:00:00 AM6/1/98
to

[ a slightly corrected version 2. ]

* David Bakhash
| I was able to get passed this by doing the following, MUCH simpler trick.
| basically, I used &rest instead of &optional and it works, oddly enough:
|
| (defmacro f (x &rest y-list &key k1 k2 &aux (y (first y-list)))
| (...))
|
| and no destructuring-bind needed. I guess &rest and &optional read
| parameters differently. Anyway, just wanted to let you know. It took me
| a while to figure that out, and I only got there by trial-and-error.

this shouldn't work. (f 1 2 :k1 3 :k2 4) should signal an error because

the resulting rest list (2 :k1 3 :k2 4) is not a valid plist, i.e., it

David Bakhash

unread,
Jun 1, 1998, 3:00:00 AM6/1/98
to

Erik Naggum <c...@naggum.no> writes:

> more often than not, some minor expectation can block understanding and
> lead you down weird paths (it has happened to me several times), and the
> further you go down that path, especially with code you don't understand
> why doesn't work, the harder it is to recover where you came from and
> give you the guidance you need to choose the right path.

yeah. I didn't check all the case; only a couple, and they worked.
So I will avoid this and stay away from using &optional/&rest together
with &key. safest bet is that if you're gonna use &key, then any
other args should be required (in my opinion) except &aux (maybe).

dave

Barry Margolin

unread,
Jun 1, 1998, 3:00:00 AM6/1/98
to

In article <cxj7m31...@raven.bu.edu>, David Bakhash <ca...@bu.edu> wrote:
>yeah. I didn't check all the case; only a couple, and they worked.
>So I will avoid this and stay away from using &optional/&rest together
>with &key. safest bet is that if you're gonna use &key, then any
>other args should be required (in my opinion) except &aux (maybe).

Yes, this is generally recommended. Common Lisp itself promotes bad
practice due to the interface to the READ-FROM-STRING function. It was
apparently done the way it was done in order to be consistent with both
READ (which uses optional arguments) and the string and sequence functions
(which generally use :START and :END). In retrospect, this was a horrible
decision, and one of the FAQs that prompted creation of the c.l.lisp FAQ
posting was "Why does (read-from-string "a b" :start 2) return A rather
than B?"

--
Barry Margolin, bar...@bbnplanet.com
GTE Internetworking, Powered by BBN, Cambridge, MA
*** DON'T SEND TECHNICAL QUESTIONS DIRECTLY TO ME, post them to newsgroups.

Harley Davis

unread,
Jun 2, 1998, 3:00:00 AM6/2/98
to

>I am trying to do something very simple, and it just doesn't seem to
>work.
>
>I wrote this macro to define a `node'. here it is:
>
>
>(defmacro defnode (name &optional question &key yes no)
> `(prog1
> (setq ,name (make-instance 'tree-node
> :yes ,yes
> :no ,no
> :question (or ,question (make-instance 'question))))
> (when ,yes
> (setf (slot-value ,yes 'parent) ,name))
> (when ,no
> (setf (slot-value ,no 'parent) ,name))))
>
>simple enough, right?
>
>so why doesn't this work, at the toplevel:
>
>[5] USER(21): (defnode t0
> :yes (defnode t1)
> :no (defnode t2))
>Error: keyword list ((DEFNODE T1) :NO (DEFNODE T2)) is not an even number
of items
> [condition type: PROGRAM-ERROR]


In addition to the &optional/&key issue being discussed, you should also
realize that your macro will multiply evaluate the arguments 'yes' and 'no',
which is certainly not what you want. Also, expanding a defining macro into
a 'setq' is a dubious choice - I would have used another defining form or
even better create your own namespace for nodes in a top-level hash table.
Something like this (excuse any non-CL names/arg positions but you should
get the idea):

;;; Runtime portion

(defvar *node-table* (make-hash-table))

(defun put-node (name node)
(setf (gethash *node-table* name) node)
name))

(defun get-node (name)
(gethash *node-name* name))

(defun create-node (name question yes no)
(let ((node (make-instance 'tree-node :yes yes :no no
:question (or question (make-instance
'question)))))
(when yes (setf (slot-value yes 'parent) name))
(when no (setf (slot-value no 'parent) name))
(put-node name node)))

;;; Macro

(defmacro defnode (name &key question yes no)
`(create-node ',name ,question ,yes ,no))

In general, the less you put in a macro and the more you put in runtime
support, the better off you are. Exceptions are pretty rare and usually
performance-oriented hacks in special cases.

-- Harley

Thomas A. Russ

unread,
Jun 4, 1998, 3:00:00 AM6/4/98
to

David Bakhash <ca...@bu.edu> writes:
>
> I was able to get passed this by doing the following, MUCH simpler
> trick. basically, I used &rest instead of &optional and it works,
> oddly enough:
>
> (defmacro f (x &rest y-list &key k1 k2 &aux (y (first y-list)))
> (...))
>
> and no destructuring-bind needed. I guess &rest and &optional read
> parameters differently. Anyway, just wanted to let you know. It took
> me a while to figure that out, and I only got there by
> trial-and-error.

The macro F doesn't really allow you to have an optional argument,
because if you ever called it like:

(f 10 20 :k1 2)

Then the keyword processing would be messed up and an error generated
because 20 is not a keyword.

You just changed the case in which an error would occur. Also, the
value of y in legal calls to F will always be one of NIL, :K1 or :K2.
That is because

This doesn't accept the same argument list as your original example:
(defmacro f' (x &optional y &key k1 k2)
(...))

The moral of the story is that mixing optional and keyword arguments is
not something that is supported by the ANSI-CL standard in the way that
would like. As a general rule, you should either use optional arguments
or keyword arguments, but mixing them often leads to trouble.

(cf. read-from-string: If you want to read from a string starting
somewhere other than the beginning of the string you need to specify
the optional error-p and eof-marker arguments before you are allowed to
use the :start keyword.)


--
Thomas A. Russ, USC/Information Sciences Institute t...@isi.edu

David Bakhash

unread,
Jun 4, 1998, 3:00:00 AM6/4/98
to

"Harley Davis" <davis@ilog_dot_com.foo> writes:

> In addition to the &optional/&key issue being discussed, you should also
> realize that your macro will multiply evaluate the arguments 'yes' and 'no',
> which is certainly not what you want. Also, expanding a defining macro into
> a 'setq' is a dubious choice - I would have used another defining form or
> even better create your own namespace for nodes in a top-level hash table.
> Something like this (excuse any non-CL names/arg positions but you should
> get the idea):
>
> ;;; Runtime portion

I don't know how to thank you for all this help, except to say thanks.

I owe you one.

dave


0 new messages