在Common Lisp中使用&rest参数

Mic*_*hel 3 lisp common-lisp

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

我对语法不完美的了解,我可能做错了.

我有两个功能:

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

(defun func-two(param-a param-b &rest params) .....)
Run Code Online (Sandbox Code Playgroud)

他们正在工作,但在某些时候,在func-one中我需要调用function-two,如下所示:

(func-two value-a value-b params)
Run Code Online (Sandbox Code Playgroud)

我认为我想要的是人类读者清楚,但我的语法错误,因为我得到一个错误消息,如:

*** _ =: (1 2 3 4 5) is not a number
Run Code Online (Sandbox Code Playgroud)

由于"&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).
Run Code Online (Sandbox Code Playgroud)

那么,调用func-two的正确方法是什么?而不是我做了什么.

tfb*_*tfb 7

在这个答案中,我将首先解释问题是什么,然后展示两种不同的方法来解决它.

了解问题

首先看两个定义:

(defun func-one (&rest params) ...)
Run Code Online (Sandbox Code Playgroud)

所以,func-one是一个带有任意数量参数的函数(包括零).在它的主体内部,所有参数都将被包装在一个列表中并且将被绑定到params:长度params将是提供给函数的参数的数量.

(defun func-two (param-a param-b &rest params) ...)
Run Code Online (Sandbox Code Playgroud)

func-two至少需要两个参数,并且尽可能多地参数(直到实现限制:这也是如此func-one).前两个参数将被绑定到param-aparam-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))
Run Code Online (Sandbox Code Playgroud)

现在我们可以通过各种方式调用它:

> (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))
Run Code Online (Sandbox Code Playgroud)

而这里的问题:如果你已经拥有了一堆东西打包成一个列表,然后当你打电话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)
Run Code Online (Sandbox Code Playgroud)

你可以看到apply正在做什么:它的第一个参数是要调用的函数,它的最后一个参数是"所有其余参数",并且它们之间的任何参数都是一些主要参数.

[这个答案的其余部分谈到了设计,这必然是一个意见问题.]

使用apply是一个非常不错的技术答案(关于arglist长度和性能有一些问题,但我认为这些是次要的).然而,经常出现的情况是必须以apply这种方式使用是某种设计问题的症状.要了解为什么这会考虑您的函数定义对人类读者的意义.

(defun frobnicate (a b &rest things-to-process) ...)
Run Code Online (Sandbox Code Playgroud)

这意味着该frobnicate函数在其主体中确实有三个参数(调用它绑定三个变量),其中第三个参数是要处理的事物列表.但是在用户级别我想调用它而不必显式指定该列表,所以我指定第三个参数,&rest这意味着该函数实际上从调用者的角度看两个或多个参数.

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

(apply #'frobnicate this that things-to-process)
Run Code Online (Sandbox Code Playgroud)

这对阅读它的人意味着什么?好吧,这意味着你确切地知道你想要调用什么函数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
  ...)
Run Code Online (Sandbox Code Playgroud)

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