Mixing Different Parameter Types

It’s possible, but rare, to use all four flavors of parameters in a single function. Whenever more than one flavor of parameter is used, they must be declared in the order I’ve discussed them: first the names of the required parameters, then the optional parameters, then the rest parameter, and finally the keyword parameters. Typically, however, in functions that use multiple flavors of parameters, you’ll combine required parameters with one other flavor or possibly combine **&optional** and **&rest** parameters. The other two combinations, either **&optional** or **&rest** parameters combined with **&key** parameters, can lead to somewhat surprising behavior.

Combining **&optional** and **&key** parameters yields surprising enough results that you should probably avoid it altogether. The problem is that if a caller doesn’t supply values for all the optional parameters, then those parameters will eat up the keywords and values intended for the keyword parameters. For instance, this function unwisely mixes **&optional** and **&key** parameters:

  1. (defun foo (x &optional y &key z) (list x y z))

If called like this, it works fine:

  1. (foo 1 2 :z 3) ==> (1 2 3)

And this is also fine:

  1. (foo 1) ==> (1 nil nil)

But this will signal an error:

  1. (foo 1 :z 3) ==> ERROR

This is because the keyword :z is taken as a value to fill the optional y parameter, leaving only the argument 3 to be processed. At that point, Lisp will be expecting either a keyword/value pair or nothing and will complain. Perhaps even worse, if the function had had two **&optional** parameters, this last call would have resulted in the values :z and 3 being bound to the two **&optional** parameters and the **&key** parameter z getting the default value **NIL** with no indication that anything was amiss.

In general, if you find yourself writing a function that uses both **&optional** and **&key** parameters, you should probably just change it to use all **&key** parameters—they’re more flexible, and you can always add new keyword parameters without disturbing existing callers of the function. You can also remove keyword parameters, as long as no one is using them.7 In general, using keyword parameters helps make code much easier to maintain and evolve—if you need to add some new behavior to a function that requires new parameters, you can add keyword parameters without having to touch, or even recompile, any existing code that calls the function.

You can safely combine **&rest** and **&key** parameters, but the behavior may be a bit surprising initially. Normally the presence of either **&rest** or **&key** in a parameter list causes all the values remaining after the required and **&optional** parameters have been filled in to be processed in a particular way—either gathered into a list for a **&rest** parameter or assigned to the appropriate **&key** parameters based on the keywords. 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, given this function:

  1. (defun foo (&rest rest &key a b c) (list rest a b c))

you get this result:

  1. (foo :a 1 :b 2 :c 3) ==> ((:A 1 :B 2 :C 3) 1 2 3)