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