在Common Lisp中使用&rest参数

时间:2019-02-20 09:31:39

标签: lisp common-lisp

我在lisp程序(带有Common Lisp)中使用&rest参数时遇到了一些麻烦。

我对语法的了解并不完美,我可能做错了事。

我有两个功能:

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

(defun func-two(param-a param-b &rest params) .....)

它们正在工作,但是在某个时候,在func-one中,我需要调用function-two,就像这样:

(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-two的正确方法是什么?而不是我所做的。

1 个答案:

答案 0 :(得分:7)

在这个答案中,我将首先说明问题所在,然后展示解决问题的两种不同方法。

了解问题

首先查看两个定义:

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

因此,func-one是一个接受任意数量参数(包括零)的函数。在其主体内部,所有参数都将包装在一个列表中并绑定到paramsparams的长度将是提供给该函数的参数的数量。

(defun func-two (param-a param-b &rest params) ...)

func-two至少接受两个个参数,并且取任意数量(达到实现限制:func-one也是如此)。前两个参数将绑定到param-aparam-b,并且所有其余参数将包装在列表中并绑定到params。 / p>

所以我们可以写一个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在做什么:它的第一个参数是调用的函数,它的 last 参数是“所有其余参数”以及所有参数之间是一些主要论点。

[此答案的其余部分讨论设计,这必然是一种见解。]

使用apply是一个非常不错的技术答案(关于arglist的长度和性能存在一些问题,但我认为这是次要的)。但是,在很多情况下,必须以这种方式使用apply是某种设计问题的征兆。要了解为什么这样做,请考虑您的函数定义对人类读者意味着什么。

(defun frobnicate (a b &rest things-to-process) ...)

这意味着frobnicate函数的主体中确实有三个参数(称它绑定了三个变量),其中第三个参数是要处理的事物的列表。但是在 user 级别,我想调用它而不必显式指定该列表,因此我将第三个参数指定为&rest,这意味着该函数实际上从调用方的参数中接受两个或多个参数。视角。

但是请考虑以下代码片段:

(apply #'frobnicate this that things-to-process)

这对阅读它的人意味着什么?好吧,这意味着您确切地知道要调用的函数frobnicate,并且已经 frobnicate实际想要的三个参数:无论前两个是什么,然后是要处理的事物列表。但是由于frobnicate的定义方式,您不能仅将它们传递给它:相反,您必须使用things-to-process apply散布到一组单独的参数中,然后立即在调用frobnicate的过程中,将其取消散布到(新的)列表中。难闻。

通常,如果最终不得不使用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而不是frobnicate,并且不再需要使用apply