別のライブラリで定義された関数を変更する必要がある場合や、foo-functionのようなフック、プロセスフィルター、あるいは基本的に関数値を保持するあらゆる変数やオブジェクトフィールドを変更する必要がある場合、名前付き関数にはfsetやdefun、フック変数にはsetq、プロセスフィルターにはset-process-filterといった適切なセッター関数を使用できますが、これらは往々にして大雑把すぎて、直前の値を完全に破棄してしまいます。
advice機能を使うと、関数にアドバイスを与えることで、関数の既存の定義に内容を追加できます。これは、関数全体を再定義するよりもすっきりとした手法です。
Emacsのアドバイスシステムは、そのためのプリミティブを2組提供しています。一つは変数やオブジェクトフィールドに保持される関数値用のコアセット(対応するプリミティブは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
は、2つの関数をどのように合成するかを指定しています。というのも、その方法にはさまざまなやり方があるからです。追加された関数は、アドバイスの一部とも呼ばれます。