在Common Lisp中同时使用&rest和&key

时间:2019-06-22 02:54:54

标签: lisp common-lisp variadic-functions keyword-argument

我想同时使用&rest&key。但是,尝试的代码如下:

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

导致错误:

  

***-测试:(1 2 3 4 5:NAME“ hoge”)中的关键字参数应该成对出现

,并且当我仅给出诸如(test :name "hoge")之类的关键字参数时,它可以工作。可以同时使用&rest和&key吗?

4 个答案:

答案 0 :(得分:3)

在Common Lisp中的函数定义内将其余参数与关键字参数混合通常不是一个好主意。如果这样做,您可能应该考虑重写函数定义,因为它可能导致某些意外行为。如果&rest和&key都出现在参数列表中,则两种情况都会发生-所有剩余的值(包括关键字本身)都被收集到与&rest参数绑定的列表中,并且适当的值也与&key绑定参数。因此,默认情况下,(名称“ who”)关键字参数绑定到您的其余参数列表。如果您尝试输入参数(1 2 3 4 5),则会出现错误,因为它们未绑定到您的参数(名称为“ who”)。这是一个示例:

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

这是您的函数定义。如果尝试调用返回参数列表的函数,则会在此处看到&rest参数与&key参数绑定在一起:

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

通过在同一参数列表中混合&rest参数和关键字参数,您将无法输入与关键字参数不匹配的任何其余参数,这就是您在此处输入breakloop的原因。

现在,如果要创建宏,则可以从技术上在定义中使用多个参数列表,并在一个列表中添加关键字参数,在另一个列表中添加&rest(或&body)参数:

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

答案 1 :(得分:1)

这是您可能如何做想做的一个例子。这很简单,但是它允许您定义带有任意数量参数以及零个或多个关键字参数的函数。然后有一个小的蹦床,将关键字及其值从参数中拉出并适当地调用该函数。

这并不意味着要成为产品质量代码:显然,使蹦床制造功能确切地知道它正在寻找的关键字(例如,可以知道),而不只是“任何关键字”会更好。

(defun make-kw-trampoline (fn)
  ;; Given a function which takes a single rest arg and a bunch of
  ;; keyword args, return a function which will extract the keywords
  ;; from a big rest list and call it appropriately
  (lambda (&rest args)
    (loop for (arg . rest) on args
          if (keywordp arg)
          if (not (null rest))
          collect arg into kws and collect (first rest) into kws
          else do (error "Unpaired keyword ~S" arg)
          finally (return (apply fn args kws)))))

(defmacro defun/rest/kw (name (rest-arg and-key . kw-specs) &body decls-and-forms)
  ;; Define a function which can take any number of arguments and zero
  ;; or more keyword arguments.
  (unless (eql and-key '&key)
    (error "um"))
  (multiple-value-bind (decls forms) (loop for (thing . rest) on decls-and-forms
                                           while (and (consp thing)
                                                      (eql (first thing) 'declare))
                                           collect thing into decls
                                           finally (return
                                                    (values decls (cons thing rest))))
    `(progn
       (setf (fdefinition ',name)
             (make-kw-trampoline (lambda (,rest-arg &key ,@kw-specs)
                                   ,@decls
                                   (block ,name
                                     ,@forms))))
       ',name)))

所以,如果我现在定义一个这样的函数:

(defun/rest/kw foo (args &key (x 1 xp))
  (declare (optimize debug))
  (values args x xp))

然后我可以这样称呼:

> (foo 1 2 3)
(1 2 3)
1
t

> (foo 1 2 :x 4 3)
(1 2 :x 4 3)
4
t

请注意,defun/rest/kw可能不会做与defun相同的事情:特别是我认为它足以正确定义函数(而不是在编译时定义),但是编译器可能没有意识到函数在编译时就存在(因此可能会有警告),并且它也没有执行任何特定于实现的魔术。

答案 2 :(得分:1)

&key&rest实际上是非常 在Lisp Lisp中很常见,但几乎总是与 &allow-other-keys

例如,假设您要为 write,但不想列出 明确显示所有需要的关键字参数:

(defun my-write (object &rest args &key stream &allow-other-keys)
  (write "my wrapper" :stream stream)
  (apply #'write object args))

您是否会发现很多地方? &rest/&key/&allow-other-keys 模式用于实际CLOS的地方 implemented

答案 3 :(得分:0)

不支持您尝试执行的操作。在 Common Lisp 中,可变参数函数的尾随“rest”参数与关键字参数一致。也就是说,当一个函数有关键字参数时,意味着函数的尾随参数(那些跟在固定参数和可选参数后面的)被解析为关键字参数

当您指定同时存在 &key&rest 参数时,意味着尾随参数被捕获为列表,并且这些参数也被解析为关键字参数。

我的意思是关键字参数不是从尾随参数中的某个未指定位置开始的。

如果我们想要电话

(test 1 2 3 4 5 :NAME "hoge") 

要工作,那么他的函数必须有五个固定位置参数(必需参数和可选参数的某种组合加起来为五个)。那么尾随参数是 :name"hoge"。如果存在 &rest 参数,则它捕获列表 (:name "hoge")

如果剩余列表是 (1 2 3 4 5 ...) 并且关键字参数从第一个关键字开始是必要的,那么您需要自己编写代码。

最明显的方法:

(defun test (&rest args)
  (let* ((keys (member-if #'keywordp args))
         (nonkeys (ldiff args keys)))
    (destructuring-bind (&key name) keys
      (list nonkeys name))))

测试:

[1]> (test 1 2 3 4 5 :name "hoge")
((1 2 3 4 5) "hoge")