サブルーチンのアトリビュートの信頼性
Perl 5.6.0以降にはアトリビュート(Attribute)というメカニズムがあり,変数やサブルーチンの宣言時に呼び出されるコールバックを設定できる*1。
このアトリビュートは実験的に取り入れられたのちに放置されるという不幸な運命をたどってしまい,現状では活用するのが難しい。特に,サブルーチンアトリビュートの扱いは非常に難しく,使用に際しては様々な問題がある*2。
一つの大きな問題は,アトリビュートハンドラが呼び出されるタイミングである。アトリビュートハンドラはサブルーチンオブジェクトの構築中*3に呼び出されるのだが,サブルーチンオブジェクトの構築が不完全なため,そのままではアトリビュートハンドラ内でできることはほとんどないのだ。以下のスクリプトはこのことを示す。
#!perl -w use strict; use Devel::Peek; sub MODIFY_CODE_ATTRIBUTES{ my($class, $code_ref, $attr_name) = @_; Dump($code_ref); } sub foo :MyAttr{ # ... } __END__
Perl5.10.0で実行した結果:
SV = RV(0x903bc00) at 0x903bbf4 REFCNT = 1 FLAGS = (PADMY,ROK) RV = 0x903c4a4 SV = PVCV(0x9052764) at 0x903c4a4 REFCNT = 6 FLAGS = () COMP_STASH = 0x0 ROOT = 0x0 GVGV::GV = 0x0 FILE = "(null)" DEPTH = 0 FLAGS = 0x0 OUTSIDE_SEQ = 112 PADLIST = 0x903bd64 PADNAME = 0x903bf84(0x0) PAD = 0x903be54(0x90433d4) OUTSIDE = 0x9022784 (MAIN)
ここで注目したいのは,GVGV::GV*4がNULLということだ。つまり,コードリファレンスと型グロブが結び付けられていない。ハンドラに渡されるのは呼び出し元のパッケージ名とコードリファレンスとアトリビュート名だけであるため,元のサブルーチンの上書きなどの操作がまったくできない。
この問題に対処するため,Attribute::Handlersモジュールでは,メタアトリビュートハンドラの中で処理キューに登録するだけにとどめ,CHECKブロック*5の中で登録したアトリビュートハンドラを呼び出すようになっている*6。そして,それがAttribute::Handlersを使いにくいものにしている。CHECKブロックを実行時のrequire()やeval()で定義しても呼び出されないため,Attribute::Handlersによって定義したアトリビュートハンドラは呼び出されることが保障されないのだ。これではアトリビュートハンドラは信頼できず,使い物にならない*7。
そこで,アトリビュートハンドラが確実に呼び出されるように工夫したSub::Attributeというモジュールを書いてみた。Cレベルから見てもハンドラが呼び出された時点では利用できる情報がほとんどないため,やはり処理キューに登録してサブルーチンオブジェクト構築後に登録したハンドラを呼び出すことにした。呼び出されるタイミングはB::Hooks::EndOfScopeと同じ,つまり,コンパイルフェーズにおけるスコープの終わりである。B::Hooks::EndOfScopeもXSモジュールであるVariable::Magicに依存しており,このタイミングで安定してハンドラを呼び出すのは,Pure Perlでの実装はできそうもない。使い方はAttribute::Handlersとほぼ同じで,渡される引数はAttribute::Handlersより少ないが,互換性はあるようにした。
さて,これでハンドラが呼び出されないことがあるという問題は解決できた。しかしながら,真の問題はこのエントリの最初に書いたように「このアトリビュートは実験的に取り入れられたのちに放置されるという不幸な運命をたどっ」たことにある。アトリビュートハンドラが呼び出されるop.cのnewATTRSUB()を読む限りでは,そのタイミングは恣意的であり,必然性は感じられない。これが,newATTRSUB()の終了直前に呼び出されるものであったとしたら,Sub::Attributeが行っているようなハックは不要であり,そもそも問題はなかったと考えられる。
*1:なお,このアトリビュートはMooseなどで用いられるオブジェクトフィールドとしてのアトリビュートとは無関係である。
*2:このエントリで指摘しなかった問題については,たとえばモジュールの実行時ロードと相性が悪いとういうことがある。この問題については牧氏による以下のエントリが参考になる:http://mt.endeworks.jp/d-6/2009/05/moosification-catalyst-58.html
*3:newATTRSUB()においてCV構造体を構築している最中
*4:本文とは関係ないが,GVGV::GVと"GV"を3つも並べているのは一体何なのか。
*6:なお,Attribute::Handlersでは,呼び出されるフェーズはATTRメタアトリビュートの引数で操作できる。しかし,BEGINフェーズで呼び出すようにするとやはりサブルーチンオブジェクトの構築が終わっていないため,意味のある操作はほとんどできない。