[pod] [xml]

NAME tie

perltie - オブジェクトクラスを単純な変数に隠す方法

SYNOPSIS

 tie VARIABLE, CLASSNAME, LIST
 $object = tied VARIABLE
 untie VARIABLE

DESCRIPTION

5.0 より前の Perl では、プログラマは dbmopen() を使ってディスクにある 標準 UNIX dbm(3x) フォーマットのデータベースをプログラム中の %HASH と 結び付けることができました。 しかしながら、Perl は特定の dbm ライブラリか別のものを使って ビルドすることができたものの、両方一度にはできませんでした。 そして、この仕組みを他のパッケージや変数の型に拡張することは できなかったのです。

今はできます。

tie() 関数は変数と、その変数に対するアクセスメソッドの実装を提供する クラス(パッケージ)とを結び付けます。 この魔法が一度働けば、tie された変数は自動的に適切なクラスにある メソッド呼び出しを実行します。 クラスのすべての複雑性はメソッド呼び出しに隠されます。 それらのメソッドの名前は、BEGIN() や END() と同様に(そのメソッドを) Perl が こっそりと呼び出すことを示すための規約に従って全て大文字です。

tie() コールの中で、VARIABLE は魔法を掛けられる変数の名前です。 CLASSNAME は正しい型のオブジェクトを実装するクラスの名前です。 LIST にあるその他の引数はクラスの適切なコンストラクタメソッド TIESCALAR()、TIEARRAY()、TIEHASH()、TIEHANDLE() のいずれかに 渡されます(典型的にはこれらの引数は C の dbminit() 関数に渡すのと 同じものです)。 "new" メソッドから返されたオブジェクトは同様に関数 tie() からも 返されます。 これはあなたが CLASSNAME の中の別のメソッドでアクセスしたいというときに 便利でしょう(あなたは実際には正しい「型」(HASH か CLASSNAME) の 参照を、それが適切な bless されたオブジェクトであるということから 返す必要はありません)。 また、関数 tied() を使って、基礎となるオブジェクトへのリファレンスを 取得することができます。

dbmopen() とは異なり、tie() はモジュールを use したり require したり することはありません。 あなたが、自分自身でそれを明示的に行わなければなりません。

Tying Scalars scalar, tying

(スカラを tie する)

tie されたスカラを実装するクラスは、TIESCALAR, FETCH, STORE, そして可能であれば UNTIE や DESTROY といったメソッドを定義しておくべきです。

以下のような操作を、ユーザーに許しているスカラに対してクラスを tie する例を使って順に見て行きましょう。

    tie $his_speed, 'Nice', getppid();
    tie $my_speed,  'Nice', $$;

こうした後ではこれらの変数のいずれかがアクセスされたときには、カレントの システム優先順位が取得されたり返されたりします。 もし変数に代入が行われれば、プロセスの優先順位は変更されます!

システムの PRIO_PROCESS, PRIO_MIN, PRIO_MAX といった定数に アクセスするために Jarkko Hietaniemi <jhi@iki.fi> の BSD::Resource クラスを使います。 以下はこのクラスの前置きです。

    package Nice;
    use Carp;
    use BSD::Resource;
    use strict;
    $Nice::DEBUG = 0 unless defined $Nice::DEBUG;

これがすべきことの全てです。 実際のところ、それよりも多くのことがあります。 ですから、私たちはここでちょっとした完全性、堅牢性、一般的な美しさと いうものを込めました。 もっと簡単な TIESCALAR クラスを作ることも可能です。

Tying Arrays array, tying

(配列を tie する)

tie された配列を実装するクラスは TIEARRAY, FETCH, STORE, FETCHSIZE, STORESIZE、そしておそらく UNTIE や DESTROY といったメソッドを 実装すべきでしょう。

FETCHSIZE と STORESIZE は $#arrayscalar(@array) アクセスに等価なものを提供します。

POP, PUSH, SHIFT, UNSHIFT, SPLICE, DELETE, EXIST といったメソッドは 同名の perl の演算子(ただし小文字)が tie された配列に対して 操作を行うときに必要となります。 Tie::Array クラスは、これらのうち、最初の 5 つの基本的なメソッドを 実装するためのベースクラスとして使用できます。 Tie::Array での DELETE と EXISTS のデフォルトの実装は 単なる croak です。

それに加え、EXTEND は perl が実際の配列中であらかじめ 拡張するようなときに呼び出されます。

ここでの説明のため、要素数が生成時に固定されたサイズである配列を実装します。 固定サイズを越えた要素を作ろうとすると、例外が発生します。 例えば:

    use FixedElem_Array;
    tie @array, 'FixedElem_Array', 3;
    $array[0] = 'cat';  # ok.
    $array[1] = 'dogs'; # exception, length('dogs') > 3.

このクラスに対する 前置きコードは以下の通りです。

    package FixedElem_Array;
    use Carp;
    use strict;

Tying Hashes hash, tying

(ハッシュを tie する)

ハッシュは tie される最初の Perl データ型でした(dbmopen() を参照)。 tie されたハッシュを実装するクラスは、以下のメソッドを定義すべきです。 TIEHASH はコンストラクタです。 FETCH と STORE はキーと値のペアにアクセスします。 EXIST はキーがハッシュにあるかどうかを報告し、DELETE はキーを削除します。 CLEAR はすべてのキーと値のペアを削除することによりハッシュを空にします。 FIRSTKEY と NEXTKEY は全てのキーを反復するための関数 keys() と each() を 実装します。 SCALAR は tie されたハッシュがスカラコンテキストで評価されたときに 呼び出されます。 UNTIE は untie が起きたときに呼び出され、DESTROY は tie された変数が ガーベジコレクションされるときに呼び出されます。

もしこれがたくさんありすぎると感じられるのなら、標準の Tie::StdHash モジュールを単純に継承し、再定義を必要とするものだけを自分で 実装することもできます。 詳しくは Tie::Hash を参照してください。

Perl がハッシュに存在していないキーと、ハッシュに存在しているけれども undef という値を持っているキーとを明確に区別しているということを 忘れないでください。 これら二つの可能性は、exists()defined() という関数を使って検査できます。

次の例は tie されたハッシュクラスを使ったものです。 この例では特定のユーザーのドットファイルを表わすハッシュを提供します。 あなたはハッシュをファイルの名前(からドットを取り除いたもの)によって 添え字付けを行い、そのドットファイルの内容を取得します。 例えば:

    use DotFiles;
    tie %dot, 'DotFiles';
    if ( $dot{profile} =~ /MANPATH/ ||
         $dot{login}   =~ /MANPATH/ ||
         $dot{cshrc}   =~ /MANPATH/    )
    {
	print "you seem to set your MANPATH\n";
    }

tie されたクラスを使ったもう一つの例です。

    tie %him, 'DotFiles', 'daemon';
    foreach $f ( keys %him ) {
	printf "daemon dot file %s is size %d\n",
	    $f, length $him{$f};
    }

この DotFiles という tie されたハッシュでは、私たちは {LIST} フィールドのみをユーザーが本当のハッシュであると考えるであろう幾つかの 重要なフィールドを持ったオブジェクトのために、通常のハッシュを 使いました。

次は Dotfiles.pm の先頭です:

    package DotFiles;
    use Carp;
    sub whowasi { (caller(1))[3] . '()' }
    my $DEBUG = 0;
    sub debug { $DEBUG = @_ ? shift : 1 }

この例では、私たちは開発の間トレースがしやすいようにデバッグ情報を 出力できるようにしたいと考えました。 同様に、警告を出力するのを助ける一つの便利な内部関数を残しました。 whowasi() は呼び出した関数の名前を返します。

以下は、DotoFiles に tie されたハッシュのためのメソッドです。

keys() や values() といった関数は、DBM ファイルのような大きなオブジェクトに 対して使ったときに大きなリストを返す可能性があるということに 注意してください。 そういったものに対して繰り返しの処理を行うには、each() を使うのが 良いでしょう。 例:

    # print out history file offsets
    use NDBM_File;
    tie(%HIST, 'NDBM_File', '/usr/lib/news/history', 1, 0);
    while (($key,$val) = each %HIST) {
        print $key, ' = ', unpack('L',$val), "\n";
    }
    untie(%HIST);

Tying FileHandles filehandle, tying

(ファイルハンドルを tie する)

これは現時点ではまだ部分的にしか実装されていません。

tie されたファイルハンドルを実装するクラスは以下のメソッドを定義すべきです。 TIEHANDLE と、PRINT, PRINTF, WRITE, READLINE, GETC, READ の 中の少なくともいずれか一つ、そして可能であればCLOSE, UNTIE, DESTROY。 また、クラスは以下のものも提供できます: BINMODE, OPEN, EOF, FILENO, SEEK, TELL - もし対応する perl の演算子がハンドルで つかわれるならです。

STDERR が tie されると、その PRINT メソッドが、警告とエラーのメッセージを 出力するために呼び出されます。 この機能は呼び出しの最中には一時的に無効にされているので、再帰ループを 作ることなく PRINT の内部で warn() を使えることを意味します。 また、__WARN____DIE__ のハンドラと同様に、STDERR の PRINT メソッドはパーサーエラーの報告に呼び出されるので、perlvar/%SIG で 言及した問題点が適用されます。

これら全ては perl が他のプログラムに埋め込まれていて、 STDOUT や STDERR で出力する場所はなんらかの特殊なやり方でリダイレクトする 必要があるときに特に便利です。 実際の例は nvi や Apache モジュールを参照してください。

私たちの例では、叫ぶハンドルを生成します。

    package Shout;

以下は私たちのサンプルをどのように使うかの例です。

    tie(*FOO,'Shout');
    print FOO "hello\n";
    $a = 4; $b = 6;
    print FOO $a, " plus ", $b, " equals ", $a + $b, "\n";
    print <FOO>;

UNTIE this UNTIE

全ての型に対する tie について、untie() で呼び出される UNTIE メソッドを 定義できます。 以下の The untie Gotcha を参照してください。

The untie Gotcha untie

(untie のコツ)

tie() や tied() が返したオブジェクトを使おうとするならば、また、 その tie されているターゲットクラスがデストラクタを 定義しているのであれば、あなたが しなければならない 微妙なコツがあります。

セットアップとして、以下の tie の例を考えてみましょう。 これはファイルを使って、スカラに代入された値を記録し続けるというものです。

    package Remember;
    use strict;
    use warnings;
    use IO::File;
    sub TIESCALAR {
        my $class = shift;
        my $filename = shift;
        my $handle = new IO::File "> $filename"
                         or die "Cannot open $filename: $!\n";
        print $handle "The Start\n";
        bless {FH => $handle, Value => 0}, $class;
    }
    sub FETCH {
        my $self = shift;
        return $self->{Value};
    }
    sub STORE {
        my $self = shift;
        my $value = shift;
        my $handle = $self->{FH};
        print $handle "$value\n";
        $self->{Value} = $value;
    }
    sub DESTROY {
        my $self = shift;
        my $handle = $self->{FH};
        print $handle "The End\n";
        close $handle;
    }
    1;

次に挙げるのは、この tie を使った例です。

    use strict;
    use Remember;
    my $fred;
    tie $fred, 'Remember', 'myfile.txt';
    $fred = 1;
    $fred = 4;
    $fred = 5;
    untie $fred;
    system "cat myfile.txt";

これを実行したときの出力は次のようになります。

    The Start
    1
    4
    5
    The End

まずまずですね。 注意深い人は tie されたオブジェクトがここでは使われていないことを 指摘するでしょう。 そこで、Remember クラスにファイルがコメントを含むことを できるようにするメソッドを追加しましょう -- そう、このような:

    sub comment {
        my $self = shift;
        my $text = shift;
        my $handle = $self->{FH};
        print $handle $text, "\n";
    }

次の例は、前の例を comment メソッド(tie されたオブジェクトを 必要とします)を使うために変更したものです。

    use strict;
    use Remember;
    my ($fred, $x);
    $x = tie $fred, 'Remember', 'myfile.txt';
    $fred = 1;
    $fred = 4;
    comment $x "changing...";
    $fred = 5;
    untie $fred;
    system "cat myfile.txt";

このコードを実行したとき、なにも出力されません。 その理由はこうです。

変数が tie されたとき、それは TIESCALAR, TIEARRAY, TIEHASH といった 関数のいずれかの返した値であるオブジェクトに結び付けられます。 このオブジェクトは、通常はただ一つのリファレンス、すなわち tie され た変数からの暗黙のリファレンスだけを持っています。 untile() が呼ばれたとき、このリファレンスは破棄されます。 したがって、最初の例にあったように、オブジェクトのデストラクタ (DESTROY) が 呼び出されてオブジェクトはもはや正当なリファレンスを持たないようになり、 さらにファイルがクローズされます。

しかしながら二番目の例においては、私たちはもう一つの tie された オブジェクトへのリファレンスを $x の中に格納しました。 これは untie() されたときに、存在するオブジェクトに対する正当な リファレンスがまだ存在しているということです。 このためデストラクタはその時には呼び出されません。 そしてファイルはクローズされないのです。 何の出力も無かった理由は、ファイルバッファがディスクに フラッシュされていなかったからです。

さて、あなたはもうこれが問題であることがわかったでしょう。 では、これを避けるにはどうすればいいでしょうか? 省略可能な UNTIE メソッドが導入される前は、唯一の方法は 古き良き -w オプションだけです。 これははあなたが untie() を呼んだそのときに、(untie() の対象となっている) tie されたオブジェクトに対する正当なリファレンスがまだ存在している場合には それを指摘してくれます。 もし二番目のスクリプトを先頭の方に use warnings 'untie' を付けるか、 -w オプションをつけた状態で実行していれば、 Perl は次のような警告メッセージを出力します。

    untie attempted while 1 inner references still exist

スクリプトを正しく動作させ、警告を黙らせるには tie されたオブジェクトが untie() を呼び出すより 前に 正当なリファレンスをなくすようにします。

    undef $x;
    untie $fred;

今や UNTIE が存在するので、クラスデザイナーはクラス機能のどの部分が 本当に untie に関連付けられ、どの部分がオブジェクトが破壊されたときに 関連付けられるかを決定できます。 与えられたクラスについてどんな意味があるかは内部のリファレンスが 維持されているかどうかに依存しているので、tie に関係ないメソッドは オブジェクトで呼び出しできます。 しかし、ほとんどの場合、DESTROY にある機能を UNTIE メソッドに移すのが 意味のあることでしょう。

もし UNTIE メソッドが存在するなら、上記の警告は起こりません。 代わりに UNTIE メソッドは「追加の」リファレンスの数が渡され、もし適切なら 自身の警告を出力できます; 例えば、UNTIE がない場合を複製するには、 このメソッドが使えます:

    sub UNTIE
    {
     my ($obj,$count) = @_;
     carp "untie attempted while $count inner references still exist" if $count;
    }

SEE ALSO

興味深い幾つかの tie() の実装については DB_FileConfig を 参照してください。 多くの tie() 実装のためのよい開始点は、モジュール Tie::Scalar, Tie::Array, Tie::Hash, Tie::Handle のいずれかです。

BUGS

scalar(%hash) で提供されるバケツ使用情報は利用できません。 これが意味することは、真偽値コンテキストで %tied_hash を使っても正しく 動作しないということです(現在のところ、ハッシュが空かハッシュ要素かに 関わらず、このテストは常に偽となります)。

配列やハッシュのローカル化は動作しません。 スコープの終了後、配列やハッシュの値は元に戻りません。

scalar(keys(%hash))scalar(values(%hash)) を使ってハッシュ内の エントリの数を数えることは非効率的です; 全てのエントリに対して FIRSTKEY/NEXTKEY を使って反復する必要があるからです。

tie されたハッシュや配列のスライスは複数回の FETCH/STORE の組を引き起こします; スライス操作のための tie メソッドはありません。

(ハッシュのハッシュのような)複数レベルのデータ構造を dbm ファイルに tie することは簡単にはできません。 問題は、GDBM と Berkeley DB はサイズに制限があり、それを超えることが できないということで、また、ディスク上にあるものを参照する方法についても 問題があります。 これを解決しようとしている実験的なモジュールの一つに、 DBM::Deep というものがあります。 ソースコードは perlmodlib にあるように、 あなたのお近くの CPAN サイトを確かめてください。 その名前にも関わらず、DBM::Deep は DBM を使わないことに注意してください。 問題を解決するためのもう一つの初期の試みは MLDBM で、これも CPAN から 利用可能ですが、かなり重大な制限があります。

ファイルハンドルの tie はまだ不完全です。 現在のところ、sysopen(), truncate(), flock(), fcntl(), stat(), -X は トラップできません。

AUTHOR

Tom Christiansen

TIEHANDLE by Sven Verdoolaege <skimo@dns.ufsia.ac.be> and Doug MacEachern <dougm@osf.org>

UNTIE by Nick Ing-Simmons <nick@ing-simmons.net>

SCALAR by Tassilo von Parseval <tassilo.von.parseval@rwth-aachen.de>

Tying Arrays by Casey West <casey@geeknest.com>