我在使用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的正确方法是什么?而不是我做了什么.
在这个答案中,我将首先解释问题是什么,然后展示两种不同的方法来解决它.
首先看两个定义:
(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-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))
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.