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.

在单一函数里使用所有四种类型形参的情况虽然罕见,但也是可能的。无论何时,当用到多种类型的形参时,它们必须以这样的顺序声明:首先是必要形参,其次是可选形参,再次是剩余形参,最后才是关键字形参。但在使用多种类型形参的函数中,一般情况是将必要形参和另外一种类型的形参组合使用,或者可能是组合 &optional 形参和 &rest 形参。其他两种组合方式,无论是 &optional 形参还是 &rest 形参,当与 &key 形参组合使用时,都可能导致某种奇怪的行为。

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:

&optional 形参和 &key 形参组合使用时会产生令人惊奇的结果,因此也许应该避免将它们一起使用。问题出在如果调用者没有为所有可选形参提供值时,那么没有得到值的可选形参将吃掉原本用于关键字形参的关键字和值。例如,下面这个函数很不明智地混合了 &optional&key 形参:

  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.

这是因为关键字 :z 被作为一个值填入到可选的 y 形参中了,只留下了参数 3 被处理。在这里,Lisp 期待一个成对的键/值,或者什么也没有,否则就会报错。也许更坏的是,如果该函数带有两个 &optional 形参,上面最后一个调用将导致值 :z 和 3 分别被绑定到两个 &optional 形参上,而 &key 形参 z 将得到默认值 NIL,而不声明缺失了东西。

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

一般而言,如果正在编写一个同时使用 &optional 形参和 &key 形参的函数,可能就应该将它变成全部使用 &key 形参的形式——它们更灵活,并且总会可以在不破坏该函数的已有调用的情况下添加新的关键字形参。也可以移除关键字形参,只要没人在使用它们。 一般而言,使用关键字形参将会使代码相对易于维护和拓展——如果需要为函数添加一些需要用到新参数的新行为,就可以直接添加关键字形参,而无需修改甚至重新编译任何调用该函数的已有代码。

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:

虽然可以安全地组合使用 &rest 形参和 &key 形参,但其行为初看起来可能会有一点奇怪。正常地来讲,无论是 &rest 还是 &key 出现在形参列表中,都将导致所有出现在必要形参和 &optional 形参之后的那些值被特别处理——要么作为 &rest 形参被收集到一个形参列表中,要么基于关键字被分配到适当的 &key 形参中。如果 &rest&key 同时出现在形参列表中,那么两件事都会发生——所有剩余的值,包括关键字本身,都将被收集到一个列表里,然后被绑定到 &rest 形参上;而适当的值,也会同时被绑定到 &key 形参上。因此,给定下列函数:

  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)