Warning: file_get_contents(/data/phpspider/zhask/data//catemap/8/design-patterns/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Common lisp Common lisp:我可以用任意数量的参数和可选的关键字参数定义函数吗?_Common Lisp - Fatal编程技术网

Common lisp Common lisp:我可以用任意数量的参数和可选的关键字参数定义函数吗?

Common lisp Common lisp:我可以用任意数量的参数和可选的关键字参数定义函数吗?,common-lisp,Common Lisp,我是来自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) 在这里,如果我指定了两个关键字参数以外的任何内容,则会出现未知和关键错误: (

我是来自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)
在这里,如果我指定了两个关键字参数以外的任何内容,则会出现未知和关键错误:

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

在CL中没有标准的方法可以做到这一点。如果不想以不同的方式设计API,可以让函数接受
rest
参数中的所有内容,并自己解析关键字参数

也就是说,这里有几点需要进一步探讨。它们可能在特定用例中有用,但相当有限,并且利用依赖于实现的行为

您可以在lambda列表中使用
&allow other key
,或者在调用
foo
时使用
&allow other key t
,以防止出现未知键错误,但是
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)
    (defun foo (&rest rest &key (a 1) (b 2) &allow-other-keys) ...)
正如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
ing大量参数通常不是一个好主意,因为这可能会导致代码效率低下。更糟糕的是,它也不是很可靠,因为允许的参数数量取决于实现。例如,在我的系统上的CCL下,
调用参数限制为65536。在其他实现和系统下,它可能要小得多,甚至几个数量级。因此,一般来说,宁愿
减少
ing,也不愿
应用大量参数。

基本上,以下是结合关键字处理变量数量的方法:

  • 首先,强制参数和可选参数都绑定到变量
  • 然后,其余提供的参数将绑定到
    &rest
    参数(如果给定);假设参数名为
    rest
    。它是所有剩余参数的列表
  • 现在,如果
    &key
    也是以lambda形式提供的,那么关键字将从
    rest
    中提取出来,预计将有偶数个参数(cf.),其中关键字和值交替,如
    (:k1 v1:k2 v2…
  • 除非在lambda表单定义中附加
    &allow other key
    ,或在调用它时提供
    :allow other key T
    ,否则提供的参数数量必须与预期关键字参数的数量(和名称)完全匹配。您只能提供lambda表单中声明的关键字参数(详见@acelant的评论)
那么,你能做什么

最简单的方法是定义函数,以便所有参数都绑定到关键字,并使用默认值。您还可以允许调用方传递其他参数:

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)
    (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的参数来定义函数

    (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
    ))
如果你想从宏观扩张中获得乐趣,那么就这样做吧,但实际上,我很确定这是不必要的


EDIT我最初修改了
args
参数(以前名为
rest
),而不是进行复制,但这是一个糟糕的示例:

当函数通过&rest接收其参数时,实现允许(但不是必需)将rest参数绑定到与要应用的最后一个参数共享结构的对象。因为函数既无法检测是否通过apply调用它,也无法检测是否(如果是)调用它最后一个要应用的参数是一个常量,一致性程序既不能依赖新使用的rest列表的列表结构,也不能修改该列表结构。

中的