サブルーチンのアトリビュートの信頼性

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つも並べているのは一体何なのか。

*5:コンパイルフェーズが終了するタイミング

*6:なお,Attribute::Handlersでは,呼び出されるフェーズはATTRメタアトリビュートの引数で操作できる。しかし,BEGINフェーズで呼び出すようにするとやはりサブルーチンオブジェクトの構築が終わっていないため,意味のある操作はほとんどできない。

*7:なお,この話はサブルーチンのアトリビュートに限る。変数のハンドラはまた別のプロセスを経て処理される。