Perlで特異メソッド
Ruby界隈ではあたりまえのように使われる特異メソッド*1だが,Perlでは組み込みでのサポートはなく,標準モジュールにも特異メソッドを実現するものはない。Class::MOP/Mooseの匿名クラスが似た用途を持っている*2が,オブジェクトの実装型に制約がある。たとえば,以下のコードは動かない。
#!perl -w use strict; use IO::File (); use Moose (); my $anonclass = Moose::Meta::Class->create_anon_class( superclasses => [qw(IO::File)], methods => { hello => sub{ my $self = shift; $self->print("Hello, world!\n"); }, } ); my $io = $anonclass->new_object('foo.txt', '>'); use Data::Dumper; print Dumper $io; $io->hello(); # throws "Not a GLOB reference at ..." __END__
そこで,Class::Monadicというモジュールを作ってみた。
- http://search.cpan.org/dist/Class-Monadic
- http://svn.coderepos.org/share/lang/perl/Class-Monadic/trunk
Rubyでは特異クラスはSingleton Classと呼ばれるが,それではシングルトンパターンと区別できないので適当な名前を付けた。
Class::Monadicを使うと,上記のコードは以下のようになる。
#!perl -w use strict; use IO::File (); use Class::Monadic qw(:all); my $io = IO::File->new('>foo.txt'); monadic($io)->add_method( hello => sub{ my($io) = @_; $io->print("Hello, world!\n"); } ); $io->hello();
実装としては,オブジェクトの元のクラスを継承した新しいシンボルテーブルと対応するメタクラスインスタンスを作り,オブジェクトが破棄されると同時にそのメタクラスインスタンスのデストラクタでシンボルテーブルを削除するようになっている。このメタクラスインスタンスをオブジェクトに関連付けるためにはフィールドハッシュを使っており,そのためオブジェクトの実装型に依存しないで済んでいる*3。また,メソッドやフィールドなどの全てのメタデータはメタクラスが管理する。このため元のオブジェクトに対する操作はほとんどなく,既存のクラス全てに対して適用できる。この辺りはClass::MOPを参考にした。
ところで,Class::MOPの匿名クラスはサブクラス化するのが正しいクラスモデルであるように思える。特異クラスを新設して,Classs::MOP::Class::Monadic is-a Class::MOP::Class::Anon is-a Class::MOP::Classとでもしたい。
(追記#1)
なお,Perl 5.10.0にはハッシュリファレンスをweaken()するとメモリリークする(リファレンスが開放されてもウィークリファレンス用のメタデータが一部開放されない)というバグがあり,ウィークリファレンスを使用する上記コードはそのバグの影響を受けてしまう。したがって,永続環境ではウィークリファレンスを使用するコードは避けたほうがよい。
(追記#2)
Perlで特異メソッドを実現する方法は既にid:dankogaiが言及していた:http://blog.livedoor.jp/dankogai/archives/50484421.html
*1:特定のオブジェクトに固有のメソッドで,シングルトンメソッドとも呼ばれる。Rubyのクラスメソッドがクラスオブジェクトの特異メソッドとして実装されているくらい,ごく普通の機能である。