Page 1 of 1
Combine &key and &optional arguments in macro
Posted: Tue May 22, 2012 10:56 am
by wvxvw
I was trying to come up with a macro that would pass all arguments to the function it calls, but I can't figure out how to pass them (the function has both &key and &optional arguments)
Code: Select all
(defmacro populated-hash-table
((;; &optional
;; hash-function weakness synchronized
&key
(test ''eq)
(size 1000)
(rehash-size 1.5)
(rehash-threshold 1))
&body body)
`(let ((table (make-hash-table
;; ,hash-function ,weakness ,synchronized
:test ,test
:size ,size
:rehash-size ,rehash-size
:rehash-threshold ,rehash-threshold
)))
,@(mapcar #'(lambda (x)
`(setf (gethash ',(car x) table)
,(cdr x))) body)
table))
(populated-hash-table (:test #'equal) (a . 1) (b . 2) (c . 3))
(let ((test-table (populated-hash-table (:test #'equal) (a . 1) (b . 2) (c . 3))))
(loop for x being the hash-keys in test-table
for y being the hash-values in test-table
do (format t "~s => ~s~&" x y)))
So, it works with just the &key arguments, but it doesn't if I try to add the &optional ones :S
Re: Combine &key and &optional arguments in macro
Posted: Wed May 23, 2012 6:04 am
by pjstirling
The simplest way to do what you want is to add a &whole parameter at the beginning of the argument list, that argument will receive the entire form as it appeared in your source code.
As a general rule you want to avoid mixing &key with &optional because if you don't specify all of the optional arguments then the compiler thinks that you meant to use the keyword as one of the optional arguments.
Code: Select all
(defun test (&optional foo bar &key baz)
(format t "foo ~a, bar ~a, baz ~a~%" foo bar baz))
CL-USER> (test 1 2)
foo 1, bar 2, baz NIL
NIL
CL-USER> (test :baz 3)
foo BAZ, bar 3, baz NIL
NIL
BTW sbcl gives you a style warning if you try to do this, because of this very issue.
You could fix this by manually scanning your &optional arguments for one of your &key argument keywords and then some setf action, but it is generally better to just make all optional args keyword args.
Re: Combine &key and &optional arguments in macro
Posted: Wed May 23, 2012 1:31 pm
by wvxvw
The thing is - those optional arguments are what SBCL adds on top of the standard make-hash-table arguments, and I'd like to keep them in the macro just the same way they appear in the original function.
As a general rule [...]
Yup, I've noticed - which makes them pretty much mandatory, in the contrast to the keyword used to declare them

I think I'll just go with the all-keywords option then.
Thanks.
PS. Actually, after you said that, I went to look at the function's source and it appears to have all of them as keywords - for w/e reason slime's autocompletion showed them as optional arguments instead.
Re: Combine &key and &optional arguments in macro
Posted: Thu May 24, 2012 11:31 am
by pjstirling
As I said before, the easiest way to do what you want is to use a &whole arg for your macro:
Code: Select all
(defmacro populated-hash-table (&whole whole)
`(let ((table (make-hash-table ,@(rest whole))))
...))
(CAR whole) in this case would be the symbol POPULATED-HASH-TABLE.
Re: Combine &key and &optional arguments in macro
Posted: Fri May 25, 2012 2:41 am
by wvxvw
Sorry, this actually does something strange for me... it complains about me not passing in the correct number of arguments, and it says that no more then 0 (zero) arguments are expected to satisfy the lambda list containing &whole. I.e. the code as you posted it won't work for me (won't create a hash table, unless I call it with no arguments).
EDIT: However, if I do it like this:
Code: Select all
(defmacro whole-hash-table ((&rest whole) &body body)
`(let ((table (make-hash-table ,@whole)))
,@(mapcar #'(lambda (x)
`(setf (gethash ',(car x) table)
,(cdr x))) body)
table))
I get the desired effect. Nevertheless, I'd like to know how to use &whole - I wasn't able to use it in any way that I could pass an argument with it.
Re: Combine &key and &optional arguments in macro
Posted: Sat May 26, 2012 4:54 pm
by pjstirling
Re: Combine &key and &optional arguments in macro
Posted: Sun May 27, 2012 5:53 am
by wvxvw
Thanks, I've red that, but I can't make any legitimate use of the &whole keyword, unless I'm not passing any arguments, including your example (it doesn't compile for me, if any arguments are given). So, what I'm trying to understand is:
(most probably) I'm doing something wrong, but I don't know what.
(less probably) this keyword isn't implemented in SBCL, the version I'm using - which would also explain it.
I.e. consider this simplified example:
Code: Select all
(defmacro dummy (&whole whole)
`(progn
,@(rest whole)))
(dummy (princ 1) (princ 2) (princ 3))
Won't compile, arguing that I must not pass any arguments to dummy.
EDIT:
Further experimentation showed that:
Code: Select all
(defmacro dummy (&whole whole &rest others)
`(progn
,@whole))
(dummy (princ 1) (princ 2) (princ 3))
gives:
Code: Select all
(progn dummy (princ 1) (princ 2) (princ 3))
I.e. whole is bound to the lambda list but it doesn't allow arbitrary number of arguments - those need to be specified separately. Was my understanding correct?