我对来自R和Python的CL相当新。
我想定义一个函数,我可以传递任意数量的参数,并且如果我想要默认值中的不同值,也可以设置我可以设置的默认值的关键字。
在R中我可以这样做:
foo <- function(..., a = 1, b = 2){
list(a = a, b = b, ...)
}
foo(1, 2, 3)
foo(1, 2, 3, a = 2)
foo(b = 10, 1, 2, 3)
在PCL中,它表示你可以将&amp; key和&amp; rest参数组合起来,但如果我尝试类似
的话,它就不会像我期望的那样工作(defun foo (&rest rest &key (a 1) (b 2))
(list rest a b))
在这里,如果我指定除两个关键字args之外的任何内容,我会收到一个未知&amp;键错误:
(foo :a 100 12 3)
>> unknown &KEY argument: 12
我想要的是与此类似的功能:
(defun bar (&optional (a 1) (b 2) &rest rest)
(list rest a b))
(bar 5 4 1 2 3 4)
>>((1 2 3 4) 5 4)
但我想选择是否提供参数:a和:b。 我正在使用sbcl。
答案 0 :(得分:7)
在CL中没有标准的方法可以做到这一点。你可以让函数接受rest
参数中的所有内容,并自己解析关键字参数,如果你不想以不同的方式设计API。
那就是说,这里有几点需要进一步探索。它们可能在特定用例中很有用,但相当有限并且可以利用与实现相关的行为。
您可以在lambda列表中使用&allow-other-keys
,或在调用:allow-other-keys t
时使用foo
以防止未知密钥错误,但rest
还会包含您的密钥和值关键字参数:
CL-USER> (defun foo (&rest rest &key (a 1) (b 2))
(list rest a b))
FOO
CL-USER> (foo)
(NIL 1 2)
CL-USER> (foo :a 100 12 3 :allow-other-keys t)
((:A 100 12 3 :ALLOW-OTHER-KEYS T) 100 2)
CL-USER> (defun foo (&rest rest &key (a 1) (b 2) &allow-other-keys)
(list rest a b))
FOO
CL-USER> (foo)
(NIL 1 2)
CL-USER> (foo :a 100 12 3)
((:A 100 12 3) 100 2)
正如acelent在下面的评论中正确指出的那样,可能表示错误。它在默认优化设置下适用于CLISP,SBCL和CCL,但是通过标准,关键字参数名称(即每对参数中的第一个)必须是符号。这是否有效取决于安全级别,并且取决于实现。它应该在安全代码(安全级别为3)中发出错误信号(符合实现)。
通常,允许其他键可用于传递关键字参数,但并不完全符合您的要求。一种快速而肮脏的方式可能是在rest
中过滤关键字参数,只是删除它们及其后续元素。像这样:
CL-USER> (defun foo (&rest rest &key (a 1) (b 2) &allow-other-keys)
(let ((rest (loop for (key value) on rest by #'cddr
unless (keywordp key)
append (list key value))))
(list rest a b)))
FOO
CL-USER> (foo)
(NIL 1 2)
CL-USER> (foo :a 100 12 3)
((12 3) 100 2)
对于偶数个参数,标准只会起作用,正如coredump在他的回答中指出的那样。 (对于某些安全级别,可能在一些实现下使用奇数个参数,但它在我测试的实现中没有用。)另外,显然它不是很健壮在其他方面(关键字参数的不同位置等),并不是用于生产用途,而只是作为可能的探索的起点。
正确的解决方案是编写自己的关键字参数解析器,并将其与rest
一起使用,或者如coredump的答案中所指出的,使用不同的API。值得一提的另一点是,在CL中,apply
大量的参数通常不是一个好主意,因为它可能会导致代码效率低下。更糟糕的是,它也不是很可靠,因为允许的参数数量与实现有关。例如,在我的系统上的CCL下,call-arguments-limit
是65536.在其他实现和系统下,它可能显着 - 甚至数量级 - 更小。因此,一般来说,更喜欢reduce
到apply
大量的论点。
答案 1 :(得分:4)
基本上,这里是如何结合关键字处理可变数量的参数:
&rest
参数,如果给定的话;让我们说参数名为rest
。它是所有剩余参数的列表。&key
,则会从rest
,中提取关键字,然后预计会有偶数个参数(参见关键字和值互换的specification),如(:k1 v1 :k2 v2 ...)
中所示。 &allow-other-keys
或在调用它时提供:allow-other-keys T
,否则那么,你能做什么?
最简单的方法是定义您的函数,以便所有参数都绑定到关键字,并使用默认值。您还可以允许调用者传递其他参数:
(defun foo (&rest rest &key (a 1) (b 2) &allow-other-keys) ...)
这与您的定义非常相似,但您不能只传递值;所有论据都必须与密钥一起提供:
(foo :a 100 :max-depth 12 :max-try 3) ;; for example
CL不是R也不是Python:也许你不需要为函数传递这么多参数,也许你可以使用特殊变量(动态范围),泛型方法,... 现有包中的函数通常混合使用强制,可选和关键字参数。花点时间阅读标准API(例如cl-ppcre,drakma),看看如何以更惯用的方式定义函数。
总而言之,也许您确实需要使用类似R的参数定义函数。如果是这种情况,您可以尝试在函数定义表单中为lambda列表定义自己的宏,以便
(r-defun FOO ((a 1) (b 2) &rest rest) ...)
被翻译成类似:
(defun FOO (&rest args)
(let* ((rest (copy-seq args)) ;; must not modify "args"
(a (find-keyword-and-remove-from-rest :a rest :default 1)
(b (find-keyword-and-remove-from-rest :b rest :default 2))
... ;; function body
))
如果您希望获得宏扩展的乐趣,请执行此操作,但实际上,我非常确定不需要这样做。
编辑我最初修改了args
参数(之前名为rest
)而不是复制,但这是一个不好的例子。 The specification says:
当函数通过&amp; rest接收其参数时,允许(但不是必需)实现将rest参数绑定到与要应用的最后一个参数共享结构的对象。因为函数既不能检测它是否是通过apply调用,也不能检测是否(如果是)最后一个应用的参数是常量,符合的程序既不能依赖于休息列表的列表结构,也不能修改它,也不能修改该列表结构的