[ << ] [ >> ]           [Top] [Contents] [Index] [ ? ]

16. Emacs Lisp関数のアドバイス

アドバイス(advice)機能により、関数の既存の定義に追加できます。 これは、Emacsの他の部分で定義された関数を ライブラリにおいてカスタマイズする見通しのよい方法です。 関数全体を再定義するよりも見通しがよいのです。

各関数は、個別に定義した複数のアドバイス断片を持てます。 それぞれのアドバイス断片は、明示的に有効にしたり無効にできます。 任意の関数の有効にしたアドバイス断片が実際にその効果を発揮するのは、 当該関数のアドバイスを活性にしたときか 当該関数をのちに定義したり再定義したときです。

使用上の注意: アドバイスは、既存関数の既存の呼び出しのふるまいを変更するのに有用である。 新たな呼び出しやキーバインドの新たなふるまいが必要な場合には、 既存関数を使う新たな関数(や新たなコマンド)を定義するほうが 見通しがよい。

16.1 単純なアドバイスの例  A simple example to explain the basics of advice.
16.2 アドバイス定義  Detailed description of defadvice.
16.3 包囲アドバイス  Wrapping advice around a function's definition.
16.4 計算アドバイス  ...is to defadvice as fset is to defun.
16.5 アドバイスの活性化  Advice doesn't do anything until you activate it.
16.6 アドバイスの有効化と無効化  You can enable or disable each piece of advice.
16.7 予約活性  Preactivation is a way of speeding up the loading of compiled advice.
16.8 アドバイスからの引数の参照  How advice can access the function's arguments.
16.9 subr引数リストの定義  Accessing arguments when advising a primitive.
16.10 結合定義  How advice is implemented.


16.1 単純なアドバイスの例

コマンドnext-lineは、ポイントを垂直に複数行移動します。 標準バインドはC-nです。 バッファの最終行で使うと、 (next-line-add-newlinesnil以外の場合) このコマンドは行を作るために改行を挿入し、その行に移動します。

同様な機能をprevious-lineに追加したいとします。 つまり、バッファの先頭に新たな行を挿入し、その行へ移動するのです。 どのようにすればよいでしょう?

当該関数を再定義すればできますが、それではモジュール性がよくありません。 アドバイス機能が見通しのよい代替方法を提供します。 既存の関数定義を実際に変更したりその定義を参照することなく、 関数定義に読者のコードを実質的に追加できます。 つぎのように行います。

 
(defadvice previous-line (before next-line-at-end (arg))
  "Insert an empty line when moving up from the top line."
  (if (and next-line-add-newlines (= arg 1)
           (save-excursion (beginning-of-line) (bobp)))
      (progn
        (beginning-of-line)
        (newline))))

この式は、関数previous-lineに対するアドバイス断片を定義します。 このアドバイス断片にはnext-line-at-endという名前が付きます。 シンボルbeforeにより、 previous-lineの通常の定義を実行するまえに実行する 事前アドバイス(before-advice)であることを意味します。 (arg)は、アドバイス断片がどのように関数の引数を参照するかを指定します。

このアドバイス断片が実行されると、必要な場面では新たに行を作りますが、 その行へはポイントを移動しません。 これはアドバイスを書く正しいやりかたです。 というのは、通常の定義がこのあとに実行され、新たに挿入した行へ移動します。

アドバイスを定義しても関数previous-lineをただちには変更しません。 つぎのようにアドバイスを活性にすると変わります。

 
(ad-activate 'previous-line)

これにより、関数previous-lineに対して定義してある アドバイスを使い始めます。 これ以降、C-pM-xでユーザーが起動したのか Lispから呼ばれたのかに関わらず、 この関数を起動すると、まずアドバイスを実行してから 関数の通常の定義を実行します。

この例は、アドバイスの1つのクラスである事前アドバイスの例であり、 関数の元定義のまえに実行されます。 他に2つのアドバイスクラスがあります。 元定義のあとに実行される事後アドバイス(after-advice)と 元定義の起動を包み込む式を指定する包囲アドバイス(around-advice)です。


16.2 アドバイス定義

アドバイス断片を定義するには、マクロdefadviceを使います。 defadviceの呼び出しはつぎのような構文です。 defundefmacroの構文を基にしていますが、 追加部分があります。

 
(defadvice function (class name
                         [position] [arglist]
                         flags...)
  [documentation-string]
  [interactive-form]
  body-forms...)

ここで、functionはアドバイス対象となる関数 (やマクロやスペシャルフォーム)です。 以後、アドバイスする対象を単に『関数』と書きますが、 これにはつねにマクロやスペシャルフォームを含みます。

classはアドバイスのクラスを指定し、 beforeafteraroundのいずれかです。 事前アドバイス(before)は関数そのもののまえに実行されます。 事後アドバイス(after)は関数そのもののあとに実行されます。 包囲アドバイス(around)は関数自身の実行を包み込みます。 事後アドバイスと包囲アドバイスでは、 ad-return-valueに設定することで戻り値を変更できます。

Variable: ad-return-value
アドバイスを実行しているとき、 関数の元定義の実行を完了したあとでは、この変数はその戻り値を保持する。 すべてのアドバイスを完了すると、最終的には、この値を呼び出し側へ返す。 事後アドバイスと包囲アドバイスでは、この変数に別の値を設定することで 戻り値を変更できる。

引数nameはアドバイスの名前であり、nil以外のシンボルです。 アドバイス名は、functionの特定クラスのすべてのアドバイス断片から 1つのアドバイス断片を一意に識別します。 名前でアドバイス断片を参照でき、 それを再定義したり有効にしたり無効にできます。

通常の関数定義の引数リストのかわりに、 アドバイス定義では異なる情報を必要とします。

省略可能なpositionは、指定したclassの 現在のアドバイスリストのどこに新たなアドバイスを置くかを指定します。 firstlast、あるいは、 0から数え始める位置を指定する数である必要があります (firstは0と等価)。 位置を指定しないとデフォルトはfirstです。 当該クラスの既存位置の範囲を超えている場合には、 先頭か末尾のどちらか近いほうになります。 既存のアドバイス断片を再定義する場合には、値positionは無視されます。

省略可能なarglistは、 アドバイスが使う引数リストを定義するために使います。 これは、アドバイスを実行するために生成される結合定義 (see section 16.10 結合定義)の引数リストになります。 その結果、アドバイスの式では、 引数の値を参照するためにこのリストの引数変数を使えます。

この引数リストは、関数の実際の呼び出し方を扱えるように、 もとの関数の引数リストと互換性がある必要があります。 2つ以上のアドバイス断片で引数リストを指定している場合、 すべてのアドバイスクラスの中で最初のもの(位置が最小のもの)を使います。

残りの要素flagsは、このアドバイス断片の使い方に関する情報を指定する シンボルです。 正しいシンボルとそれらの意味はつぎのとおりです。

activate
functionに対するアドバイスをただちに活性にする。 関数のアドバイスに対する変更は、当該関数のアドバイスを活性にすると 効果を持つようになる。 このフラグは、functionに対するこのアドバイス断片を定義した直後に そのようにすることを指示する。

functionが未定義(未定義のアドバイス(forward advice)と呼ぶ状況) であるとこのフラグにはなんの効果もない。 というのは、未定義関数のアドバイスは活性にできないからである。 しかし、functionを定義するとそのアドバイスは自動的に活性にされる。

protect
このアドバイス断片をそれよりまえに実行されるコードやアドバイスによる 非ローカル脱出やエラーに対して保護する。 保護したアドバイス断片は、 フォームunwind-protectの中に後始末として置かれ、 それよりまえに実行されるコードでエラーが発生したりthrowを使っても 実行される。 see section 9.5.4 非ローカル脱出時の後始末

compile
アドバイスの実行に使われる結合定義をコンパイルする。 activateとともに指定しないと、このフラグは無視する。 see section 16.10 結合定義

disable
このアドバイス断片を当初は無効にしておき、 のちに明示的に有効にしない限り使われない。 see section 16.6 アドバイスの有効化と無効化

preactivate
このdefadviceをコンパイルしたりマクロ展開したときに、 functionに対するアドバイスを活性にする。 これにより現在のアドバイスの状態に応じたアドバイス定義をコンパイルし、 必要に応じて使われるようになる。

このdefadviceをバイトコンパイルする場合にのみ意味を持つ。

省略可能なdocumentation-stringは、 このアドバイス断片の説明文字列になります。 functionに対するアドバイスが活性であると、 (documentationが返す)functionの説明文は、 関数の元定義の説明文字列とfunctionのアドバイスすべての説明文字列の 合成になります。

省略可能なinteractive-formは、 元関数の対話的ふるまいを変更するために指定します。 2つ以上のアドバイス断片でinteractive-formを指定している場合、 すべてのアドバイスの中で最初のもの(位置が最小のもの)が優先します。

空リストでもかまわないbody-formsは、アドバイスの本体です。 アドバイスの本体では、引数、戻り値、束縛環境を参照/変更したり、 いかなる種類の副作用を起こせます。

警告: マクロをアドバイスする場合、 マクロはプログラムのコンパイル時に展開されるのであって、 コンパイルしたプログラムの実行時に展開されるのではないことに注意。 アドバイスが使用するすべてのサブルーティンは、 バイトコンパイラがマクロを展開するときに必要になる。


16.3 包囲アドバイス

包囲アドバイスにより、関数の元定義を包み込むLisp式を書けます。 関数の元定義を実行する場所を特別なシンボルad-do-itで指定します。 包囲アドバイスの本体に現れたこのシンボルは、 元定義(と内側の包囲アドバイス本体)のフォームを含んだprognで 置き換えられます。 例を示しましょう。

 
(defadvice foo (around foo-around)
  "Ignore case in `foo'."
  (let ((case-fold-search t))
    ad-do-it))

これは、fooの元定義を実行するときに 大文字小文字を区別しないで探索することを保証します。

Variable: ad-do-it
これは実際には変数ではないが、包囲アドバイス内では変数のように用いる。 関数の元定義と『より内側の』包囲アドバイスを実行する場所を指定する。

包囲アドバイスでad-do-itを用いなければ、関数の元定義を実行しません。 これは、元定義を完全に無効にする手段です。 (さらに、内側の包囲アドバイス断片も無効にする。)


16.4 計算アドバイス

マクロdefadvicedefunに似ていて、 アドバイスのコードやアドバイスに関する他のすべての情報を ソースコードで明示します。 関数ad-add-adviceを用いると、 その詳細を計算で求めたアドバイスを作成できます。

Function: ad-add-advice function advice class position
ad-add-adviceを呼び出すと、 関数functionに対するクラスclassのアドバイス断片として adviceを追加する。 引数adviceはつぎの形式である。

 
(name protected enabled definition)

ここで、protectedenabledはフラグであり、 definitionはアドバイスの動作を指定する式である。 enablednilであると、 このアドバイス断片は当初は無効になる (see section 16.6 アドバイスの有効化と無効化)。

functionに指定したクラスclassのアドバイス断片がすでにあると、 positionは新しいアドバイス断片をリストのどこに置くかを指定する。 positionの値は、firstlast、あるいは、 (リストの先頭を0から数えた)数である。 範囲外の数は先頭か末尾のどちらか近いほうになる。

functionに同じ名前のアドバイス断片adviceがすでにあると、 引数positionは無視され、古いアドバイス断片を新しいもので置き換える。


16.5 アドバイスの活性化

デフォルトでは、アドバイスを定義してもその効果は発揮されません。 アドバイスした関数のアドバイスを活性にして始めて効果を発揮します。 defadviceでフラグactivateを指定すれば、 関数にアドバイスを定義したときに活性にできます。 しかし、普通は、関数ad-activateや以下の活性化コマンドを 呼び出すことで、関数のアドバイスを活性にします。

アドバイスの定義操作と活性化操作を区別することで、 アドバイスを追加するたびに関数を再定義することなる、 関数に複数のアドバイス断片を効率よく追加できます。 さらに重要なことは、関数を実際に定義するまえでも 関数にアドバイスを定義できることです。

関数のアドバイスを初めて活性にすると、 関数の元定義を保存してから、関数に対する有効なアドバイス断片すべてを 元定義と結合して新たな定義を作り出します。 (現在無効にしてあるアドバイス断片は使用しない。 see section 16.6 アドバイスの有効化と無効化。) この定義をインストールし、 以下に述べる条件に応じてバイトコンパイルする場合もあります。

アドバイスを活性にするコマンドすべてにおいて、 compiletであると、 アドバイスを実装する結合定義をコンパイルします。

コマンド: ad-activate function &optional compile
このコマンドはfunctionに対するアドバイスを活性にする。

関数のアドバイスがすでに活性になっているアドバイスを 活性にしても意味があります。 当該関数のアドバイスを活性にしたあとでアドバイスを変更した場合、 その変更が効果を持つようにする操作になります。

コマンド: ad-deactivate function
このコマンドはfunctionのアドバイスを不活性にする。

コマンド: ad-activate-all &optional compile
このコマンドはすべての関数に対するアドバイスを活性にする。

コマンド: ad-deactivate-all
このコマンドはすべての関数に対するアドバイスを不活性にする。

コマンド: ad-activate-regexp regexp &optional compile
このコマンドはregexpに一致する名前のすべてのアドバイス断片を活性にする。 より正確には、regexpに一致する名前のアドバイス断片を持つ任意の 関数のすべてのアドバイスを活性にする。

コマンド: ad-deactivate-regexp regexp
このコマンドはregexpに一致する名前の すべてのアドバイス断片を不活性にする。 より正確には、regexpに一致する名前のアドバイス断片を持つ任意の 関数のすべてのアドバイスを不活性にする。

コマンド: ad-update-regexp regexp &optional compile
このコマンドはregexpに一致する名前のアドバイス断片を活性にするが、 すでにアドバイスが活性になっている関数に対するものだけである。

関数に対するアドバイスの再活性化は、 アドバイスを活性にしたあとに行った当該アドバイスの変更すべて (有効にしたり無効にしたアドバイス断片を含む。 see section 16.6 アドバイスの有効化と無効化)が効果を持つようにするのに便利である。

コマンド: ad-start-advice
関数を定義したり再定義したときにアドバイスを自動的に活性にする。 このモードをオンにすると、アドバイスを定義するとただちに効果を持つようになる。

コマンド: ad-stop-advice
関数を定義したり再定義してもアドバイスを自動的には活性にしない。

User Option: ad-default-compilation-action
この変数は、関数に対するアドバイスを活性にした結果作られる 結合定義をコンパイルするかどうか制御する。

『予約活性』(see section 16.7 予約活性)中にアドバイス定義を作成すると その定義はすでにコンパイルされているはずです。 というのは、preactivateフラグを指定したdefadviceを 含むファイルをバイトコンパイル中にそれが定義されたはずだからです。


16.6 アドバイスの有効化と無効化

各アドバイス断片には、 それを有効にするか無効にするかを指定するフラグがあります。 アドバイス断片を有効にしたり無効にすることで、 アドバイス断片を未定義にしたり再定義することなくオン/オフできます。 たとえば、関数fooに対するアドバイス断片my-adviceを 無効にするには、つぎのようにします。

 
(ad-disable-advice 'foo 'before 'my-advice)

この関数自身は、アドバイス断片の有効化フラグを変更するだけです。 アドバイスした関数でこの変更の効果を発揮するには、 fooのアドバイスを再度活性にする必要があります。

 
(ad-activate 'foo)

コマンド: ad-disable-advice function class name
このコマンドはfunctionに対するクラスclass内の nameで指名したアドバイス断片を無効にする。

コマンド: ad-enable-advice function class name
このコマンドはfunctionに対するクラスclass内の nameで指名したアドバイス断片を有効にする。

正規表現を用いて、さまざまな関数に対する 多数のアドバイス断片を一度に無効にすることもできます。 この場合も、当該関数のアドバイスを再度活性にすることで、 その効果が発揮されます。

コマンド: ad-disable-regexp regexp
このコマンドは、すべての関数のすべてのクラスの regexpに一致するアドバイス断片すべてを無効にする。

コマンド: ad-enable-regexp regexp
このコマンドは、すべての関数のすべてのクラスの regexpに一致するアドバイス断片すべてを有効にする。


16.7 予約活性

アドバイスを実行するための結合定義を作成することは、 ある程度手間がかかります。 ライブラリで多数の関数をアドバイスしていると、 ライブラリのロードが遅くなります。 そのような場合、あらかじめ適切な結合定義を作成する 予約活性(preactivation)を使えます。

予約活性を使うには、defadviceでアドバイスを定義するときに フラグpreactivateを指定します。 このようなdefadviceの呼び出しでは、 (有効か無効に関わらず)このアドバイス断片と 当該関数に対して現在有効になっている他のアドバイスを元定義 に結合した定義を作成します。 defadviceをコンパイルすると、その結合定義もコンパイルします。

のちに関数のアドバイスを活性にしたとき、 関数に対する有効にしたアドバイスがこの結合定義の作成に 使用したものに一致すると既存の結合定義を使います。 そのため、新たに結合定義を作成する必要がなくなります。 したがって、予約活性はけっしてまちがった結果を生じませんが、 予約活性に用いたアドバイスと活性にした有効なアドバイスが一致しないと 利点はなくなります。

不一致のために予約活性が正しく動作していない兆候の例を示します。

関数自体が定義されるまえであってもコンパイル済みの予約活性したアドバイスは 正しく動作します。 しかし、予約活性したアドバイスをコンパイルするときには 関数は定義済みである必要があります。

予約活性したアドバイスが使われない理由を調べるよい方法はありません。 できることは、 関数のアドバイスを活性にするまえに、 (関数trace-function-backgroundで) 関数ad-cache-id-verification-codeをトレースすることです。 活性にしたあと、当該関数に対してad-cache-id-verification-codeが 返した値を調べます。 verifiedならば予約活性したアドバイスが使われています。 これ以外の値は、アドバイスが不適切と判断された理由に関する情報を 与えます。

警告: 予約活性が失敗する場合が1つ知られている。 現在のアドバイスの状態に一致しなくても、 あらかじめ作成した結合定義を使ってしまう。 これは、同一関数に対する同じクラスの同一名称であるが異なるアドバイス断片を 2つのパッケージで定義している場合に発生する。 このようなことは避けること。


16.8 アドバイスからの引数の参照

アドバイス断片の本体からアドバイスする関数の引数を参照する もっとも簡単な方法は、関数定義で用いているものと同じ名前を使うことです。 これには、元関数の引数の変数名を知る必要があります。

多くの場合、この単純な方法で十分ですが、欠点もあります。 アドバイス内に引数名を直接書き込むために、堅牢ではありません。 関数の元定義が変更されると、アドバイスは動作しません。

他の方法は、アドバイスそのものに引数リストを指定することです。 これは関数の元定義の引数名を知る必要はありませんが、制約もあります。 関数に対するすべてのアドバイスで同一の引数リストを使う必要があります。 なぜなら、すべてのアドバイスに実際に使われる引数リストは、 当該関数のアドバイス断片の最初のものだからです。

より堅牢な方法は、活性にするときに、 つまり、アドバイスを結合した定義を作成するときに 適切なフォームに展開されるマクロを使うことです。 参照用マクロは、関数の引数変数への実引数の分配方法に依存しない 実引数の位置で参照します。 Emacs Lispにおいては、引数の意味は引数リスト内での位置で決まるため、 これは堅牢です。

Macro: ad-get-arg position
位置positionにある実引数を返す。

Macro: ad-get-args position
位置positionから始まる実引数のリストを返す。

Macro: ad-set-arg position value
位置positionにある実引数の値を設定する。

Macro: ad-set-args position value-list
位置positionから始まる実引数のリストにvalue-listを設定する。

例を示します。 関数fooの定義はつぎのとおりであり、

 
(defun foo (x y &optional z &rest r) ...)

つぎのように呼ばれるとします。

 
(foo 0 1 2 3 4 5 6)

そうすると、fooの本体では、 xは0、yは1、zは2、r(3 4 5 6)です。 このとき、ad-get-argad-get-argsは、つぎの値を返します。

 
(ad-get-arg 0) => 0
(ad-get-arg 1) => 1
(ad-get-arg 2) => 2
(ad-get-arg 3) => 3
(ad-get-args 2) => (2 3 4 5 6)
(ad-get-args 4) => (4 5 6)

この例では、引数に値を設定できます。

 
(ad-set-arg 5 "five")

の効果は、6番目の引数を"five"に変更します。 fooの本体を実行するまえにこのアドバイスが実行されると、 本体内ではr(3 4 "five" 6)になります。

つぎは引数リストを変更する例です。

 
(ad-set-args 0 '(5 4 3 2 1 0))

fooの本体を実行するまえにこのアドバイスが実行されると、 fooの本体内では、 xは5、yは4、zは3、r(2 1 0)になります。

これらの引数参照は、実際にはLispマクロとしての実装ではありません。 アドバイス機構で特別に実装してあります。


16.9 subr引数リストの定義

アドバイス機能が結合定義を作成するとき、 元関数の引数リストを知る必要があります。 基本関数に対しては、これはつねに可能とは限りません。 アドバイスが引数リストを決定できないときには、 (&rest ad-subr-args)を使います。 これはつねに動作しますが、 引数値のリストを作成するために効率的ではありません。 ad-define-subr-argsを使って、 基本関数に対する適当な引数名を宣言できます。

Function: ad-define-subr-args function arglist
この関数は、関数functionの引数リストとして arglistを使うことを指定する。

たとえば、

 
(ad-define-subr-args 'fset '(sym newdef))

は、関数fsetの引数リストを指定します。


16.10 結合定義

関数には、n個の事前アドバイス(before-advice)、 m個の包囲アドバイス(around-advice)、 k個の事後アドバイス(after-advice)があるとします。 保護したアドバイス断片はないと仮定すると、 関数のアドバイスを実装するために作成される結合定義は つぎのようになります。

 
(lambda arglist
  [ [advised-docstring] [(interactive ...)] ]
  (let (ad-return-value)
    before-0-body-form...
         ....
    before-n-1-body-form...
    around-0-body-form...
       around-1-body-form...
             ....
          around-m-1-body-form...
             (setq ad-return-value
                   apply original definition to arglist)
          other-around-m-1-body-form...
             ....
       other-around-1-body-form...
    other-around-0-body-form...
    after-0-body-form...
          ....
    after-k-1-body-form...
    ad-return-value))

マクロはマクロとして再定義します。 つまり、結合定義の先頭にmacroを追加します。

元関数やアドバイス断片のどれかに対話宣言があれば、 対話宣言フォームが入ります。 対話的な基本関数をアドバイスした場合には、 特別な方法を使います。 つまり、基本関数をcall-interactivelyで呼び出して、 基本関数自身が引数を読み取るようにします。 この場合、アドバイスからは引数を参照できません。

各クラスのさまざまなアドバイスの本体フォームは、 それらの指定された順に組み立てられます。 包囲アドバイスl(around-advice l)のフォーム群は、 包囲アドバイスl - 1(around-advice l - 1)の フォームの1つに入ります。

包囲アドバイスのもっとも内側では、

 
元定義をarglistに適用

しますが、そのフォームは元関数の種類に依存します。 変数ad-return-valueには、その戻り値が設定されます。 この変数はすべてのアドバイス断片から見えるので、 アドバイスした関数から実際に戻るまえに、 これを参照したり変更できます。

保護したアドバイス断片を含むアドバイスした関数の構造も同じです。 唯一の違いは、フォームunwind-protectにより、 アドバイス断片でエラーを起こしたり非ローカル脱出を行っても、 保護したアドバイスが実行されることを保証します。 包囲アドバイスを1つでも保護していると、その結果として、 包囲アドバイス全体が保護されます。


[ << ] [ >> ]           [Top] [Contents] [Index] [ ? ]

This document was generated by Akihiro Sagawa on January, 21 2003 using texi2html