【问题标题】:use of &rest and &key at the same time in Common Lisp在 Common Lisp 中同时使用 &rest 和 &key
【发布时间】:2019-06-22 02:54:54
【问题描述】:

我想同时使用&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?

【问题讨论】:

  • 如果你想让 &rest 表示这个意思,你需要有自己的 defun 来包装标准的 defun 并实现你想要的调用约定。在 CL 中,rest 和 key 可以一起使用,但要按照lispworks.com/documentation/HyperSpec/Body/03_da.htm中的规则进行
  • 请注意,您可以使用&allow-other-keys,这可能会帮助您解决一些问题(并非全部:参数的数量仍然需要偶数)。

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


【解决方案1】:

&key&rest 其实非常 在 Common 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.

【讨论】:

  • 这是一个很好的观点:根据我的经验,&allow-other-keys 在为 GF 实施方法时通常很重要(还有可怕的 (IMO) :allow-other-keys 关键字参数)。不过,所有这些都需要偶数个参数。
【解决方案2】:

在 Common Lisp 的函数定义中,将剩余参数与关键字参数混合通常不是一个好主意。如果这样做,您可能应该考虑重写函数定义,因为它可能导致一些意外行为。如果 &rest 和 &key 都出现在参数列表中,那么这两件事都会发生——所有剩余的值,包括关键字本身,都被收集到一个绑定到 &rest 参数的列表中,并且适当的值也绑定到 &key参数。因此 (name "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 参数和关键字参数,您将无法输入任何与您的关键字参数不匹配的其余参数,这就是您在此处进入断循环的原因。

现在,如果你想创建一个宏,从技术上讲,你可以在定义中使用多个参数列表,并在一个列表中添加关键字参数,在另一个列表中添加 &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> 

【讨论】:

  • “将rest参数与关键字参数混合通常不是一个好主意”反对“&key和&rest的组合实际上在Common Lisp中很常见”,虽然两者都有一点道理我会第二个答案! rest 参数与 allow-other-keys 一起使用,很好地允许其他键 ;)。
【解决方案3】:

这是一个示例,说明您可以如何做您想做的事。这是相当简单的,但它允许您定义带有任意数量参数的函数,以及零个或多个关键字参数。然后有一个小蹦床,它从参数中提取关键字及其值并适当地调用函数。

这并不意味着是生产质量的代码:让蹦床制作函数确切地知道它正在寻找的关键字显然更好,例如,哪些关键字是已知的,而不仅仅是“任何关键字”。

(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 相同的事情:特别是我认为它足以正确定义函数(而不是在编译时定义它)但编译器可能无法实现该函数存在于编译时(因此可能会出现警告),并且它也不会执行任何特定于实现的魔法。

【讨论】:

  • 这是一个很好的解决方案,可以满足某些人可能期望的 rest/key 形式。
【解决方案4】:

不支持您尝试执行的操作。在 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")

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-08-03
    • 2020-07-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-06-11
    相关资源
    最近更新 更多