LISP - 使用 `apply` 调用函数
Lisp - 使用 apply 和 funcall 动态调用函数
Section titled “Lisp - 使用 apply 和 funcall 动态调用函数”Lisp 以其同像性(homoiconicity,即代码即数据)而闻名,这使得强大的元编程(metaprogramming)成为可能。其中一个关键部分是动态调用函数的能力。apply 和 funcall 函数是完成这项任务的主要工具。它们允许你调用在运行时(runtime)确定的函数。
funcall 与 apply:核心区别
Section titled “funcall 与 apply:核心区别”funcall 和 apply 都用于调用函数,但它们接受参数的方式有所不同:
funcall:接受一个函数对象(function object),然后是它的各个参数,一个接一个地传入。apply:接受一个函数对象、任意数量的初始参数,以及一个包含其余参数的列表(list)。
;; 目标是计算 (+ 10 20 30)
;; 使用 FUNCALL:参数是单独传递的。(funcall #'+ 10 20 30);=> 60
;; 使用 APPLY:参数是在一个列表中传递的。(apply #'+ '(10 20 30));=> 60
;; APPLY 也可以在列表之前接受初始参数。(apply #'+ 10 '(20 30));=> 60
;; 常见的错误是给 funcall 传递一个列表。; (funcall #'+ '(10 20 30)) ; 这会是一个错误!核心要点:当你的参数已经被收集到一个列表(list)中时,使用 apply。
实际用例:一个动态计算器
Section titled “实际用例:一个动态计算器”想象一下,你需要一个函数,它接受一个操作(如 + 或 *)和一个数字列表,并返回结果。这正是 apply 的完美应用场景,因为操作的参数已经在一个列表中了。
main.lisp
Section titled “main.lisp”(defun calculate (operation numbers) "Applies a given operation to a list of numbers." (unless (listp numbers) (error "'numbers' must be a list.")) (apply operation numbers))
;; --- 使用 calculate 函数 ---
;; 调用加法函数的符号(print (calculate '+ '(10 20 30)))
;; 调用一个 lambda 函数(print (calculate (lambda (x y) (- x y)) '(100 25)))
;; 调用一个预定义函数(适用于任意元数 arity)(print (calculate #'max '(1 8 5 9 2)))当你执行这段代码时,它会返回以下结果:
60759高级用例:参数转发
Section titled “高级用例:参数转发”apply 对于编写需要将参数转发给另一个函数的包装器(wrapper)函数或宏(macro)至关重要。这通常与函数定义中的 &rest 参数结合使用。
(defun logging-wrapper (function-to-call &rest args) "A wrapper that logs before and after calling a function with its arguments." ;; 一个包装器,在调用函数及其参数之前和之后进行日志记录。 (format t "--> Calling ~s with arguments ~s~%" function-to-call args)
;; 'args' 是一个列表,所以我们必须使用 APPLY 来调用函数。 (let ((result (apply function-to-call args))) (format t "<-- Function ~s returned ~s~%" function-to-call result) result))
;; 使用该包装器(logging-wrapper #'+ 1 2 3)--> Calling #<FUNCTION +> with arguments (1 2 3)<-- Function #<FUNCTION +> returned 6关键注意事项
Section titled “关键注意事项”#'符号:使用#'(sharp-quote,尖引号)是获取函数对象(例如#'+)的现代、首选方式。它比使用像'+这样的裸符号(bare symbol)更明确。- 动态派发(Dynamic Dispatch):
apply和funcall是 Lisp 中动态派发的核心。它们允许你编写灵活的、泛型(generic)的代码,根据运行时条件决定调用哪个函数。 - 错误处理:请注意,如果使用错误数量或类型的参数调用
apply或funcall来执行目标函数,将导致运行时错误,就像直接调用一样。