(apply #'somefunc args) 和 (somefunc args) 之间的区别

Difference between (apply #'somefunc args) and (somefunc args)

在阅读 Paul Graham 的 On Lisp 时,我在 第 4 章,效用函数 中发现了以下 function

(defun symb (&rest args)
  (values (intern (apply #'mkstr args)))) ;; mkstr function is "applied"
;; which evaluates like in the following example:
> (symb nil T :a)
NILTA

我想了解下函数有什么区别,略有不同:

(defun symb1 (&rest args)
  (values (intern (mkstr args)))) ;; directly calling mkstr
;; which evaluates like in the following example:
> (symb1 nil T :a)
|(NIL T A)|

在第二个版本中,mkstr直接用args参数计算,但我不明白为什么我们需要在原来的(apply #'mkstr ...)中做。

当您调用 (f args) 时,您调用 f 时带有一个参数。

使用 (apply #'f args),您可以使用 args 列表包含的参数调用 f。所以如果args(1 2),那么(apply #'f args)就等价于(f 1 2).

APPLY

我们来看看mkstr的定义:

CL-USER> (defun mkstr (&rest args)
           (with-output-to-string (s)
             (dolist (a args) (princ a s))))
MKSTR

它是一个函数,它接受可变数量的任何类型的参数,将它们打包在一个列表中,并将该列表分配给形式参数 args(由于 &rest 规范参数)。然后,该函数使用 printc 打印此列表的所有元素,生成一个字符串,该字符串是 连接 其所有打印表示形式(中间没有空格)的结果。因此,例如:

CL-USER> (mkstr '(a b c))
"(A B C)"
CL-USER> (mkstr 3 'A '(A b 4))
"3A(A B 4)"

类似地,函数 symbsymb1 接受可变数量的参数,并且 args 将包含由它们形成的列表。因此,symb1 使用 单个 参数调用 mkstr,传递给 symb1 的参数列表,以便 mkstr 创建一个列表中的唯一字符串,最后保留列表以将其转换为原子。相反,在 symb 中,函数 mkstr 应用于从列表中提取 的所有参数 ,因为使用了 apply(请参阅 specification), 这样列表的所有元素都被连接在一起,然后转化为一个原子..

APPLY 的目的是调用带有计算参数列表的函数。

假设用户输入了一些参数,我们想要调用函数 WRITEWRITE 需要很多可能的参数。第一个参数是要打印的对象,其余是关键字值选项:

WRITE 的可能关键字参数:

array base case circle escape gensym
length level lines miser-width pprint-dispatch
pretty radix readably right-margin stream

让我们使用 READ 将参数列表作为列表读取,并使用参数列表通过 APPLY 调用 WRITE

CL-USER 30 > (loop for input = (read)
                   while input
                   do
                   (format t "~%# ")
                   (apply #'write input)
                   (format t "~%~%"))
((1 5 10 30 55 26 12 17))
# (1 5 10 30 55 26 12 17)

((1 5 10 30 55 26 12 17) :base 16)
# (1 5 A 1E 37 1A C 11)

((1 5 10 30 55 26 12 17) :base 12)
# (1 5 A 26 47 22 10 15)

((1 5 10 30 55 26 12 17) :length 5)
# (1 5 10 30 55 ...)

((1 5 10 30 55 26 12 17) :base 16 :length 5)
# (1 5 A 1E 37 ...)

实现类似效果的另一种方法是使用 EVAL。

CL-USER 35 > (let ((f #'+)
                   (args '(20 22)))
               (eql (eval (list* 'funcall f args))
                    (apply f args)))
T