モダンPerlの世界へようこそ

第4回 Any::Moose:なにがどうでもムースはムース

この記事を読むのに必要な時間:およそ 5 分

CPANTSは情報の宝庫

Perlを使う最大の利点といわれるCPANですが,CPANは単なるモジュール置き場ではありません。CPANはまたPerlの利用状況を知るうえで不可欠な統計情報を得る場でもあります。そのような統計情報のいくつかは,いわゆるCPAN検索サイトからも確認できますが,より突っ込んだ情報が欲しい場合はCPANTSCPAN Testing Serviceと呼ばれるサイトを確認するのが便利です。

国内ではnipotanこと谷口公一氏が始めた輝け!全日本最強 CPAN Author 決定選手権のネタ元として知られていますが,このサイトでは個々の作者やモジュールの品質だけでなく,そのモジュールが実際にどこで使われているかという情報を得ることもできます。

たとえば前回取り上げたロール関連のモジュールの利用状況を調べてみると,古き良きExporterを依存モジュールとして取り上げているモジュールの数は275個。ルーク・パーマー氏のClass::Roleを利用しているモジュールはなし。クロマティック氏のClass::Rolesを利用しているモジュールは8個(そのうちご当人のモジュールが5個)。前回大きく取り上げたスティーヴン・リトル氏とカーティス・ポー氏のClass::Traitは1個。スティーヴン・リトル氏のPerl6::MetaModelはCPANに登録されていないのでデータなしという具合に,Moose以前のロール関連モジュールはプロトタイプとしての価値しかなかったことがデータからも浮き彫りになるのですが,前回最後に取り上げたClass::MOPとMooseのほうは,この原稿を書いている2009年3月末の時点で,Class::MOPが24個Mooseにいたっては100人を越す作者の手になる382個ものモジュールが利用していて,その数はいまも日々増え続けているのですから,少なくともMooseはもう「ギークのおもちゃ」のレベルを超えて,実用段階に入ってきていることが読みとれるといってよいでしょう。

地上でもっとも「重い」シカ

ただ,そんなMooseにも泣き所はいくつかあります。

Wikipediaによると,Moose(ヘラジカ)は平均的な雄で体重380~720キログラム。地上でもっとも重いシカであり,アメリカやヨーロッパの陸上生物としてはバイソンに次ぐ大きさを誇るそうですが,PerlモジュールとしてのMooseも,CPAN界最大というほどではないにせよ,大きく,重たい,というのもそのひとつです。

もっとも,その重さの大部分は,Mooseの影に隠れているメタクラスがアトリビュートのアクセサやロールのメソッドなどを整理してオブジェクトを構築するときのコストによるもの。オブジェクトの構築法が変わっただけで,オブジェクトそのものは従来のPerl 5オブジェクトですから,準備ができたあとの実行速度は従来の方式で組んだオブジェクトと理論上差はありません。

ためしにこのようなスクリプトを実行してみてください。

package MooseObj;
use Moose;
has 'name' => (is => 'rw', isa => 'Str', default => 'foo');
sub print { my $self = shift; print $self->name; }

package main;
use Data::Dumper;
local $Data::Dumper::Indent = 1;
print Dumper(MooseObj->new);

ふつうにオブジェクトをダンプする限り,従来のオブジェクトをダンプしたときと同じ結果しか返ってきませんが,

$VAR1 = bless( {
  'name' => 'foo'
}, 'MooseObj' );

最後の行をこう変えると――あまりに長いので掲載はしませんが――ダンプされる行はたちまち100行ほどにふくれあがります。

print Dumper(MooseObj->new->meta);

Mooseが管理している範囲

このように,ひとつのオブジェクトをつくるだけで芋蔓式に大量のメタオブジェクトができてしまうことが,Mooseが重いといわれる原因のひとつなのですが,そのメタオブジェクトがもたらす自由をフルに活用している(あるいは,できる)ようなオブジェクト/モジュールというのは,それほど多くはありません。

もう一度,先ほどのメタオブジェクトをダンプした結果を確認してみてください。

Moose::Meta::Classのオブジェクト内にmethodsという項目がありますが,そこに入っているのはパッケージ内で明示的に指定したnameというアクセサと,Mooseのほうで自動的に追加するmetaというメソッドだけ。MooseObjパッケージに直書きした「printメソッド」の姿はありません。

  'methods' => {
    'name' => $VAR1->{'_meta_instance'}{'attributes'}[0]{'associated_methods'}[0],
    'meta' => bless( {
      'body' => sub { "DUMMY" },
      'associated_metaclass' => $VAR1,
      'name' => 'meta',
      'package_name' => 'MooseObj'
    }, 'Moose::Meta::Method' )

もちろんこれはこれでPerl 5のオブジェクト機構としては正しい挙動です。ここではあえて「printメソッド」と呼びましたが,これがMooseObjのメソッドであると言えるのは,私たちが$selfという文字を見てメソッドであると判断しているから。Perlにしてみれば,これだけではオブジェクトとは関係のないパッケージ関数なのか,オブジェクトに紐付いているメソッドなのか,判断しようがありません。

だから,本当にこのprint関数をMooseObjのメソッドとして扱いたければ,Mooseのロールをつくってwithコマンドで取り込むか(こうするとMooseが内部でロール内の関数をオブジェクトのメソッドとして登録してくれます),あるいは自分でメタオブジェクトを操作して,メソッドを追加するしかありません。

先の例でいうと,たとえばこのような書き方をすれば,MooseObjのメタオブジェクトをダンプしたときにprintというメソッドが確認できるようになります※1)。

package MooseObj::Role::Print;
use Moose::Role;
sub print { my $self = shift; print $self->name; }

package MooseObj;
use Moose; with 'MooseObj::Role::Print';
has 'name' => (is => 'rw', isa => 'Str', default => 'foo');
※1

もっとも,これはあまりよい例ではありません。厳密を期すならロールの方には当然「requires 'name';」を加えるべきでしょうし,残念なことに,いまのMooseではこのようなアトリビュートのアクセサに対するrequiresは(メタオブジェクト上ではメソッドとして勘定されているにもかかわらず)エラーになってしまいます。

著者プロフィール

石垣憲一(いしがきけんいち)

あるときは翻訳家。あるときはPerlプログラマ。先日『カクテルホントのうんちく話』(柴田書店)を上梓。最新刊は『ガリア戦記』(平凡社ライブラリー)。

URLhttp://d.hatena.ne.jp/charsbar/

コメント

コメントの記入