《On Lisp》第四章第三节图4.6中的rmapcar函数中展现的apply陷阱

时间:2023-03-09 13:42:09
《On Lisp》第四章第三节图4.6中的rmapcar函数中展现的apply陷阱
(defun rmapcar (fn &rest args)
(if (some #'atom args)
(apply fn args)
(apply #'mapcar
#'(lambda (&rest args)
(apply #'rmapcar fn args))
args)))

这段代码第一眼看上去,怎么都像无限递归,不断的用&rest对参数做list,然后用mapcar做car,但是这段代码又是确确实实能运行的.仔细分析以后,可以肯定哪个函数的调用对参数多做了一次类似car的拆解.

当看到apply函数时,想起《Ansi Common Lisp》中对apply的描述

apply接受一个函数和任意数量的参数,但是最后一个参数必须是一个列表
Syntax:
apply function &rest args+ => result*

虽然很多函数都有&rest参数,由于&rest会自动把不定的参数组成一个列表,这个函数的说明就略显突兀了,于是查看了参数说明.

args---a spreadable argument list designator.
spreadable argument list designator n. a designator for a list of objects; that is, an object that denotes a list and that is a non-null list L1 of length n, whose last element is a list L2 of length m (denoting a list L3 of length m+n- whose elements are L1i for i < n- followed by L2j for j < m). ``The list (  (  )) is a spreadable argument list designator for the list (    ).''

发现第二个apply和平常的调用不同在于,这次有2个function,1个list.

平常调用apply:

(apply #'(lambda (&rest args) args) '(a b c))

平常调用函数:

 ((lambda (&rest args) args) 'd '(a b c))

&rest超过一个list的调用:

(apply #'(lambda (&rest args) args) 'd '(a b c))

在机器上运行上面3个调用,会发现第三个调用相当于对&rest中的参数做了一次拆解,最后一个列表又刚好是传入参数,曾被&rest包装过一次,在这里利用apply的这个特性,做了拆解,自然没有形成无限递归.

结论:一个函数使用由&rest传递进来的参数调用一个其他函数时,用apply调用那个函数,一次包装一次拆解刚好抵消.