当你需要修改其他库中定义的函数,或需要修改类似 foo-function 这样的钩子、进程过滤器(process filter),抑或是任何存储函数值的变量 / 对象字段时,你可以使用对应的设置函数:例如,对命名函数使用 fset 或 defun,对钩子变量使用 setq,对进程过滤器使用 set-process-filter——但这些方式通常过于「粗暴」,会直接丢弃原有值。
建议(advice) 特性允许你通过 为函数添加建议(advising the function) 的方式,在函数现有定义的基础上进行扩展。相比重新定义整个函数,这是一种更优雅的方法。
Emacs 的建议(advice)系统为此提供了两套原语:一套核心原语,用于处理存储在变量和对象字段中的函数值(对应的核心原语为 add-function 和 remove-function);另一套构建在核心原语之上的扩展原语,专门用于命名函数(主要原语为 advice-add 和 advice-remove)。
举一个简单的例子,以下代码展示了如何添加一段建议,让某个函数每次被调用时,其返回值都会被修改:
(defun my-double (x) (* x 2)) (defun my-increase (x) (+ x 1)) (advice-add 'my-double :filter-return #'my-increase)
添加这段建议后,若你调用 my-double 并传入参数 ‘3’,函数的返回值将变为 ‘7’。要移除这段建议,只需执行:
(advice-remove 'my-double #'my-increase)
一个更进阶的示例是追踪进程 proc 的进程过滤器调用情况:
(defun my-tracing-function (proc string) (message "Proc %S received %S" proc string)) (add-function :before (process-filter proc) #'my-tracing-function)
这会让该进程的输出在传递给原始进程过滤器之前,先传递给 my-tracing-function。my-tracing-function 会接收与原始函数完全相同的参数。当你不再需要追踪时,可通过以下代码恢复到无追踪的状态:
(remove-function (process-filter proc) #'my-tracing-function)
类似地,如果你想追踪名为 display-buffer 的函数的执行过程,可以使用:
(defun his-tracing-function (orig-fun &rest args)
(message "display-buffer called with args %S" args)
(let ((res (apply orig-fun args)))
(message "display-buffer returned %S" res)
res))
(advice-add 'display-buffer :around #'his-tracing-function)
此时,his-tracing-function 会替代原始函数被调用;该函数除了接收原始函数的参数外,还会将原始函数本身作为参数接收 ——因此它可以在需要时调用原始函数。当你不想再看到这些输出信息时,可通过以下代码恢复到无追踪的状态:
(advice-remove 'display-buffer #'his-tracing-function)
上述示例中使用的参数 :before 和 :around,用于指定两个函数的组合方式(因为函数组合的实现方式有很多种)。这个被添加的函数也被称为一段 建议(advice)。