【问题标题】:Lambda List Error with &rest and &key arguments in Common LispCommon Lisp 中带有 &rest 和 &key 参数的 Lambda 列表错误
【发布时间】:2019-08-03 11:38:09
【问题描述】:

以下函数旨在用几个参数创建一个符号。但是,调用它会产生关键字错误。

(defun create-symbol (&rest objects &key intern (package *package*))
  "Creates a symbol from the objects."
  (let ((arg-string (format nil "~{~A~^~}" (first objects))))
    (if intern
        (values (intern arg-string package))
      (make-symbol arg-string))))

例如,(create-symbol "A" 1) 生成 Unknown &KEY argument: "A" 而不是 #:A1

如果没有提交关键字,也不确定(first objects) 是否是访问 &rest 参数的正确方法。

感谢任何帮助验证此功能的预期操作。

编辑:鉴于下面的 cmets,当参数是 lambda 列表关键字 &optional、&rest 和 &key 的组合时,看起来手动解析参数可能是一种方法。以下功能似乎符合我最初的意图:

(defun create-symbol (&rest objects&keys)
  "Creates a symbol from the objects,
   with optional keywords :intern and :package."
  (let* ((keys (member-if #'keywordp objects&keys))
         (objects (ldiff objects&keys keys))
         (arg-string (format nil "~{~A~^~}" objects)))
    (if (getf keys :intern)
      (intern arg-string (or (getf keys :package) *package*))
      (make-symbol arg-string))))

【问题讨论】:

  • 关于您的功能,(create-symbol 'a :intern t) 是什么? (create-symbol :a :intern t) 怎么样? (create-symbol :package :intern t) 怎么样?
  • 对。回到绘图板。 (谢谢。)宏能帮上忙吗?
  • 重新考虑之后,我想我还是回到标准的 lambda 列表,将第一个参数设为必需的对象或对象列表。关于 lambda 列表的复杂性的好课程!

标签: common-lisp symbols keyword-argument


【解决方案1】:

显示出了什么问题的一个非常小的例子是:

(defun test-key-rest (&rest args &key a (b t))
    (list 'args args 'a a 'b b))
(test-key-rest :a 1 :b 2); => (ARGS (:A 1 :B 2) A 1 B 2)
(test-key-rest :a 1 :b 2 "rest now?");;; Error: The passed key "rest now?" is not defined for this function
(test-key-rest :a 1 :b 2 :c 3);;; Error: The passed key :C is not defined for this function

也许可以使用&allow-other-keys,但我认为它会很乱。我记得在Practical Common Lisp 中读到过这种情况,Peter Seibel 写道(强调我的):

其他两种组合,无论是 &optional 或 &rest 参数与 &key 参数组合,都可能导致一些令人惊讶的行为。

我建议将两个参数列表分开。 destructuring-bind 让一切变得简单:

(defun test-two-arg-lists (keys &rest args)
    (destructuring-bind (&key (a nil) (b t)) keys
        (list 'a a 'b b 'args args)))
(test-two-arg-lists (list :a 1) "more" "please"); => (A 1 B T ARGS ("more" "please"))

但是我(我假设其他人)不想构造第一个关键字参数列表,所以让我们让它像我们期望的那样使用宏来评估它的参数:

(defmacro test-two-nice (keys &rest args)
    `(test-two-arg-lists (list ,@keys) ,@args))
(test-two-nice (:a 1) "more" "please"); => (A 1 B T ARGS ("more" "please"))

所以把它们放在一起:

(defun create-symbol-fn (keys &rest objects)
  "Creates a symbol from the objects."
  (destructuring-bind (&key intern (package *package*)) keys
    (let ((arg-string (format nil "~{~A~}" objects)))
      (if intern
          (values (intern arg-string package))
        (make-symbol arg-string)))))
(defmacro create-symbol (keys &rest objects)
  `(create-symbol-fn (list ,@keys) ,@objects))
(create-symbol (:intern nil) 'a 'b 'c 'd); => #:ABCD

【讨论】:

  • 很好的讨论。 Seibel 在您提到的部分中的评论还说:“您可以安全地组合 &rest 和 &key 参数,但最初的行为可能有点令人惊讶。”然而,除了他提供的论据之外,他最后的例子似乎并没有真正起作用。
【解决方案2】:

基本上你不能这样做。要么使 intern 不是关键字参数,要么自己进行关键字解析。以下是普通函数的参数解析规则:

  1. 函数具有三种用于解析目的的参数:必需、可选和剩余
  2. 出现在 lambda 列表关键字(例如 &optional)之前的任何参数都是必需的参数。他们必须通过。对于进一步的步骤,仅计算在所需参数之后传递的参数。
  3. 在 lambda 列表中的必需参数之后可能是 &optional,然后是可选参数。如果有任何传递的参数尚未解析,则这些参数将被视为可选参数,直到没有可选参数可供解析。如果没有要解析的参数,那么我们就完成了。
  4. 在可选参数(如果有)之后可以有一个剩余参数(&rest 后跟一个要绑定的符号),关键字参数(以 &key 为前缀,&allow-other-keys 关键字修改解析。任何传递的参数在这个阶段还没有被解析的称为剩余参数。这就是它们的解析方式:
    1. 如果 lambda 列表中有 &rest 参数,请将其绑定到尚未解析的任何参数。
    2. 如果有任何关键字参数,则要求剩余参数的数量为偶数,并且对于剩下的每个参数,首先读取一个键并将其与未绑定关键字参数的符号匹配。将该参数绑定到下一个 rest 参数。如果一个键被重复,那就是一个错误。如果密钥未知,则除非指定了 &allow-other-keys 关键字,否则这是一个错误。

可以想象如下变换:

(defun f ( { args } [ &rest rest ] &key { kwargs } [ &allow-other-keys ] )
  ...)

;; - - ->

(defun f ( { args } &rest rest )
  (destructuring-bind ( &key { kwargs } [ &allow-other-keys ] ) rest
    ...))

这样可能会更清楚一点。


你可以让你的函数按照你想要的方式运行(不使用getf,尽管因为奇偶校验),但我认为这是错误的。考虑以下几点:

(defun foobar-sym (k)
  (create-symbol 'foo k 'bar))

CL-USER> (foobar-sym :baz)
#:FOOBAZBAR
CL-USER> (foobar-sym :intern)
FOO

这有点奇怪。

【讨论】:

  • 正如您所建议的,也许最好的方法是自己解析参数,只使用 &rest。如果您和 Jonathan Johansen 不介意编辑您的答案,我希望看到各种方法来做到这一点(将关键字保留在最后)。
  • @davypough 我不认为这是最好的方法,甚至可能是最好的方法。我只是声称这是可能的。我不会为你实现它。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-06-11
  • 2015-12-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多