Perlにおけるメタプログラミングのための基礎知識

例のごとくAmon2の実装を眺めていた所、Amon2::Web::Dispatcher::RouterBoomの解読で手が止まりました。使い方さえ分かれば無理に読む必要は無いわけですが、どうにも気になってしょうがないので仕組みを調べることにしました。コードの雰囲気からしてメタプログラミングしているということは分かるので、その辺りから掘り下げて調べた結果を、この記事でまとめています。

パッケージのシンボルテーブル

シンボルテーブル

パッケージのシンボルテーブルは、パッケージ名に二つのコロンを付けた名前の ハッシュに蓄えられます。 つまり、main のシンボルテーブルは %main::、または短く %:: となります。 同様に、先に述べたネストしたパッケージは %OUTER::INNER:: となります。

perlmod - Perl のモジュール (パッケージとシンボルテーブル) - perldoc.jp

パッケージ内の個々のエントリは%パッケージ名::なハッシュで参照できます。

use strict;
use warnings;
use utf8;

print 'ref(\%main::) => ', ref(\%main::), "\n";
MyModule::info();

package MyModule;
use Data::Dumper;

sub info {
    print 'ref(\%MyModule::) => ', ref(\%MyModule::), "\n";
    print Dumper(\%MyModule::);
}

実行結果

ref(\%main::) => HASH
ref(\%MyModule::) => HASH
$VAR1 = {
          'Dumper' => *MyModule::Dumper,
          'info' => *MyModule::info,
          'BEGIN' => *MyModule::BEGIN
        };

こんな感じにしてみると、

use strict;
use warnings;
use utf8;

my $m = \%MyModule::;
$m->{info}->();
$m->{arg_num}->();
MyModule::arg_num();
MyModule->arg_num();

package MyModule;
use Data::Dumper;

sub info {
    print 'ref(\%MyModule::) => ', ref(\%MyModule::), "\n";
    print Dumper(\%MyModule::);
}

sub arg_num {
    print scalar(@_), "\n";
}

こうなります。

ref(\%MyModule::) => HASH
$VAR1 = {
          'info' => *MyModule::info,
          'Dumper' => *MyModule::Dumper,
          'arg_num' => *MyModule::arg_num,
          'BEGIN' => *MyModule::BEGIN
        };
0
0
1

大体予想通りですね。

ということはこう出来るのかな?

use strict;
use warnings;
use utf8;

my $m = \%MyModule::;
$m->{arg_num} = sub { print scalar(@_), "\n"; };

MyModule::arg_num(); # 0
MyModule->arg_num(); # 1

package MyModule;

実行結果

0
1

できました。

というか、わざわざ変数に格納せずとも、こう出来ます。

use strict;
use warnings;
use utf8;

$MyModule::{arg_num} = sub { print scalar(@_), "\n"; };

MyModule::arg_num(); # 0
MyModule->arg_num(); # 1

package MyModule;

メソッドを動的に生成することも出来ます。

use strict;
use warnings;
use utf8;

foreach my $method (qw{foo bar}) {
    $MyModule::{$method} = sub { print "${method}\n"; };
}

MyModule::foo();
MyModule::bar();

package MyModule;

では、パッケージも動的に出来るのかな?

use strict;
use warnings;
use utf8;
use Data::Dumper;

print Dumper(\%MyModule::);

my $pkg = 'MyModule';
my $m = \%$pkg::;
print Dumper($m);

package MyModule;
sub foo {}

実行結果

$VAR1 = {
          'foo' => *MyModule::foo
        };
$VAR1 = {};

コンパイルは通るけれど、単にMyModule::を$pkg::に置き換えれば良いという話ではないようです。

さて、どうしたものでしょうか。

おまけ:アロー演算子

ここまでは、モジュールを書き変える時にダブルコロンを使ってきましたが、メンバへのアクセス方法にはアロー演算子も使えます。

アロー演算子の場合、アローの左右を変数にすることができます。

use strict;
use warnings;
use utf8;

my $pkg = 'MyModule';
my $method = 'foo';

$pkg->$method();

package MyModule;
sub foo { print "bar\n"; }

ただし、アローの場合は、そもそもの用途が違うので、代入とかは出来ません。(代入すると"Can't modify non-lvalue subroutine call"と言われます。)

おまけ:外部からのメソッドの定義方法

実はこうもできます(参考:このスライドの12枚目)。

# ok
sub MyModule::arg_num { print scalar(@_), "\n"; }
# also ok
*MyModule::arg_num = sub { print scalar(@_), "\n"; };

シンボリックリファレンス

シンボリックリファレンス

リファレンスはもし未定義であれば必要に応じて存在するようになると言いましたが、 もしリファレンスとして使われた値が既に定義されていたときにはどのように なるのか示していませんでした。 これはハードリファレンスでは ありません。 リファレンスとして使ったならそれはシンボリックリファレンスとして扱われます。 つまり、スカラの値は(おそらく)無名の値への直接のリンクではなく、 変数の 名前 として扱われます。

perlref - Perlのリファレンスとネストしたデータ構造 - perldoc.jp

前述の通り、モジュールをハッシュのリファレンスで参照する方法は、既知のモジュールのメソッドを書き換える分には十分ですが、書き換えるモジュールを動的に変更することは出来ないことが分かりました。

これは、文字列をシンボルとして扱えれば解決します。それがシンボリックリファレンスと呼ばれる機能です。が、このシンボリックリファレンスは非常に仕様が厄介です。

例えばこんなコードがあった時、

use utf8;

my $foo = 100;
my $name = 'foo';
$$name = 200;
print $foo, "\n";

これの出力結果は200、、、とみせかけて100です。以下なら200です。

use utf8;

our $foo = 100;
my $name = 'foo';
$$name = 200;
print $foo, "\n";

このことは、以下の仕様に基づきます。

シンボリックリファレンスでは、パッケージ変数(ローカル化されていたとしても グローバル)だけを見ることができます。 (my() で宣言した) レキシカル変数は、シンボルテーブルに 存在しないのでシンボリックリファレンスでは参照することができません。

http://perldoc.jp/docs/perl/5.18.1/perlref.pod#Symbolic32references

これだけ見ても実に非直感的です。ということで、use strict 'refs'下では関数以外のシンボリックリファレンスが制限されます

閑話休題というか結論

ということで、危ない危ないシンボリックリファレンスではありますが、これを使えばモジュール名も動的に出来るんじゃないかなというお話です。

use strict;
use warnings;
use utf8;

no strict 'refs';

*{'MyModule::arg_num'} = sub {
    print scalar(@_), "\n";
};

MyModule::arg_num(1, 2, 3);

package MyModule;

ただ、注意しないとno strict 'refs'状態が広範囲に及んでしまうので上手く使いましょうとのPerlハッカーのありがたいお言葉あるので、気をつけましょう。

補足:DSLとして提供する場合

前述の通りで、モジュールは拡張できますが、DSLとしてexportしたい場合は、sub import内でセットするのが良いです。それ以外でセットする場合は、DSLを使う側で関数に明示的にカッコを付けることが必要となります

sub importはuseされた時に勝手に実行される関数です。

指定したモジュールから、現在のパッケージにさまざまな内容をインポートします; 多くは、パッケージのサブルーチン名や、変数名に別名を付けることで、 実現されています。 これは、以下は等価ですが:

BEGIN { require Module; Module->import( LIST ); }

Module が 裸の単語でなければならない ことを除けば、です。

http://perldoc.jp/func/use

(ただ、perldoc perlmodによれば)

Perl のモジュールは以下のようにして、プログラムに取り込まれます:

use Module;

または:

use Module LIST;

これは以下のものと全く等価です:

BEGIN { require Module; import Module; }

または:

BEGIN { require Module; import Module LIST; }

特殊な場合として

use Module ();

というのは、以下のものと全く等価です:

BEGIN { require Module; }
http://perldoc.jp/docs/perl/perlmod.pod#Perl32Modules

謎です。