Common Lisp 中 &rest 和 &key 参数的 Lambda 列表错误
Lambda List Error with &rest and &key arguments in Common Lisp
下面的函数旨在用几个参数创建一个符号。但是,调用它会产生关键字错误。
(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 参数的正确方法。
感谢您帮助验证此函数的预期操作。
编辑:根据下面的评论,当参数是 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))))
基本上你不能这样做。使 intern
不是关键字参数或进行您自己的关键字解析。下面是普通函数的参数解析规则:
- 一个函数有三种用于解析的参数:必需的、可选的和剩余的
- 任何出现在 lambda 列表关键字(例如
&optional
)之前的参数都是必需的参数。他们必须通过。对于进一步的步骤,只计算在所需参数之后传递的参数。
- 在 lambda 列表中的必需参数之后可能会出现
&optional
,然后是可选参数。如果有任何传递的参数尚未解析,则这些参数将被视为可选参数,直到没有可选参数需要解析为止。如果没有参数需要解析,那么我们就完成了。
- 在可选参数(如果有的话)之后可以有一个剩余参数(
&rest
后跟一个要绑定的符号),关键字参数(前缀为 &key
和 &allow-other-keys
修改解析的关键字。任何在此阶段尚未解析的传递参数称为剩余参数。这是它们解析的方式:
- 如果 lambda 列表中有一个
&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
这有点奇怪。
显示问题所在的一个非常小的例子是:
(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, but I think it would be messy. I remembered reading about this kind of situation in Practical Common Lisp,其中 Peter Seibel 写道(强调我的):
The other two combinations, either &optional or &rest parameters combined with &key parameters, can lead to somewhat surprising behavior.
我建议将两个参数列表分开。 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
下面的函数旨在用几个参数创建一个符号。但是,调用它会产生关键字错误。
(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 参数的正确方法。
感谢您帮助验证此函数的预期操作。
编辑:根据下面的评论,当参数是 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))))
基本上你不能这样做。使 intern
不是关键字参数或进行您自己的关键字解析。下面是普通函数的参数解析规则:
- 一个函数有三种用于解析的参数:必需的、可选的和剩余的
- 任何出现在 lambda 列表关键字(例如
&optional
)之前的参数都是必需的参数。他们必须通过。对于进一步的步骤,只计算在所需参数之后传递的参数。 - 在 lambda 列表中的必需参数之后可能会出现
&optional
,然后是可选参数。如果有任何传递的参数尚未解析,则这些参数将被视为可选参数,直到没有可选参数需要解析为止。如果没有参数需要解析,那么我们就完成了。 - 在可选参数(如果有的话)之后可以有一个剩余参数(
&rest
后跟一个要绑定的符号),关键字参数(前缀为&key
和&allow-other-keys
修改解析的关键字。任何在此阶段尚未解析的传递参数称为剩余参数。这是它们解析的方式:- 如果 lambda 列表中有一个
&rest
参数,将其绑定到任何尚未解析的参数。 - 如果有任何关键字参数,则要求其余参数的数量为偶数,并且对于每个剩余参数,首先读取一个键并将其与未绑定关键字参数的符号匹配。将该参数绑定到下一个剩余参数。如果键重复,则为错误。如果一个键是未知的,除非指定了
&allow-other-keys
关键字,否则它是一个错误。
- 如果 lambda 列表中有一个
可以想象以下转换:
(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
这有点奇怪。
显示问题所在的一个非常小的例子是:
(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, but I think it would be messy. I remembered reading about this kind of situation in Practical Common Lisp,其中 Peter Seibel 写道(强调我的):
The other two combinations, either &optional or &rest parameters combined with &key parameters, can lead to somewhat surprising behavior.
我建议将两个参数列表分开。 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