筆者の好きな Perl 5 標準モジュールの話をする。実用性は特にない。
SelectSaver というモジュールを知っているだろうか。 なにしろラクダ本にも載っている天下の標準モジュールである。ちょっとでも Perl 5 をかじったことのあるプログラマなら知っているだろう。1
それでこれは何かというと、オブジェクト生成時にファイルハンドルを select し、破棄時に元のファイルハンドルを再び select するガードオブジェクト、要するに select 専用 Scope::Guard である。
このモジュールの意義を知るためにはまず select を知る必要がある。 ニワカでなければ誰でも知っていることだが2、Perl 5 の select は無ないし一引数版 (以下 Prolog 風に select/1 と書く) と三ないし四引数版 (同 select/4) でセマンティクスが丸っきり違う。実質オーバーロードされていると言って良い。 select/4 は単にシステムコール select(2) の Perl 版だが、select/1 は print / say / write / $| (AUTOFLUSH) などが使うデフォルトのファイルハンドルを選択するという全く無関係な機能を提供する。 標準出力をリダイレクトするわけではない。STDOUT はそのままで print みたいな標準関数や $| みたいな特殊変数が指定されたファイルハンドルを使うようになるだけである。また戻り値は直前に select されていたファイルハンドルを返す。ちなみに引数なしで呼び出すと現在の設定を変更せずに同じ値を返す。
プロセスのグローバルな状態を書き換えるなんとも C っぽい関数だが、例えば一つのファイルにひたすら書き出すときにファイルハンドルを省略できて便利である:
sub print_batch {
my ($fh) = @_;
my $old_fh = select $fh;
print ...;
print ... if ...;
print ... for 1 .. 10;
failable_instruction(...); # XXX: 失敗し得る関数呼出し
...
select $old_fh;
}しかし上記の関数には問題がある。例外安全でないことだ。 もし failable_instruction が die すると select $old_fh が呼ばれないまま関数から脱出してしまい、$old_fh が何だったのか復元する方法はない。
このようなシナリオで活躍するのが SelectSaver である。C++ でお馴染みの RAII で自分が生まれたときに select されていたファイルハンドルを死ぬときに返してくれる:
sub print_batch {
my ($fh) = @_;
# 生成時に渡した $fh が select される。元のファイルハンドルは $saver が覚えている
my $saver = SelectSaver->new($fh);
print ...;
print ... if ...;
print ... for 1 .. 10;
failable_instruction(...);
...
# failable_instruction が死んでも関数が末尾に到達しても $saver のスコープを抜けるので元のファイルハンドルを `select` し直す
}自分が死んでも約束を果たす、今どきこんな義理堅い奴はちょっといないだろう。いや実際は「死んだら約束を果たす」なので保険がかけられた借金オヤジかも知らんが。
実装は非常に簡潔である:
use Carp;
use Symbol;
sub new {
@_ >= 1 && @_ <= 2 or croak 'usage: SelectSaver->new( [FILEHANDLE] )';
my $fh = select;
my $self = bless \$fh, $_[0];
select qualify($_[1], caller) if @_ > 1;
$self;
}
sub DESTROY {
my $self = $_[0];
select $$self;
}コンストラクタで元のファイルハンドルへのリファレンス自身を bless しておき、デストラクタはそれをもう一度 select するだけ。SV 一個で済んで実にエコ。
少しややこしいのは select qualify($_[1], caller) の箇所だろうか。ここにはファイルハンドルを裸のワードとして扱ってきた歴史的経緯が見える。 qualify は Symbol モジュールが提供する関数で、第一引数が文字列かつそれが識別子の修飾名でなければ第二引数のパッケージに存在する識別子として修飾名を返す。 裸のワードは同名の関数がなければ文字列として扱われるので、呼出し元パッケージ (caller の戻り値; qualify のプロトタイプが $;$ なのでスカラコンテキストで呼ばれる) のグローバルなファイルハンドル識別子が得られる訳である。 ちなみに裸のワードでなく近頃 (i.e., 90年代中頃から) の流儀に沿ってグロブへのリファレンスを渡した場合は qualify が第一引数自身を返すので結局問題ない。
以上、Perl 5 の標準モジュールで筆者が最も好きな SelectSaver を紹介した。その魅力は
- 名前の格好良さ
- 実装の簡潔さ
- 馬鹿馬鹿しいほど単機能にも関らず標準モジュールという事実
に尽きると思われる。たまに商用のプログラムに使ってみるのも一興である。
コメント
コメントを投稿