Mooseでもっと簡単に!
ここまでClass::MOPによるクラス定義を紹介してきましたが、
クラスの定義
ではさっそく先ほどMOPで直接定義したクラスをMooseで再定義してみましょう
package Person;
use Moose; # (1)
# (2)アトリビュートの定義
has 'name' => (
is => 'rw',
required => 1,
);
# (3)メソッドの定義
sub describe {
my $self = shift;
my $name = $self->name;
print "$name";
}
リスト5use Moose
と宣言すると、strict
とwarnings
アトリビュート作成はリスト5has
を使うと$meta->add_
を呼んでいるのと同等になり、name
アトリビュートが生成されます。引数のis => 'rw'
はadd_
の際にaccessor
引数を渡しているのと同等になり、required
を指定することによってオブジェクト生成時にこのアトリビュートが引数で渡されていることが必須になります。
リスト5$meta->add_
が呼ばれたのと同じ状態になります。
Mooseを使用している場合、
my $meta = Person->meta;
$meta->isa( 'Class::MOP::Class' ); # true!
$meta->isa( 'Moose::Meta::Class' ); # true!
クラスの継承
MooseでPersonクラスが再定義できたので、
package Employee;
use Moose;
extends 'Person'; # (1)親クラス
has 'employer' => ( # (2)アトリビュート
is => 'rw',
isa => 'Company',
required => 1,
);
before describe => sub { # (3)メソッドモディファイア
my $self = shift;
my $company = $self->employer->name;
print "所属 $company, 社員名 ";
};
Employeeクラスでは親クラスの指定が必要ですので、extends
は$meta->superclasses
と同等になります。
リスト7isa
を追加すると動的な型制約を指定できます。
最後にリスト7describe
にメソッドモディファイアを追加しています。Class::MOP版に比べると冗長だったadd_
の呼び出しがぐっとシンプルになりました。
MOPとMooseの使い分け
MOPを直接使ってもMooseのようなシンタックスシュガーを使っても、
なおMooseは比較的好き嫌いが分かれるツールですが、
メタクラスの拡張
ここまでMOPで通常のクラスを定義する方法を紹介してきました。ですが、has()
でアトリビュートを定義したときに作成されるアクセサの挙動を変えたり、
メタクラスの定義
たとえば、
この動作を実装するInstanceTrackerをリスト8に定義します。
package InstanceTracker;
use Moose;
# (1)Moose::Meta::Classの子クラスなので、これもメタクラスである
extends 'Moose::Meta::Class';
# (2)生成したインスタンスの配列
has instances => (
is => 'ro',
isa => 'ArrayRef',
default => sub { [] },
);
# インスタンスを生成する継承したメソッドをオーバーライド
sub _construct_instance {
my $self = shift;
# Moose::Meta::Classの_construct_instance本体がインスタンスを返す
my $instance = $self->next::method(@_);
# (3)このインスタンスを見失わないようにする
push @{ $self->instances }, $instance;
# newにインスタンスを返る
return $instance;
};
リスト8instances
アトリビュートも宣言します。
このメタクラスのキモはリスト8_construct_
をオーバーライドしてなおかつインスタンスを保存しています。これでメタクラス経由ですべてのオブジェクトインスタンスを見つけることができます。
InstanceTrackerメタクラスの利用
ではInstanceTrackerを使ってみましょう
リスト9 定義したメタクラスの利用
package User;
# (1) カスタマイズしたメタクラスを利用宣言する
use Moose -metaclass => 'InstanceTracker';
has name => (
is => 'rw',
isa => 'Str',
required => 1,
);
# (2) 生成時に、InstanceTrackerの_construct_instanceに
# 登録したフックを実行する
my $me = User->new(name => "SARTAK");
my $friend = User->new(name => "DMAKI");
# (3) インスタンス全部を小文字の名前に変換する
for my $instance (@{ User->meta->instances }) {
$instance->name( lc($instance->name) );
}
print $me->name; # sartak
print $friend->name; # dmaki
リスト9ではUserというクラスのインスタンスをすべて保存することにしました。InstanceTrackerメタクラスをこのUserクラスに適用するには、use Moose
する際にリスト9-metaclass
引数にメタクラスの名前を渡すだけでOKです。
-metaclass
宣言が適用されたクラスは、_construct_
が実行されてメタクラス内のinstances
アトリビュートに新しいオブジェクトが格納されてから返されます。
あとはUser->meta
からメタクラスを取得して、instances
の中身に操作を加えることができます。リスト9
CPANに登録されている拡張モジュール
CPANには前節で解説したメタクラスの拡張を利用したモジュールがたくさんあります。ここでいくつかを紹介しましょう。
MooseX::StrictConstructor─引数の厳密な確認
Perlでは、
リスト10のWidgetクラスではリスト10write_
アトリビュートを定義したので、write_
という引数を渡せるはずです。しかしリスト10
package Widget;
use Moose;
has write_log => ( # (1)
is => 'rw',
default => 0,
);
sub something {
my $self = shift;
if ($self->write_log) { # (2)
warn "write_log";
}
else {
warn "oops!";
}
}
my $widget = Widget->new(use_log => 1); # (3)
$widget->something();
通常クラス定義とオブジェクト生成のコードは別のファイルにあるため、
これを避けるためのコードを通常のPerlのみで書くこともできますが、
この機能を実装しているのがMooseX::StrictConstructorです。このモジュールを使用するだけで、
リスト11use MooseX::StrictConstructor
を追加すると、use_
という引数を渡すことはエラーになります。エラーがバグの原因行を指摘してくれるので便利です。
package Widget;
use Moose;
use MooseX::StrictConstructor; # (1)メタクラスを拡張する
has write_log => (
is => 'rw',
default => 0,
);
my $widget = Widget->new(use_log => 1); # (2)直接エラーを投げる
$widget->something();
簡単なモジュールですが、
MooseX::Method::Signatures─メソッド引数を定義できるPerl!?
Perlでは、
sub foo {
# 自分で引数 @_から変数に代入しなければならない
my ($arg1, $arg2, $arg3) = @_;
...
}
ですが最近では、
MooseX::Method::Signaturesは、
シグネチャの指定
リスト13では、
package Computer;
use Moose;
use MooseX::Method::Signatures;
# (1)シグネチャなしでもよい
method try_crash {
die if rand(100) (2)$delayは位置個定の引数
method shutdown (Int $delay) {
# (3)$selfも自動的に割り当てる
$self->try_crash;
sleep $delay;
exit;
}
# (4)名前付き引数
method speak (Str :$text, Int :$wpm = 160) {
system("say", "--rate=$wpm", $text);
}
my $laptop = Computer->new;
$laptop->try_crash;
$laptop->speak(text => "This is goodbye.", wpm => 500);
# (5)wpmは160のデフォルト
$laptop->speak(text => "Sayonara.");
# (6)shutdownの$delay変数は5にする
$laptop->shutdown(5);
リスト13method
キーワードでメソッドを宣言します。ここでは引数なしのメソッドを定義しています。
リスト13Int
型の$delay
という引数を宣言します。リスト13my $self
やmy $delay
指定をせずとも、
リスト13$wpm
という引数に160というデフォルト値を指定しています。
なお、
裏で何が起こっているのか
このようにメソッドシグネチャが利用できるようになる裏側では、
もしあとからさらにシグネチャを調べて何か操作をしたければ、
my $meta = Computer->meta;
# (1)MooseX::Method::Signatures::Meta::Methodのインスタンス
my $shutdown = $meta->get_method('shutdown');
$shutdown->signature; # (2)"(Int $delay)"
# (3)シグネチャを表現するオブジェクト
my $signature = $shutdown->parsed_signature;
# "1" -- positionalである引数がある
$signature->has_positional_params;
# "0" -- 名前付き引数がない
$signature->has_named_params;
# (4)各引数を表現するオブジェクトの配列
my @params = @{ $signature->positional_params };
my $delay = $params[0];
$delay->variable_name; # $delay
$delay->type_constraints->to_string; # "Int"
リスト14get_
で定義したメソッドshutdown
のメタレイヤを取得するとMooseX::Method::Signatures::Meta::MethodというMoose::Meta::Methodクラスを継承したオブジェクトを返します。リスト14
文字列だけではあまり使い道がありませんが、parsed_
アトリビュートを取得すれば、
またリスト14