13.12 Советы для функций Emacs Lisp

Когда вам нужно изменить функцию, определённую в другой библиотеке, или когда вам нужно изменить хук, например foo-function, фильтр процесса или, в общем, любую переменную или поле объекта, которое хранит значение функции, вы можете использовать соответствующую функцию установки, такую как fset или defun для именованных функций, setq для переменных-хуков или set-process-filter для фильтров процессов, но они часто слишком грубы, полностью отбрасывая предыдущее значение.

Возможность советов позволяет вам добавлять что-то к существующему определению функции, давая совет функции. Это более чистый метод, чем полное переопределение всей функции.

Система советов Emacs предоставляет два набора примитивов для этого: основной набор — для значений функций, хранящихся в переменных и полях объектов (с соответствующими примитивами 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, использованные в приведённых выше примерах, указывают, как компонуются две функции, поскольку существует много различных способов это сделать. Добавленная функция также называется советом.