使用&;公共Lisp中的rest参数

使用&;公共Lisp中的rest参数,lisp,common-lisp,Lisp,Common Lisp,在lisp程序(使用Common lisp)中使用&rest参数时遇到一些问题 我的语法知识并不完美,我可能做错了什么 我有两个职能: (defun func-one(&rest params) .....) (defun func-two(param-a param-b &rest params) .....) 它们正在工作,但在某个时刻,在func one内部,我需要调用函数2,如下所示: (func-two value-a value-b params) 我假设我想要

在lisp程序(使用Common lisp)中使用&rest参数时遇到一些问题

我的语法知识并不完美,我可能做错了什么

我有两个职能:

(defun func-one(&rest params) .....)

(defun func-two(param-a param-b &rest params) .....)
它们正在工作,但在某个时刻,在func one内部,我需要调用函数2,如下所示:

(func-two value-a value-b params)
我假设我想要的对人类读者来说是清楚的,但我的语法是错误的,因为我得到了一条错误消息,如:

*** _ =: (1 2 3 4 5) is not a number
由于“&rest params”用于保存一个数字列表,因此我理解该消息。 但是我怎样才能得到我想要的结果呢

(1 2 3 4 5) passed in the "&rest params" should be seen as : 1 2 3 4 5 by func-two
and not as one element which is a list (indeed not a number).

那么,调用func 2的正确方法是什么?在这个答案中,我将从解释问题的原因开始,然后展示两种不同的解决方法

理解问题 首先看两个定义:

(defun func-one (&rest params) ...)
因此,
func one
是一个接受任意数量参数(包括零)的函数。在它的主体内,所有参数将被包装在一个列表中,并将绑定到
params
params
的长度将是提供给函数的参数数量

(defun func-two (param-a param-b &rest params) ...)
func-two
至少接受两个参数,并且可以接受任意数量的参数(直到实现限制:对于
func-one
也是如此)。前两个参数将绑定到
param-a
param-b
,其余所有参数将封装在列表中并绑定到
params

因此,我们可以编写一个小型的
func two
假版本,解释它得到的参数:

(defun func-two (param-1 param-2 &rest params)
  (format t "~&~D arguments~%param-1: ~S~%param-2: ~S~%params:  ~S~%"
          (+ 2 (length params))
          param-1 param-2 params)
  (values))
现在我们可以用不同的方式来称呼它:

> (func-two 1 2)
2 arguments
param-1: 1
param-2: 2
params:  nil

> (func-two 1 2 3)
3 arguments
param-1: 1
param-2: 2
params:  (3)

> (func-two 1 2 3 4)
4 arguments
param-1: 1
param-2: 2
params:  (3 4)

> (func-two 1 2 '(3 4))
3 arguments
param-1: 1
param-2: 2
params:  ((3 4))
问题是:如果你已经把一堆东西打包到一个列表中,那么当你调用
func two
时,这个列表只是函数的一个参数:它们不是你想要的“所有其他参数”

解决此问题有两种方法,将在下面的章节中介绍

第一种方法:使用
apply
第一种也是最简单的方法是使用
apply
,其生活目的就是处理这个问题:如果您想用列表中的一组参数调用一个函数,也许还有一些单独的前导参数,那么可以使用
apply

> (apply #'func-two 1 2 '(3 4))
4 arguments
param-1: 1
param-2: 2
params:  (3 4)

> (apply #'func-two '(1 2 3 4))
4 arguments
param-1: 1
param-2: 2
params:  (3 4)
您可以看到
apply
正在做什么:它的第一个参数是要调用的函数,最后一个参数是“所有其余参数”,中间的任何参数都是一些前导参数

[本答案的其余部分讨论设计,这必然是一个意见问题。]

使用
apply
是一个非常好的技术回答(关于arglist长度和性能有一些问题,但我认为这些是次要的)。然而,通常情况下,必须以这种方式使用
apply
,这是某种设计问题的症状。看看为什么这是你的函数定义对人类读者意味着什么。

(defun frobnicate (a b &rest things-to-process) ...)
这意味着
frobnite
函数的主体中确实有三个参数(调用它绑定了三个变量),其中第三个参数是要处理的事情的列表。但是在用户级别,我希望调用它而不必显式地指定该列表,因此我将第三个参数指定为
&rest
,这意味着函数实际上从调用方的角度接受两个或多个参数

但请考虑此代码片段:

(apply #'frobnicate this that things-to-process)
这对阅读它的人意味着什么?这意味着您确切地知道要调用什么函数,
frobnite
,并且您已经有了
frobnite
实际需要的三个参数:前两个是什么,然后是要处理的事情列表。但是由于
frobnite
的定义方式,您不能只将它们传递给它:相反,您必须使用
apply
将要处理的
内容分散到一组单独的参数中,然后在调用
frobnite
的过程中立即将其读回(新)列表。闻起来很难闻

通常,如果您最终不得不使用
apply
将参数扩展到您编写的函数,并且在编写调用该函数的代码时您知道该函数的名称,那么这通常是设计中某种阻抗不匹配的迹象

第二种方法:改变设计 所以问题的第二个答案是改变设计,这样阻抗不匹配就不会发生。解决阻抗失配的一种方法是在层中定义:

(defun frobnicate (a b &rest things-to-process)
  ;; user convenience function
  (frob a b things-to-process))

(defun frob (a b things-to-process)
  ;; implementation
  ...)

现在,在已经得到要处理的对象列表的实现中,调用
frob
,而不是
frobnite
,并且不再需要使用
apply

,一种简单的方法是调用
(apply#func-two value-A value-b参数)
。请注意,参数列表具有特定于实现的长度限制。它可以低至50(例如:JVM的ABCL)。查看变量
调用参数limit
的值。我需要仔细查看(和实验),但为什么要写“apply实际上是最后的解决方案”。并不是说我特别喜欢它的语法,但至少它使一些简单的东西,我认为这应该不是很难开始。我是否遗漏了什么?我认为最后两段值得商榷。这要视情况而定;我不认为将
apply
描述为“最后手段”是有帮助的。我已经把我认为需要
apply
说明问题的原因这一节分了出来,并明确指出这是一个设计问题,因此是(我)意见的问题。@embe:我已经添加了你建议的编辑的修改版本,有明确的部分:我希望没问题。这是你的答案,所以当然没问题。而且现在更好了。