2005年12月01日

さて、前回ではattributeの扱い方を軽く見てみました。
ただ、あんなコードを毎回書くのは面倒です。
そこでAttribute::Handlersモジュールの登場です。

概要としては、独自attributeの作成を簡単にしてくれます。
もちろん関数以外のattributeの作成が出来ますが、今回も関数のみに絞ります。
詳細はperldocしてください。

基本的な使い方も簡単で

use strict;
use Attribute::Handlers;
sub ah_test1 : ATTR(CODE) {
my($package, $symbol, $referent, $attr, $data, $phase) = @_;
print "ah_test1: $package\n";
}
sub test : ah_test1 {
my $str = shift;
print "test: $str\n";
}
print "start\n";
test('value');
print "end\n";
こんな感じでかけます。
大雑把には、Attribute::HandlersはATTRというattributeを利用可にしています。
ATTRを関数に定義すると、関数名と同じ名前でattributeを作成します。
(CODE)の部分は、コードリファレンスを指します。(ANY)とか()でもありです。
ただし、(SCALAR)とかにしてしまうと型が違うため今回の例ではエラーが起きます。
このコードでは、at_test1という関数で利用可能なattributeを作成しています。
で、実行結果が
]$ perl ./ah1.pl
ah_test1: main
start
test: value
end
となります。

ちなみに、ATTR attributeがついた関数は、任意のperl実行フェーズで動かすことが可能です。
サンプルとして

use strict;
use Attribute::Handlers;
sub ah_test1 : ATTR(CODE,INIT,BEGIN,CHECK,END) {
my($package, $symbol, $referent, $attr, $data, $phase) = @_;
print "ah_test1: $package : $phase\n";
}
sub test : ah_test1 {
my $str = shift;
print "test: $str\n";
}
print "start\n";
test('value');
print "end\n";
だとすると、実行結果が
$ perl ./ah2.pl
ah_test1: main : BEGIN
ah_test1: main : CHECK
ah_test1: main : INIT
start
test: value
end
ah_test1: main : END
となります。

さて、ATTR attributeがついた関数には決められた引数が与えられます。
今回の例のah_test1でも、明示的に呼び出しを行っていないことからも分かるとおり、Attribute::Handlersより呼び出しが行われています。
で、

my($package, $symbol, $referent, $attr, $data, $phase) = @_;
こんな感じの引数なわけですが、それぞれ。
$packageは、attributeされている関数が属しているパッケージ。
$symbolは、attributeされている関数のシンボルテーブル。
$referentは、attributeされている関数のリファレンス。
$attrは、自分自信のattribute名。
$dataは、attributeに対する引数。
$phaseは、現在の実行フェーズ。
になります。
手っ取り早く次のサンプルを見てください。
use strict;
use Attribute::Handlers;
use Data::Dumper;
sub ah_test1 : ATTR(CODE) {
print "\n" . Dumper(@_) . "\n";
}
sub test : ah_test1(attr,{attr => 'hoge'}) {
my $str = shift;
print "test: $str\n";
}
print "start\n";
test('value');
print "end\n";
実行結果
$ perl ./ah3.pl

$VAR1 = 'main';
$VAR2 = \*::test;
$VAR3 = sub { "DUMMY" };
$VAR4 = 'ah_test1';
$VAR5 = [
'attr',
{
'attr' => 'hoge'
}
];
$VAR6 = 'CHECK';

start
test: value
end


大まかには、こんな感じです。
次は応用例として。
前回もattributeは複数設定できると書きましたが、shellのパイプのように複数のattributeを繋げて処理することも出来ます。
その場合は、ちょっとattributeのシンボルテーブルをいじってあげます。

use strict;
use Attribute::Handlers;
use Data::Dumper;
my $i = 1;
sub pipe1 : ATTR(CODE) {
my($package, $symbol, $referent, $attr, $data, $phase) = @_;
print "$attr: $package\n";
no warnings 'redefine';
*{$symbol} = sub {
my $c = $i++;
print "$attr [$c] start\n";
my $ret = &$referent($_[0] . " ($data [$c])");
print "$attr [$c] return = $ret\n";
print "$attr [$c] end\n";
return $ret . "($attr [$c])";
};
}
sub pipe2 : ATTR(CODE) {
my($package, $symbol, $referent, $attr, $data, $phase) = @_;
print "$attr: $package\n";
no warnings 'redefine';
*{$symbol} = sub {
my $c = $i++;
print "$attr [$c] start\n";
my $ret = &$referent("($data [$c]) " . $_[0]);
print "$attr [$c] return = $ret\n";
print "$attr [$c] end\n";
return $ret . "($attr [$c])";
};
}
sub test : pipe1('foo') pipe2('bar') pipe1('baz') pipe1('foo2') pipe2('bar2') pipe1('baz2') {
my $str = shift;
print "test: $str\n";
return $str;
}
print "START\n";
print test('value') ."\n";
print "END\n";
前回の最後のサンプルのような感じになります。
長いですが、結果は下記の通り。
$ perl ./ah4.pl
pipe1: main
pipe2: main
pipe1: main
pipe1: main
pipe2: main
pipe1: main
START
pipe1 [1] start
pipe2 [2] start
pipe1 [3] start
pipe1 [4] start
pipe2 [5] start
pipe1 [6] start
test: (bar [5]) (bar2 [2]) value (baz2 [1]) (foo2 [3]) (baz [4]) (foo [6])
pipe1 [6] return = (bar [5]) (bar2 [2]) value (baz2 [1]) (foo2 [3]) (baz [4]) (foo [6])
pipe1 [6] end
pipe2 [5] return = (bar [5]) (bar2 [2]) value (baz2 [1]) (foo2 [3]) (baz [4]) (foo [6])(pipe1 [6])
pipe2 [5] end
pipe1 [4] return = (bar [5]) (bar2 [2]) value (baz2 [1]) (foo2 [3]) (baz [4]) (foo [6])(pipe1 [6])(pipe2 [5])
pipe1 [4] end
pipe1 [3] return = (bar [5]) (bar2 [2]) value (baz2 [1]) (foo2 [3]) (baz [4]) (foo [6])(pipe1 [6])(pipe2 [5])(pipe1 [4])
pipe1 [3] end
pipe2 [2] return = (bar [5]) (bar2 [2]) value (baz2 [1]) (foo2 [3]) (baz [4]) (foo [6])(pipe1 [6])(pipe2 [5])(pipe1 [4])(pipe1 [3])
pipe2 [2] end
pipe1 [1] return = (bar [5]) (bar2 [2]) value (baz2 [1]) (foo2 [3]) (baz [4]) (foo [6])(pipe1 [6])(pipe2 [5])(pipe1 [4])(pipe1 [3])(pipe2 [2])
pipe1 [1] end
(bar [5]) (bar2 [2]) value (baz2 [1]) (foo2 [3]) (baz [4]) (foo [6])(pipe1 [6])(pipe2 [5])(pipe1 [4])(pipe1 [3])(pipe2 [2])(pipe1 [1])
END
また、このサンプルで、どのタイミングでATTR attributeが実行されているかも分かったかと思います。
関数の入力値や戻り値などの加工や、値チェックなども行うことが出来ます。
それ以外にも、内部フラグの状況に応じて、実際に関数を動かすだとか動かす関数を別のものに切り替えるなんて処理も出来ます。
ただ、ちょっと気軽にコードを書く用途では大掛かりすぎるのかも知れません。


Catalystのようなフレームワークでの活用や、独自アプリケーションソフトのプラグイン機構なんかで取り入れるとしっくりするかと思われます。
ちょっと頑張って、プラグイン機構が組み込まれた、なんちゃってサーバを作ってみました。
プラグインの骨組みはAHappli::Pluginsに書かれています。
プラグイン本体はAHappli::Plugins::Fooのような場所におきます。
プラグインの組み込みは、Catalystと同様にuseする時に組み込むプラグインを指定します。

肝はattributeにあるので、プラグイン側のメソッドは適当でもちゃんと動きます。
本当に簡単に作っているので、挙動などはコードを見て把握してみてください。
全ファイルのダウンロードはhttp://tech.yappo.jp/download/AHappli.tar.gzにて。
もうちょっとしっかり作りこめば、それらしい物が作れるはずでしょう。

CPANにはAttribute::Handlersを使ったモジュールがいくつかありますので、そっちも参考にしてみてください。


MFPM: Attribute::Handlers

Posted by Yappo at 2005年12月01日 06:07 | TrackBack | Perl
Comments
Post a comment









Remember personal info?






コメントを投稿する前に↓の場所にnospamと入力してください。