13.12 为 Emacs Lisp 函数添加建议

当你需要修改其他库中定义的函数,或需要修改类似 foo-function 这样的钩子、进程过滤器(process filter),抑或是任何存储函数值的变量 / 对象字段时,你可以使用对应的设置函数:例如,对命名函数使用 fsetdefun,对钩子变量使用 setq,对进程过滤器使用 set-process-filter——但这些方式通常过于「粗暴」,会直接丢弃原有值。

建议(advice) 特性允许你通过 为函数添加建议(advising the function) 的方式,在函数现有定义的基础上进行扩展。相比重新定义整个函数,这是一种更优雅的方法。

Emacs 的建议(advice)系统为此提供了两套原语:一套核心原语,用于处理存储在变量和对象字段中的函数值(对应的核心原语为 add-functionremove-function);另一套构建在核心原语之上的扩展原语,专门用于命名函数(主要原语为 advice-addadvice-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-functionmy-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)


emacs

Emacs

org-mode

Orgmode

Donations

打赏

Copyright

© 2025 Jasper Hsu

Creative Commons

Creative Commons

Attribute

Attribute

Noncommercial

Noncommercial

Share Alike

Share Alike