This is a read-only archive of lispforum.com. The forum was locked to new users and posts and is preserved here as static HTML from a database snapshot taken on 2019-09-07.

Combine &key and &optional arguments in macro

7 posts · 3401 views

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)
(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

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.
(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

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

As I said before, the easiest way to do what you want is to use a &whole arg for your macro:
(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

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:
(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

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:
(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:
(defmacro dummy (&whole whole &rest others)
  `(progn
     ,@whole))

(dummy (princ 1) (princ 2) (princ 3))
gives:
(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?