Combine &key and &optional arguments in macro

Discussion of Common Lisp
Post Reply
wvxvw
Posts: 127
Joined: Sat Mar 26, 2011 6:23 am

Combine &key and &optional arguments in macro

Post by wvxvw » Tue May 22, 2012 10:56 am

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

pjstirling
Posts: 166
Joined: Sun Nov 28, 2010 4:21 pm

Re: Combine &key and &optional arguments in macro

Post by pjstirling » Wed May 23, 2012 6:04 am

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.

wvxvw
Posts: 127
Joined: Sat Mar 26, 2011 6:23 am

Re: Combine &key and &optional arguments in macro

Post by wvxvw » Wed May 23, 2012 1:31 pm

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.

pjstirling
Posts: 166
Joined: Sun Nov 28, 2010 4:21 pm

Re: Combine &key and &optional arguments in macro

Post by pjstirling » Thu May 24, 2012 11:31 am

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.

wvxvw
Posts: 127
Joined: Sat Mar 26, 2011 6:23 am

Re: Combine &key and &optional arguments in macro

Post by wvxvw » Fri May 25, 2012 2:41 am

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.


wvxvw
Posts: 127
Joined: Sat Mar 26, 2011 6:23 am

Re: Combine &key and &optional arguments in macro

Post by wvxvw » Sun May 27, 2012 5:53 am

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?

Post Reply