Moose::RoleはJavaのInterfaceと似たような物だと思ってたんですが、大きな誤解でした。
モダンPerlの世界へようこそを読んで、Moose::RoleはTraits: Composable Units of Behaviorの概念の実装らしいことがわかったので、この論文を読んでみました。*1。非常に面白い内容でした。P.12 の a) と b) を見るだけでも、この概念の面白さが伝わるんじゃないかと。要は、指定した振る舞い(requires)から新しい振る舞い(provides)を作るものが、Traitsってことです。(ただし、ここで言う振る舞いにはアクセサを含みます。)
使ってみる
詳しい説明はMoose::Cookbook::Roles::Recipe1に先ほどの論文と似た例が載ってるのでこちらに譲りますが、お試しで適当にやってみます*4。
合成してLoggingロールを作る
まず、 Println なんてロールを作ります。これは、writeメソッドがあるクラスに対して、改行付きでの出力を行う println メソッドを提供します。
package Println; use Moose::Role; requires 'write'; sub println{ my $self = shift; $self->write(@_, "\n"); } no Moose::Role;
後、Logging と言う log を提供するロールを作ります。こいつは今作った println メソッドが使えるとありがたいので、先ほどの Println と合成(composite)して作ります*5。ちなみに、Logging は Println を合成してますので、 println メソッドを必要(requires)とはしません。←4/8修正:Printlnロールが改変されてprintlnが無くなる場合もあり得るので、requiresは必要です。
package Logging; use Moose::Role; with 'Println'; requires 'println'; sub log { my $self = shift; $self->println( scalar localtime() . ' ' . $_) for @_; } no Moose::Role;
HelloWorldロールを作る
さてさて、ここで上記とは全く別に HelloWorld なんて Role を作ってみます。こいつは、 println メソッドがある(requires)クラスにhelloworldメソッドを付け加え(provides)ます。この println メソッドは、HelloWorldロールが「こんなメソッドあればいいなあ」と思っている(requires)だけで、前述のPrintlnロールとは全く関連がないことに注目して下さい。
package HelloWorld; use Moose::Role; requires 'println'; sub helloworld { my $self = shift; $self->println("Hello world!"); } no Moose::Role;
ロールをクラスに合成する
さて、それでは、MyAppクラスを作ります。このクラスは "Hello world!" を出力して そのことをロギングします。先ほどのロールを合成しましょう。
package MyApp; use Moose; with 'HelloWorld', 'Logging'; no Moose; package main; my $myapp = MyApp->new; $myapp->helloworld; $myapp->log("said 'hello'");
完成!! これで MyApp は HelloWorldする能力とロギングする能力を兼ね備えているはずですので、動かしてみます。
'HelloWorld|Logging' requires the method 'write' to be implemented by 'MyApp' at XXXX.pm line YY
エラーが出ました。これは、requires されているメソッドのうち、writeが用意されてないと言うエラーです。ここで、HelloWorldロールでrequiresしている "println" が勝手に解決されているのが大きなポイントです。このように、requiresに指定されているものでも他のRoleが持っているメソッドがあれば勝手に解決してくれます。
では、言われた通りにMoose::Roleが欲しがっているメソッド(GLUE)である write を作りましょう。
package MyApp; use Moose; with 'HelloWorld', 'Logging'; sub write { my $self = shift; warn @_; } no Moose;
hello world! Sun Apr 5 21:10:33 2009 said 'hello'
きちんと動きました!