euc-jpNAME

perlpragma - ユーザープラグマの書き方

DESCRIPTION

プラグマは、strictwarnings のように、コンパイル時や実行時の 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/in_effect"} = 1;
    }
    
    sub unimport {
        $^H{"myint/in_effect"} = 0;
    }
    
    sub in_effect {
        my $level = shift // 0;
        my $hinthash = (caller($level))[10];
        return $hinthash->{"myint/in_effect"};
    }
    
    1;

他のモジュールと同様、プラグマもモジュールとして実装され、use myint; は 以下のようになり:

    BEGIN {
        require myint;
        myint->import();
    }

no myint; は以下のようになります:

    BEGIN {
        require myint;
        myint->unimport();
    }

従って importunimport のルーチンはユーザーコードの コンパイル時 に呼び出されます。

ユーザープラグマは、マジカルなハッシュ %^H に書き込むことで状態を 保持するので、これらの二つのサブルーチンはこれを操作します。 %^H の状態情報は構文木に保管され、caller() から返されたリストの インデックス 10 の要素としてして実行中に読み込み専用で取得できます。 例のプラグマでは、返されたものはユーザースクリプトのプラグマの値を 見つけるために上がっていく呼び出しフレームの数をパラメータとして受け取る in_effect() ルーチンでカプセル化されます。 これはユーザーのスクリプトの各行が呼び出されるときに $^H{"myint/in_effect"} の値を決定するために caller() を使うので、 オーバーロードされた加算を実装しているサブルーチン内で正しい意味論を 提供します。

キーの命名規則

%^H は一つだけしかありませんが、そのスコープ意味論を使いたいモジュールは 任意の数があり得ます。 お互いの足を踏むことを裂けるために、ハッシュ中で異なったキーを使うように する必要があります。 従って、モジュールはモジュールの名前 (主なパッケージの名前) と "/" 文字から 始まるキーだけを使うという慣習があります。 このモジュール識別接頭辞の後、キーの残りは全てモジュール次第です: どんな文字でも使えます。 例えば、モジュール Foo::BarFoo::Bar/bazFoo::Bar/$%/_! のようなキーを使うべきです。 この慣習に従っているモジュールは全て互いにうまく動作します。

Perl コアは一部この規約に従わないキーで %^H を使います; なぜなら 以前からあるからです。 慣習に従うキーはコアの歴史的なキーと衝突することはありません。

実装の詳細

構文木はスレッド間で共有されます。 つまり、構文木はそれを作った特定のスレッド(従ってインタプリタ実体)よりも 長生きする可能性があると言うことです; 従って、真の Perl スカラを構文木に 保管することが出来ません。 圧縮形式を使う代わりに、整数(符号付きと符号なし)、文字列、undef の いずれかの値のみを保管できます - リファレンスと浮動小数点数は 文字列化されます。 もし複数の値や複雑な構造体を保管する必要があるなら、例えば pack などを 使って直列化するべきです。 %^H からのハッシュキーの削除は記録され、いままで通り exists を使うことで 値が undef でキーが存在することと区別できます。

リファレンスをcaller 経由で取得して変換し直した整数としてデータ構造に 保管しようとしてはいけません; これはスレッドセーフではないからです。 アクセスはロックなしでの構造体に対してであり(これは Perl のスカラに対しては 安全ではありません)、構造体がメモリリークするか、作成したスレッドが終了時に 解放され、もしもし他のスレッドが長生きしすぎると、構文木が削除されたものを 参照することになります。