アクセサ(3) クロージャでアクセサ

先日の AUTOLOAD アクセサに関して id:fbis さんから言及を頂きました♪(初トラックバック!)
アクセサの作り方のまとめ - Unknown::Programming

アクセサの作成方法はいろいろあるけど自作するならAUTOLOADとか使わずに、

 package Hoge::Class;
 use strict;
 
 for my $method (qw/age name tel hoge/) {
     my $code = sub {
         my $self = shift;
         $self->{$method} = shift if @_;
         return $self->{$method};
     };
     no strict 'refs';
     *{$method} = $code;
 }

って感じでクロージャと型グロブで定義すればいいかと。

ななな、なるほど。
せっかくコードを書いて頂いたので、お題にして勉強したいと思います。転載させていただきますが、何か不都合があったらごめんなさい。
色々学ぶ事がありそうですね。

 for my $method (qw/age name tel hoge/) {

qw は囲んだ文字列を空白で区切ったリストにする演算子です。つまりこの for 文の $method には "age", "name", "tel", "hoge" が順番に入る事になります。文字列をわざわざクォートする必要がなくなるので、qw 演算子は良く使われるそうです。
ちなみに、q や qq などと同じように囲うブラケットには '/' に限らず好きな文字が使えます。

     my $code = sub {
         my $self = shift;
         $self->{$method} = shift if @_;
         return $self->{$method};
     };

ここが肝のクロージャとなる無名関数ですね。中身は一見普通のアクセサですが、「$self->{$method}」に注目です。外側のレキシカル変数である $method を、中で参照しています。
クロージャ」(closure)と呼ばれるゆえんとして、「取り囲んでいる(enclosing)レキシカルスコープを保存する」という特徴があります。「取り囲んでいるレキシカルスコープ」とは、この場合 for 文のブロックですね。「保存する」とは、つまり「レキシカル変数(my で宣言された変数)を保存する」という意味です。
このコードの場合、クロージャ $code は生成された時点の「$method」の値を保存します。$method が "age" の時に生成されたクロージャは、後から呼び出しても、$method の中身は "age" のままです。
SF的な表現で言えば、クロージャの外の時間が進んでレキシカル変数の内容が変えられようとも、クロージャの中の時間は生成された時点で止まったままなのです。
そうして作られたクロージャのリファレンス $code を

     no strict 'refs';
     *{$method} = $code;

で、実際にメソッドとして登録しています。
「*{$method}」は、$method という名前の型グロブを指す為の、シンボリックリファレンスですね。
strict プラグマを使っていると、シンボリックリファレンスの使用が制限されますので、「no strict 'ref'」で一時的にシンボリックリファレンスを使えるようにしています。
そして、型グロブにリファレンスを代入した時の動きはすでに勉強しました。$code は関数へのリファレンスですから、型グロブに代入すると、その型グロブの名前の関数になります。つまり、ここでパッケージのメソッドとして登録されているわけですね。
$method には "age", "name", "tel", "hoge" が順番に入りますから、それぞれに対するアクセサのクロージャが生成され、メソッドとして登録される事になります。すごい!

このコードの場合、メソッドが登録されるのは実行時ですが、fbis さんは他にも

  • AUTOLOAD された時に初めてアクセサを登録する方法
  • あらかじめ用意したアクセサ名のリストと照らし合わせる事で、存在しないアクセサメソッドの AUTOLOAD を防ぐ方法
  • UNIVERSAL::canAUTOLOAD モジュールによって AUTOLOAD されるメソッドを UNIVERSAL::can に対応させる方法

など、様々な方法をご紹介してくださっています。

そして最後のどんでん返し。

とまあアクセサ作成方法をここまで書いておきながら一番いいのはClass::Accessor(or Class::Accessor::Fast)使うこと。

 package Hoge::Class;
 use base qw( Class::Accessor::Fast );
 __PACKAGE__->mk_accessors(qw/age name tel hoge/);

ただ僕個人的にはClass::Data::Accessorの方が好きなんでそっち使ってるけどね。

あぜん。
これは楽チンですね。Class::Accessor や Class::Data::Accessor については後日じっくり勉強したいと思います!


人が書いたコードを読むのは、とっても勉強になりますね! TIMTOWTDIPerl だから、人によって書き方は千差万別。アクセサの書き方1つとっても、こんなに色々な書き方があるのは、とても面白いです。
fbis さん、アドバイスありがとうございました。m(_ _)m