日向夏特殊応援部隊

俺様向けメモ

Module::Pluggable::Fast Source Code Reading

そもそも論ですけど

Similar to C but instantiates plugins as soon as they're found, useful for code generators like C.

ってあるように、Module::Pluggableと同じインターフェースな訳じゃなくて似てるモジュールです。

  1. 似てる
  2. 速い
  3. すぐインスタンス化する

ってのが特徴ですね。

同様にimport経由でpluginの読み込みを行います。

Module::Pluggable::Fast

import()
sub import {
    my ( $class, %args ) = @_;
    my $caller = caller;
    no strict 'refs';
    *{ "$caller\::" . ( $args{name} || 'plugins' ) } = sub {
        my $self = shift;
        $args{search}   ||= ["$caller\::Plugin"];
        $args{require}  ||= 0;
        $args{callback} ||= sub {
            my $plugin = shift;
            my $obj    = $plugin;
            eval { $obj = $plugin->new(@_) };
            carp qq/Couldn't instantiate "$plugin", "$@"/ if $@;
            return $obj;
        };

name argsでpluginsって名前以外にもメソッド名が変えられるようですが、
デフォではpluginsメソッドがcallerで取得した呼び出し元のpackageに、
メソッドとして追加されます。

冒頭はそもそものimportメソッドで指定するargsの初期値設定から始まります。
重要なのはcallbackが指定出来る所でしょうか。

ここでcallbackを弄ればplugin名から何でも出来るっぽぃですね。

my %plugins;
        foreach my $dir ( exists $INC{'blib.pm'} ? grep { /blib/ } @INC : @INC )
        {
            foreach my $searchpath ( @{ $args{search} } ) {
                my $sp = catdir( $dir, ( split /::/, $searchpath ) );
                next unless ( -e $sp && -d $sp );

ここら辺はModule::Pluggableとほぼ同じ処理で、モジュール作成時のblibがあれば@INCを書き換えつつ、
search argsで指定された探索パス*1をループさせて、さらにそれを@INCから取り出したディレクトリと結合して、
そのディレクトリがあるば次に進みます。

                foreach my $file ( _find_packages($sp) ) {
                    my ( $name, $directory ) = fileparse $file, qr/\.pm/;
                    $directory = abs2rel $directory, $sp;
                    my $plugin = join '::', splitdir catdir $searchpath,
                      $directory, $name;
                    $plugin->require;
                    my $error = $UNIVERSAL::require::ERROR;
                    die qq/Couldn't load "$plugin", "$error"/ if $error;

相対ディレクトリ化して、それらからpluginのパッケージを抽出、そしてUNIVERSAL::requireを使ってロードします。

                    unless ( $plugins{$plugin} ) {
                        $plugins{$plugin} =
                            $args{require}
                          ? $plugin
                          : $args{callback}->( $plugin, @_ );
                    }

%pluginハッシュのキーにまだ$pluginが存在しない場合は、require argsがtrueならばパッケージ名のままで、
falseならばcallbackを叩きます。ちなみにcallerで取り出した呼び出しもとのpackageからplugins()を呼び出す際のパラメータが全て渡されてますね。

                    for my $class ( _list_packages($plugin) ) {
                        next if $plugins{$class};
                        $plugins{$class} =
                            $args{require}
                          ? $class
                          : $args{callback}->( $class, @_ );
                    }
                }
            }
        }
        return values %plugins;
    };

多分、内部パッケージの処理ですね。一応__list_packages()関数を見てみましょう。

__list_packages()
sub _list_packages {
    my $class = shift;
    $class .= '::' unless $class =~ m!::$!;
    no strict 'refs';
    my @classes;
    for my $subclass ( grep !/^main::$/, grep /::$/, keys %$class ) {
        $subclass =~ s!::$!!;
        next if $subclass =~ /^::/;
        push @classes, "$class$subclass";
        push @classes, _list_packages("$class$subclass");
    }
    return @classes;
}

シンボルテーブルにアクセスする為に、package名のお尻に「::」を付けて、さらにそれのkeyを抽出して、お尻に::を含み、
さらにmainで始まらない物を抽出します。

この際にお尻の「::」は余計なので削除して、さらに先頭が「::」で始まる場合*2はスキップ。
完全なパッケージ名として@classesに追加します。さらに再帰的に呼び出してさらにサブクラスを抽出して行くと言う流れになってます。

まとめ

見るからにModule::Pluggable::Fastの方が速そうですし、やってる事もシンプルなのでこっちを使った方がいいんじゃないかなーと思います。
callbackで結構色々出来そうですしね。

*1:packageのprefix

*2:これはどういうケースだろう。。。