NAME

mro - メソッド解決順序(Method Resolution Order)

SYNOPSIS

  use mro; # enables next::method and friends globally

  use mro 'dfs'; # enable DFS MRO for this class (Perl default)
  use mro 'c3'; # enable C3 MRO for this class

DESCRIPTION

"mro" 名前空間はメソッド解決順序と一般的なメソッドキャッシュを扱うための いくつかのユーティリティを提供します。

これらのインターフェースは Perl 5.9.5 以上でのみ利用可能です。 より古い Perl のための、ほとんど前方互換な実装については CPAN の MRO::Compat を参照してください。

概観

あるクラスの MRO は、上述の use mro を使うか、以下の "mro::set_mro" 関数を使うことで変更できます。 mro 名前空間の関数は mro モジュールを読み込む必要はなく、実際には コア perl インタプリタによって提供されます。

特殊メソッド next::method, next::can, maybe::next::methoduserequire によって mro モジュールを読み込むまで 利用できません。

The C3 MRO

伝統的な Perl のデフォルトの MRO (深さ優先探索 (depth first search); ここでは DFS と呼ばれます)に加えて、Perl は C3 MRO も 提供するようになりました。 Perl の C3 対応は Stevan Little による Class::C3 モジュールで行われた 作業を基としていて、ここにある C3 関係の文章のほとんどは、そこから直接 コピーされたものです。

C3 って何?

C3 は多重継承における健全なメソッド解決順序を提供することを目的とした アルゴリズムの名前です。 これは最初に Dylan と言う言語 ("SEE ALSO" の章のリンクを 参照してください)、で導入され、後に Python 2.3 の新型のクラスでの 優先 MRO (Method Resolution Order; メソッド解決順序) として採用されました。 つい最近では Perl 6 のクラスでの「正統な」MRO として採用され、Parrot プロジェクトでもデフォルトの MRO として採用されました。

C3 の動作

C3 は常に局所的な優先順位を保存して動作します。 これは、基本的にどのクラスもそのサブクラスより先に現れることはないことを 意味します。 例えば、以下のような古典的なダイヤ型継承パターンを考えます:

     <A>
    /   \
  <B>   <C>
    \   /
     <D>

標準の Perl 5 MRO は (D, B, A, C) です。 この結果、CA のサブクラスにも関わらず、AC より先に 検索されます。 しかし、C3 MRO アルゴリズムでは、(D, B, C, A) の順序になり、この問題は ありません。

この例はかなりつまらないものです; より複雑な場合とより深い説明については、 "SEE ALSO" の章のリンクを参照してください。

関数

mro::get_linear_isa($classname[, $type])

与えられたクラスの、線形化された MRO を含む配列リファレンスを返します。 このクラスで有効な MRO はデフォルトか、あるいは与えられた MRO ($type として指定されていれば c3dfs のどちらか) かを使います。

あるクラスの、線形化された MRO とは、そのクラスでメソッドを解決するときに 検索する(自分自身のクラスを先頭とする)全てのクラスの順序付き配列です。

要求されたクラスがまだ存在していない場合、この関数はそれでも成功し、 [ $classname ] を返します。

UNIVERSAL (および UNIVERSAL の MRO のメンバ) は、あるクラスの MRO の一部ではないことに注意してください; 全てのクラスは暗黙に UNIVERSAL とその親を継承しているにも関わらず、です。

mro::set_mro($classname, $type)

与えられたクラスの MRO を $type 引数 (c3dfs のどちらか) に 設定します。

mro::get_mro($classname)

与えられたクラスの MRO (c3dfs のどちらか) を返します。

mro::get_isarev($classname)

このクラスの mro_isarev を取得して、クラス名の配列リファレンスとして 返します。 これは、例え isa 関係が間接的であっても、与えられたクラス名に対して "isa" 関係にある全てのクラスです。 これは、メソッド/MRO キャッシュの無効化を記録するために MRO コードによって 内部的に使われます。

現在のところ、このリストは伸びるだけで、縮むことはありません。 これは性能を考慮したものです (誰かが @ISA からエントリを削除したときに 適切に追跡して isarev エントリを削除するのは重い処理で、どちらにしろ しょっちゅう起きることではありません)。 実行時にはもはや本当にはこのクラスの "isa" ではないクラスがリストに 残っているということは、将来変更される予定の奇妙な実装詳細であると 考えられています。 このリストを、コアコードと同じ理由(つまり存在する全てのクラスを 検索することに対しての性能の最適化)で見ている限りは 問題にならないはずです。

上述の mro::get_mro と同様、UNIVERSAL は特殊です。 UNIVERSAL (と親) の isarev リストは存在する全てのクラスを 含んでいるわけではありません; メソッド継承の目的において 全てのクラスが事実上子孫であるにも関わらず、です。

mro::is_universal($classname)

与えられたクラス名が、UNIVERSAL 自身あるいは @ISA 継承による UNIVERSAL の親の一つかどうかを示す真偽値を返します。

この関数が真を返すあらゆるクラスは、全てのクラスが潜在的にここから メソッドを継承しているという意味で "universal" です。

上述の isarev と同様の理由で、このフラグは恒久的です。 一旦これがセットされると、例え問い合わされたクラスが実際には もはや univeral ではないとしても、セットされたままです。

mro::invalidate_all_method_caches()

PL_sub_generation をインクリメントして、全てのパッケージの メソッドキャッシュを無効にします。

mro::method_changed_in($classname)

与えられたクラスに依存している全てのクラスのメソッドキャッシュを 無効にします。 通常これは不要です。 ピュア perl コードがメソッドキャッシュについて混乱すると知られている 唯一の場合は、constant の内部で行っているように、読み込み専用の スカラ値を使って新しい定数サブルーチンを手動で設定した場合です。 もしその他の場合を発見した場合は、どうか報告してください; それを修正するか、ここに例外として記述します。

mro::get_pkg_gen($classname)

パッケージ $classname の実ローカルメソッドが変更されるか、 $classname のローカルの @ISA が変更される度にインクリメントされる 整数を返します。

これは多くのクラスを調査するモジュールの作者のためを意図したもので、 与えられたクラスに対して最後に調べてからローカルな属性についての 何か重要なことが変更されたかどうかを素早く調べられるようにします。 これはスーパークラスでのメソッド/@ISA の変更では インクリメントされません。

実際の変更を探し求めるのは自由ですが、実際には何もないかもしれません。 おそらく最後にチェックしてからの全ての変更はお互いにキャンセルされ、 以前の状態のパッケージのままなのでしょう。

この整数は、通常パッケージスタッシュが実体化されたときに 1 から 開始します。 スタッシュが全く存在しないパッケージに対して呼び出すと、0 を返します。 パッケージスタッシュが完全に削除された場合 (通常は起きませんが、誰かが undef %PkgName:: のようなことをしたときに起こります)、数値は 01 にリセットされます (どちらになるかはどのようにしてパッケージが 完全に削除されたかに依ります)。

next::method

これはいくらか SUPER と似ていますが、多重継承の場合によりよい 一貫性を保つために C3 メソッド解決順序を使います。 一般的な継承はそのクラスに対してどの MRO が有効かに従うのに対して、 next::method は C3 MRO だけを使うことに注意してください。

一つの一般的な使用法は以下のようなものです:

  sub some_method {
    my $self = shift;
    my $superclass_answer = $self->next::method(@_);
    return $superclass_answer + 1;
  }

メソッド名を(再)指定しないことに注意してください。 常に開始したメソッドと同じメソッド名を使うことを強制されます。

これはもちろんオブジェクトやクラスを呼び出せます。

呼び出す実際のメソッドを解決する方法は:

  1. まず、呼び出されたオブジェクトやクラスの線形化された C3 MRO を決定します。

  2. 次に、起動されたコンテキストのクラスとメソッド名を決定します。

  3. 最後に、文脈的に囲まれているクラスに到達するまで C3 MRO リストを検索して、 文脈的に囲まれているメソッドと同じ名前の次のメソッドのために MRO リストを検索します。

次のメソッドを検索するのに失敗すると、例外が投げられます (代替案については以下を参照してください)。

これは複雑な多重継承での SUPER の振る舞いとは大きく異なります。 (これは、あるクラスの C3 線形化での共通スーパークラスと、その親が いつも同じ順序になるわけではないということに気付けば明らかです。)

警告: クラスの外部で定義されたメソッドからの next::method の呼び出し:

呼び出したのと違うモジュールで作られたサブルーチン内から next::method を 使うという、一つのエッジケースがあります。 これは複雑なように聞こえますが、実際にはそうではありません。 以下は正しく動作しない例です:

  *Foo::foo = sub { (shift)->next::method(@_) };

*Foo::foo グロブに代入された無名サブルーチンは、想定される foo ではなく __ANON__ から呼び出されたものとして呼び出しスタックに現れるという 問題があります。 next::method は呼び出されたメソッドの名前を見つけるために caller を 使っているので、この場合失敗します。

しかし心配はいりません; 簡単な解決法があります。 Sub::Name モジュールは perl の内部に手を入れて、名前を無名サブルーチンに 代入します。 単にこうすると:

  use Sub::Name 'subname';
  *Foo::foo = subname 'Foo::foo' => sub { (shift)->next::method(@_) };

うまく動きます。

next::can

これは next::method と同様ですが、単にコードリファレンスを返します; この名前のメソッドがもうない場合は undef を返します。

maybe::next::method

単純な場合では、これは以下と等価です:

   $self->next::method(@_) if $self->next_can;

しかし、(goto &maybe::next::method のように)この方法のみが動作する 場合もあります;

SEE ALSO

The original Dylan paper

http://www.webcom.com/haahr/dylan/linearization-oopsla96.html

The prototype Perl 6 Object Model uses C3

http://svn.openfoundry.org/pugs/perl5/Perl6-MetaModel/

Parrot now uses C3

http://aspn.activestate.com/ASPN/Mail/Message/perl6-internals/2746631
http://use.perl.org/~autrijus/journal/25768
http://www.python.org/2.3/mro.html
http://www.python.org/2.2.2/descrintro.html#mro

C3 for TinyCLOS

http://www.call-with-current-continuation.org/eggs/c3.html

Class::C3

Class::C3

AUTHOR

Brandon L. Black, <blblack@gmail.com>

Based on Stevan Little's Class::C3