[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] |
アドバイス(advice)機能により、関数の既存の定義に追加できます。 これは、Emacsの他の部分で定義された関数を ライブラリにおいてカスタマイズする見通しのよい方法です。 関数全体を再定義するよりも見通しがよいのです。
各関数は、個別に定義した複数のアドバイス断片を持てます。 それぞれのアドバイス断片は、明示的に有効にしたり無効にできます。 任意の関数で有効にされたすべてのアドバイス断片が実際にその効果を発揮するのは、 当該関数のアドバイスを活性にしたときか 当該関数を定義したり再定義したときです。ここで、アドバイス断片を「有効にする こと」と「活性にすること」は同じことでないので注意が必要です。
使用上の注意: アドバイスは、既存関数の既存の呼び出しのふるまいを変更するのに有用である。 新たな呼び出しやキーバインドの新たなふるまいが必要な場合には、 既存関数を使う新たな関数(や新たなコマンド)を定義するほうが 見通しがよい。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] |
コマンドnext-line
は、ポイントを垂直に複数行移動します。 標準バインドはC-nです。 バッファの最終行で使うと、 next-line-add-newlines
がnil
以外の場合(デフォルトはnil
) このコマンドは行を作るために改行を挿入し、その行に移動します。
同様な機能を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-pやM-xでユーザーが起動したのか Lispから呼ばれたのかに関わらず、 この関数を起動すると、まずアドバイスを実行してから 関数の通常の定義を実行します。
この例は、アドバイスの1つのクラスである事前アドバイスの例であり、 関数の元定義のまえに実行されます。 他に2つのアドバイスクラスがあります。 元定義のあとに実行される事後アドバイス(after-advice)と 元定義の起動を包み込む式を指定する包囲アドバイス(around-advice)です。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] |
アドバイス断片を定義するには、マクロdefadvice
を使います。 defadvice
の呼び出しはつぎのような構文です。 defun
やdefmacro
の構文を基にしていますが、 追加部分があります。
(defadvice function (class name [position] [arglist] flags...) [documentation-string] [interactive-form] body-forms...) |
ここで、functionはアドバイス対象となる関数 (やマクロやスペシャルフォーム)です。 以後、アドバイスする対象を単に『関数』と書きますが、 これにはつねにマクロやスペシャルフォームを含みます。
classはアドバイスのクラスを指定し、 before
、after
、around
のいずれかです。 事前アドバイス(before
)は関数そのもののまえに実行されます。 事後アドバイス(after
)は関数そのもののあとに実行されます。 包囲アドバイス(around
)は関数自身の実行を包み込みます。 事後アドバイスと包囲アドバイスでは、 ad-return-value
に設定することで戻り値を変更できます。
引数nameはアドバイスの名前であり、nil
以外のシンボルです。 アドバイス名は、functionの特定クラスのすべてのアドバイス断片から 1つのアドバイス断片を一意に識別します。 名前でアドバイス断片を参照でき、 それを再定義したり有効にしたり無効にできます。
通常の関数定義の引数リストのかわりに、 アドバイス定義では異なる情報を必要とします。
省略可能なpositionは、指定したclassの 現在のアドバイスリストのどこに新たなアドバイスを置くかを指定します。 first
、last
、あるいは、 0から数え始める位置を指定する数である必要があります (first
は0と等価)。 位置を指定しないとデフォルトはfirst
です。 当該クラスの既存位置の範囲を超えている場合には、 先頭か末尾のどちらか近いほうになります。 既存のアドバイス断片を再定義する場合には、値positionは無視されます。
省略可能なarglistは、 アドバイスが使う引数リストを定義するために使います。 これは、アドバイスを実行するために生成される結合定義 (see 節 16.10 結合定義 (2003/10/30))の引数リストになります。 その結果、アドバイスの式では、 引数の値を参照するためにこのリストの引数変数を使えます。
この引数リストは、関数の実際の呼び出し方を扱えるように、 もとの関数の引数リストと互換性がある必要があります。 2つ以上のアドバイス断片で引数リストを指定している場合、 すべてのアドバイスクラスの中で最初のもの(位置が最小のもの)を使います。
残りの要素flagsは、このアドバイス断片の使い方に関する情報を指定する シンボルです。 正しいシンボルとそれらの意味はつぎのとおりです。
activate
functionが未定義(未定義のアドバイス(forward advice)と呼ぶ状況) であるとこのフラグがすぐに効果を表すことはない。 というのは、未定義関数のアドバイスは活性にできないからである。 しかし、functionを定義するとそのアドバイスは自動的に活性にされる。
protect
unwind-protect
の中に後始末として置かれ、 それよりまえに実行されるコードでエラーが発生したりthrow
を使っても 実行される。 see 節 9.5.4 非ローカル脱出時の後始末。
compile
activate
とともに指定しないと、このフラグは無視する。 see 節 16.10 結合定義 (2003/10/30)。
disable
preactivate
defadvice
をコンパイルしたりマクロ展開したときに、 functionに対するアドバイスを活性にする。 これにより現在のアドバイスの状態に応じたアドバイス定義をコンパイルし、 必要に応じて使われるようになる。See 節 16.7 予約活性 (2003/10/30)。
このdefadvice
をバイトコンパイルする場合にのみ意味を持つ。
省略可能なdocumentation-stringは、 このアドバイス断片の説明文字列になります。 functionに対するアドバイスが活性であると、 (documentation
が返す)functionの説明文は、 関数の元定義の説明文字列とfunctionのアドバイスすべての説明文字列の 合成になります。
省略可能なinteractive-formは、 元関数の対話的ふるまいを変更するために指定します。 2つ以上のアドバイス断片でinteractive-formを指定している場合、 すべてのアドバイスの中で最初のもの(位置が最小のもの)が優先します。
空リストでもかまわないbody-formsは、アドバイスの本体です。 アドバイスの本体では、引数、戻り値、束縛環境を参照/変更したり、 いかなる種類の副作用を起こせます。
警告: マクロをアドバイスする場合、 マクロはプログラムのコンパイル時に展開されるのであって、 コンパイルしたプログラムの実行時に展開されるのではないことに注意。 アドバイスが使用するすべてのサブルーティンは、 バイトコンパイラがマクロを展開するときに必要になる。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] |
包囲アドバイスにより、関数の元定義を包み込むLisp式を書けます。 関数の元定義を実行する場所を特別なシンボルad-do-it
で指定します。 包囲アドバイスの本体に現れたこのシンボルは、 元定義(と内側の包囲アドバイス本体)のフォームを含んだprogn
で 置き換えられます。 例を示しましょう。
(defadvice foo (around foo-around) "Ignore case in `foo'." (let ((case-fold-search t)) ad-do-it)) |
これは、foo
の元定義を実行するときに 大文字小文字を区別しないで探索することを保証します。
包囲アドバイスでad-do-it
を用いなければ、関数の元定義を実行しません。 これは、元定義を完全に無効にする手段です。 (さらに、内側の包囲アドバイス断片も無効にする)
もし包囲アドバイスがad-do-it
を1つ以上用いていれば、関数の元定義 がそれぞれの場所で実行される。これにより、包囲アドバイスは元の関数(と その中の関数に含まれる包囲アドバイス断片)を何度も実行できる。このよう に、元の関数を複数回実行するには、ループの中でad-do-it
を用いて も可能である。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] |
マクロdefadvice
はdefun
に似ていて、 アドバイスのコードやアドバイスに関する他のすべての情報を ソースコードで明示します。 関数ad-add-advice
を用いると、 その詳細を計算で求めたアドバイスを作成できます。
ad-add-advice
を呼び出すと、 関数functionに対するクラスclassのアドバイス断片として adviceを追加する。 引数adviceはつぎの形式である。
(name protected enabled definition) |
ここで、protectedとenabledはフラグであり、 definitionはアドバイスの動作を指定する式である。 enabledがnil
であると、 このアドバイス断片は当初は無効になる (see 節 16.6 アドバイスの有効化と無効化 (2003/10/30))。
functionに指定したクラスclassのアドバイス断片がすでにあると、 positionは新しいアドバイス断片をリストのどこに置くかを指定する。 positionの値は、first
、last
、あるいは、 (リストの先頭を0から数えた)数である。 範囲外の数はその範囲の先頭か末尾のどちらか近いほうになる。また、 positionの値はすでに存在するアドバイス断片を再定義した時には無視 される。
functionに同じ名前のアドバイス断片adviceがすでにあると、 引数positionは無視され、古いアドバイス断片を新しいもので置き換える。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] |
デフォルトでは、アドバイスを定義してもその効果は発揮されません。 アドバイスした関数のアドバイスを活性にして始めて効果を発揮します。 defadvice
でフラグactivate
を指定すれば、 関数にアドバイスを定義したときに活性にできます。 しかし、普通は、関数ad-activate
や以下の活性化コマンドを 呼び出すことで、関数のアドバイスを活性にします。
アドバイスの定義操作と活性化操作を区別することで、 アドバイスを追加するたびに関数を再定義することなる、 関数に複数のアドバイス断片を効率よく追加できます。 さらに重要なことは、関数を実際に定義するまえでも 関数にアドバイスを定義できることです。
関数のアドバイスを初めて活性にすると、 関数の元定義を保存してから、関数に対する有効なアドバイス断片すべてを 元定義と結合して新たな定義を作り出します。 (現在無効にしてあるアドバイス断片は使用しない。 see 節 16.6 アドバイスの有効化と無効化 (2003/10/30)。) この定義をインストールし、 以下に述べる条件に応じてバイトコンパイルする場合もあります。
アドバイスを活性にするコマンドすべてにおいて、 compileがt
であると、 アドバイスを実装する結合定義をコンパイルします。
関数に対するアドバイスの再活性化は、 アドバイスを活性にしたあとに行った当該アドバイスの変更すべて (有効にしたり無効にしたアドバイス断片を含む。 see 節 16.6 アドバイスの有効化と無効化 (2003/10/30))が効果を持つようにするのに便利である。
値がalways
であれば、無条件にコンパイルします。nil
であれば 常にコンパイルしない。
値がmaybe
であれば、バイトコンパイラがすでに読み込まれていればコ ンパイルを行う。値がlike-original
であれば、アドバイスされる関数 の元定義がコンパイルされていたり、組み込み関数(built-in function)であ れば、コンパイルを行う。
この変数はad-activate
(あるいは他の上記にあげた関数)の compile引数がnil
の場合のみ有効になります。もしその引数が nil
でなければ、そのアドバイスがこの変数の値に関わらずコンパイル されます。
『予約活性』(see 節 16.7 予約活性 (2003/10/30))中にアドバイス定義を作成すると その定義はすでにコンパイルされているはずです。 というのは、preactivate
フラグを指定したdefadvice
を 含むファイルをバイトコンパイル中にそれが定義されたはずだからです。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] |
各アドバイス断片には、 それを有効にするか無効にするかを指定するフラグがあります。 アドバイス断片を有効にしたり無効にすることで、 アドバイス断片を未定義にしたり再定義することなくオン/オフできます。 たとえば、関数foo
に対するアドバイス断片my-advice
を 無効にするには、つぎのようにします。
(ad-disable-advice 'foo 'before 'my-advice) |
この関数自身は、アドバイス断片の有効化フラグを変更するだけです。 アドバイスした関数でこの変更の効果を発揮するには、 foo
のアドバイスを再度活性にする必要があります。
(ad-activate 'foo) |
正規表現を用いて、さまざまな関数に対する 多数のアドバイス断片を一度に無効にすることもできます。 この場合も、当該関数のアドバイスを再度活性にすることで、 その効果が発揮されます。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] |
アドバイスを実行するための結合定義を作成することは、 ある程度手間がかかります。 ライブラリで多数の関数をアドバイスしていると、 ライブラリのロードが遅くなります。 そのような場合、あらかじめ適切な結合定義を作成する 予約活性(preactivation)を使えます。
予約活性を使うには、defadvice
でアドバイスを定義するときに フラグpreactivate
を指定します。 このようなdefadvice
の呼び出しでは、 (有効か無効に関わらず)このアドバイス断片と 当該関数に対して現在有効になっている他のアドバイスを元定義 に結合した定義を作成します。 defadvice
をコンパイルすると、その結合定義もコンパイルします。
のちに関数のアドバイスを活性にしたとき、 関数に対する有効にしたアドバイスがこの結合定義の作成に 使用したものに一致すると既存の結合定義を使います。 そのため、新たに結合定義を作成する必要がなくなります。 したがって、予約活性はけっしてまちがった結果を生じませんが、 予約活性に用いたアドバイスと活性にした有効なアドバイスが一致しないと 利点はなくなります。
不一致のために予約活性が正しく動作していない兆候の例を示します。
features
の値にbyte-compile
が含まれる。関数自体が定義されるまえであってもコンパイル済みの予約活性したアドバイスは 正しく動作します。 しかし、予約活性したアドバイスをコンパイルするときには 関数は定義済みである必要があります。
予約活性したアドバイスが使われない理由を調べるよい方法はありません。 できることは、 関数のアドバイスを活性にするまえに、 (関数trace-function-background
で) 関数ad-cache-id-verification-code
をトレースすることです。 活性にしたあと、当該関数に対してad-cache-id-verification-code
が 返した値を調べます。 verified
ならば予約活性したアドバイスが使われています。 これ以外の値は、アドバイスが不適切と判断された理由に関する情報を 与えます。
警告: 予約活性が失敗する場合が1つ知られている。 現在のアドバイスの状態に一致しなくても、 あらかじめ作成した結合定義を使ってしまう。 これは、同一関数に対する同じクラスの同一名称であるが異なるアドバイス断片を 2つのパッケージで定義している場合に発生する。 このようなことは避けること。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] |
アドバイス断片の本体からアドバイスする関数の引数を参照する もっとも簡単な方法は、関数定義で用いているものと同じ名前を使うことです。 これには、元関数の引数の変数名を知る必要があります。
多くの場合、この単純な方法で十分ですが、欠点もあります。 アドバイス内に引数名を直接書き込むために、堅牢ではありません。 関数の元定義が変更されると、アドバイスは動作しません。
他の方法は、アドバイスそのものに引数リストを指定することです。 これは関数の元定義の引数名を知る必要はありませんが、制約もあります。 関数に対するすべてのアドバイスで同一の引数リストを使う必要があります。 なぜなら、すべてのアドバイスに実際に使われる引数リストは、 当該関数のアドバイス断片の最初のものだからです。
より堅牢な方法は、活性にするときに、 つまり、アドバイスを結合した定義を作成するときに 適切なフォームに展開されるマクロを使うことです。 参照用マクロは、関数の引数変数への実引数の分配方法に依存しない 実引数の位置で参照します。 Emacs Lispにおいては、引数の意味は引数リスト内での位置で決まるため、 これは堅牢です。
例を示します。 関数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-arg
やad-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マクロとしての実装ではありません。 アドバイス機構で特別に実装してあります。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] |
アドバイス機能が結合定義を作成するとき、 元関数の引数リストを知る必要があります。 基本関数に対しては、これはつねに可能とは限りません。 アドバイスが引数リストを決定できないときには、 (&rest ad-subr-args)
を使います。 これはつねに動作しますが、 引数値のリストを作成するために効率的ではありません。 ad-define-subr-args
を使って、 基本関数に対する適当な引数名を宣言できます。
たとえば、
(ad-define-subr-args 'fset '(sym newdef)) |
は、関数fset
の引数リストを指定します。
[ < ] | [ > ] | [ << ] | [ Up ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] |
関数には、n個(0からn-1として数えられる)の事前アドバイス(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) end-of-around-m-1-body-form... .... end-of-around-1-body-form... end-of-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つでも保護していると、その結果として、 包囲アドバイス全体が保護されます。
[ << ] | [ >> ] | [表紙] | [目次] | [索引] | [検索] [上端 / 下端] |