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 のスカラに対しては 安全ではありません)、構造体がメモリリークするか、作成したスレッドが終了時に 解放され、もしもし他のスレッドが長生きしすぎると、構文木が削除されたものを 参照することになります。