Search code examples
lispcommon-lispvariadic-functionskeyword-argument

use of &rest and &key at the same time in Common Lisp


I want to use both &rest and &key at the same time. However, the attempted code below:

(defun test (&rest args &key (name "who")) nil)
(test 1 2 3 4 5 :name "hoge")

causes an error:

*** - TEST: keyword arguments in (1 2 3 4 5 :NAME "hoge") should occur pairwise

and when I gives only keyword parameter like (test :name "hoge"), it works. Is it possible to use both &rest and &key?


Solution

  • It's generally not a good idea to mix rest parameters with keyword parameters within a function definition in Common Lisp. If you do so, you should probably consider rewriting the function definition because it can lead to some unexpected behavior. If both &rest and &key appear in a parameter list, then both things happen--all the remaining values, which include the keywords themselves, are gathered into a list that's bound to the &rest parameter, and the appropriate values are also bound to the &key parameters. So the (name "who") keyword parameter is bound to your list of rest parameters by default. if you try to enter the arguments (1 2 3 4 5), you will get an error because they aren't bound to your parameter (name "who"). Here is an example:

    (defun test (&rest args &key (name "who"))
       (list args name))
    

    Here we have your function definition. If we try to call the function which return a list of the arguments, we will see that the &rest parameters are bound to they &key parameters here:

    CL-USER> (test :name "Davis")
    ((:NAME "Davis") "Davis")
    

    By mixing &rest parameters and keyword parameters in the same parameter list, you won't be able to enter any rest parameters that don't match your keyword parameter which is why you enter into a breakloop here.

    Now, if you want to create a macro, you can technically use multiple parameter lists within the definition, and add keyword parameters in one list, and &rest (or &body) parameters in the other list:

     (defmacro hack-test ((&key (name "who")) &body body)
       `(list ,name ,@body))
    
    CL-USER> (hack-test (:name "Ricky")
                    (+ 2 3))
    ("Ricky" 5)
    
    CL-USER> (hack-test ()
                     (+ 2 4)
                     (+ 4 5)
                     (+ 9 9))
    ("who" 6 9 18)
    CL-USER>