perlpragma - ユーザープラグマの書き方
プラグマは、strict や warnings のように、コンパイル時や実行時の Perl の
振る舞いにある種の影響を与えるモジュールです。
Perl 5.10 から、プラグマは組み込みのものに制限されません; レキシカル
スコープ内のユーザー関数の振る舞いを変えるユーザープラグマを
作れるようになりました。
例えば、オーバーロードされた算術演算子を実装するクラスを作る必要が
あるとして、use integer; のように働く独自のプラグマを提供したいと
します。
以下のようなコードで
use MyMaths;
my $l = MyMaths->new(1.2);
my $r = MyMaths->new(3.4);
print "A: ", $l + $r, "\n";
use myint;
print "B: ", $l + $r, "\n";
{
no myint;
print "C: ", $l + $r, "\n";
}
print "D: ", $l + $r, "\n";
no myint;
print "E: ", $l + $r, "\n";
以下のように出力されます
A: 4.6
B: 4
C: 4.6
D: 4
E: 4.6
つまり、use myint; が有効のときには家宝演算子は整数に強制され、
一方デフォルトではそうではありませんし、デフォルトの振る舞いは
no myint; で復元されています。
MyMaths パッケージの最低限の実装は以下のようなものです:
package MyMaths;
use warnings;
use strict;
use myint();
use overload '+' => sub {
my ($l, $r) = @_;
# Pass 1 to check up one call level from here
if (myint::in_effect(1)) {
int($$l) + int($$r);
} else {
$$l + $$r;
}
};
sub new {
my ($class, $value) = @_;
bless \$value, $class;
}
1;
import が呼び出されないようにユーザープラグマ myint を空リスト
() 付きで呼び出す方法に注意してください。
Perl コンパイラとの相互作用はパッケージ myint の内側で起こります:
package myint;
use strict;
use warnings;
sub import {
$^H{myint} = 1;
}
sub unimport {
$^H{myint} = 0;
}
sub in_effect {
my $level = shift // 0;
my $hinthash = (caller($level))[10];
return $hinthash->{myint};
}
1;
他のモジュールと同様、プラグマもモジュールとして実装され、use myint; は
以下のようになり:
BEGIN {
require myint;
myint->import();
}
no myint; は以下のようになります:
BEGIN {
require myint;
myint->unimport();
}
従って import と unimport のルーチンはユーザーコードの
コンパイル時 に呼び出されます。
ユーザープラグマは、マジカルなハッシュ %^H に書き込むことで状態を
保持するので、これらの二つのサブルーチンはこれを操作します。
%^H の状態情報は構文木に保管され、caller() から返されたリストの
インデックス 10 の要素としてして実行中に取得できます。
例のプラグマでは、返されたものはユーザースクリプトのプラグマの値を
見つけるために上がっていく呼び出しフレームの数をパラメータとして受け取る
in_effect() ルーチンでカプセル化されます。
これはユーザーのスクリプトの各行が呼び出されるときに $^H{myint} の
値を決定するために caller() を使うので、オーバーロードされた加算を
実装しているサブルーチン内で正しい意味論を提供します。
構文木はスレッド間で共有されます。
つまり、構文木はそれを作った特定のスレッド(従ってインタプリタ実体)よりも
長生きする可能性があると言うことです。
従って、真の Perl スカラを構文木に保管することが出来ません。
圧縮形式を使う代わりに、整数(符号付きと符号なし)、文字列、undef の
いずれかの値のみを保管できます - リファレンスと浮動小数点数は
文字列化されます。
もし複数の値や複雑な構造体を保管する必要があるなら、例えば pack などを
使って直列化するべきです。
%^H からのハッシュキーの削除は記録され、いままで通り exists を使うことで
値が undef でキーが存在することと区別できます。
リファレンスをcaller 経由で取得して変換し直した整数としてデータ構造に
保管しようとしてはいけません; これはスレッドセーフではないからです。
アクセスはロックなしでの構造体に対してであり(これは Perl のスカラに対しては
安全ではありません)、構造体がメモリリークするか、作成したスレッドが終了時に
解放され、もしもし他のスレッドが長生きしすぎると、構文木が削除されたものを
参照することになります。