Combine &key and &optional arguments in macro

Discussion of Common Lisp

Combine &key and &optional arguments in macro

Postby 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
wvxvw
 
Posts: 125
Joined: Sat Mar 26, 2011 6:23 am

Re: Combine &key and &optional arguments in macro

Postby 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.
pjstirling
 
Posts: 78
Joined: Sun Nov 28, 2010 4:21 pm

Re: Combine &key and &optional arguments in macro

Postby 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.
wvxvw
 
Posts: 125
Joined: Sat Mar 26, 2011 6:23 am

Re: Combine &key and &optional arguments in macro

Postby 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.
pjstirling
 
Posts: 78
Joined: Sun Nov 28, 2010 4:21 pm

Re: Combine &key and &optional arguments in macro

Postby 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: 125
Joined: Sat Mar 26, 2011 6:23 am

Re: Combine &key and &optional arguments in macro

Postby pjstirling » Sat May 26, 2012 4:54 pm

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

Re: Combine &key and &optional arguments in macro

Postby 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?
wvxvw
 
Posts: 125
Joined: Sat Mar 26, 2011 6:23 am


Return to Common Lisp

Who is online

Users browsing this forum: Yahoo [Bot] and 3 guests