13.5 调用函数

定义函数只是完成了一半工作。函数在被你 调用(call)(即指令其运行)之前不会执行任何操作。调用函数也被称为 启用(invocation)

调用函数最常见的方式是对列表进行求值。例如,对列表 (concat "a" "b") 求值时,会调用函数 concat,并传入参数 "a""b"。有关求值的详细说明,see Evaluation

当你在程序中将列表编写为表达式时,会在程序文本中明确指定要调用的函数,以及要传入的参数数量。通常情况下,这正是你所需要的。但偶尔你需要在运行时计算确定要调用的函数 —— 此时可使用函数 funcall。如果还需要在运行时确定要传递的参数数量,则使用 apply

Function: funcall function &rest arguments

funcall 会调用 function 并传入 arguments 参数,最终返回 function 的执行结果。

由于 funcall 本身是一个函数,其所有参数(包括 function)都会在 funcall 被调用之前完成求值。这意味着你可以通过任意表达式来获取待调用的函数;同时也意味着 funcall 不会直接处理你为 arguments 编写的表达式,只会处理这些表达式的值。在调用 function 的过程中,这些值不会被二次求值 ——funcall 的执行逻辑与普通函数调用的流程一致,区别仅在于它的参数已提前完成求值。

参数 function 必须是一个 Lisp 函数或原语函数(primitive function)。特殊形式(special forms)和宏(macros)不被允许作为该参数 —— 因为它们只有在接收未求值的参数表达式时才有意义。而 funcall 无法满足这一要求,正如我们上文所述,它从一开始就无法获取这些未求值的表达式。

如果你需要使用 funcall 调用一个命令,并使其表现得如同以交互方式被调用,可使用 funcall-interactively(see Interactive Call)。

(setq f 'list)
     ⇒ list
(funcall f 'x 'y 'z)
     ⇒ (x y z)
(funcall f 'x 'y '(z))
     ⇒ (x y (z))
(funcall 'and t nil)
error→ Invalid function: #<subr and>

将这些示例与 apply 的示例进行对比。

Function: apply function &rest arguments

apply 调用 function 并传入 arguments 参数,其行为与 funcall 基本一致,仅存在一处差异:arguments 中的最后一项需为一个对象列表,该列表内的元素会作为独立参数传递给 function,而非将整个列表作为单个参数传入。我们称 apply展开(spreads) 这个列表,使列表中的每个独立元素都成为一个参数。

仅传入单个参数的 apply 属于特殊情况:该参数必须是非空列表,列表的第一个元素会被当作函数调用,剩余元素则作为独立参数传入该函数。传入两个或更多参数时,apply 的执行效率会更高。

apply 返回调用 function 后的结果。与 funcall 相同,function 必须是 Lisp 函数或原语函数(primitive function);特殊形式(special forms)和宏(macros)在 apply 中无法正常生效。

(setq f 'list)
     ⇒ list
(apply f 'x 'y 'z)
error→ Wrong type argument: listp, z
(apply '+ 1 2 '(3 4))
     ⇒ 10
(apply '+ '(1 2 3 4))
     ⇒ 10

(apply 'append '((a b c) nil (x y z) nil))
     ⇒ (a b c x y z)

(apply '(+ 3 4))
     ⇒ 7

关于使用 apply 的一个实用示例,可参见 Definition of mapcar

有时,将函数的部分参数固定为特定值,仅保留剩余参数待函数实际调用时传入,会非常实用。这种固定函数部分参数的操作被称为函数的 部分应用(partial application)14。 该操作的结果是生成一个新函数 —— 它接收剩余的参数,并将所有参数(固定参数 + 新传入参数)合并后调用原函数。

以下是在 Emacs Lisp 中实现函数部分应用的方法:

Function: apply-partially func &rest args

该函数会返回一个新函数;当这个新函数被调用时,它会将 args 中的参数与调用新函数时传入的额外参数合并为参数列表,再调用 func。若 func 接收 n 个参数,那么以 m <= n 个参数调用 apply-partially 时,会生成一个接收 n - m 个参数的新函数 15

假设内置函数 1+ 不存在,我们可以通过 apply-partially 和另一个内置函数 + 来定义它,示例如下 16

(defalias '1+ (apply-partially '+ 1)
  "Increment argument by one.")
(1+ 10)
     ⇒ 11

Lisp 函数常接收其他函数作为参数,或从数据结构(尤其钩子变量(hook variables)和属性列表(property lists))中查找函数,并通过 funcallapply 调用它们。这类接收函数作为参数的函数通常被称为 函数式函数(functionals)

有时调用函数式函数时,传入一个空操作(no-op)函数作为参数会很实用。以下是三种不同类型的空操作函数:

Function: identity argument

该函数返回 argument,且无任何副作用(side effects)。

Function: ignore &rest arguments

该函数忽略所有 arguments,并返回 nil

Function: always &rest arguments

该函数忽略所有 arguments,并返回 t

部分函数是用户可见的 命令(commands),可通过交互方式调用(通常通过按键序列)。使用 call-interactively 函数,能够完全模拟交互式调用的方式来执行这类命令。See Interactive Call


Footnotes

(14)

该概念与 柯里化(currying) 相关但并不等同:柯里化会对接收多个参数的函数进行转换,使其可作为一系列函数的链式调用执行,每个函数仅接收单个参数。

(15)

func 可接收的参数数量无上限,那么这个新函数也会接收无数量上限的参数;这种情况下,apply-partially 不会减少新函数可接收的参数数量。

(16)

注意:与内置的 1+ 函数不同,这个自定义版本可以接收任意数量的参数。


emacs

Emacs

org-mode

Orgmode

Donations

打赏

Copyright

© 2025 Jasper Hsu

Creative Commons

Creative Commons

Attribute

Attribute

Noncommercial

Noncommercial

Share Alike

Share Alike