Moose-0.92 > Moose::Spec::Role

題名

Moose::Spec::Role - ロールの振る舞いに関する公式仕様

本文

注意: このドキュメントには現在不備があります。

ロールの構成要素

排除ロール

ロールには排除するロールの一覧が定義されていることがあります。これは基本的に合成できないロールのことです。これは直接合成できないものだけでなく、「継承」したときに合成できないものも含まれます。

この機能はFortress言語をまねたものですが、大量のロールを「ブロック」のように組み合わせていくときには本当に役に立ちます。「ブロック」の中にはどうしても組み合わせられないものもあるからです。

アトリビュート

ロールのアトリビュートはクラスのアトリビュートと似ていますが、実際にロールに適用されるわけではないところが異なります。つまり、アトリビュートアクセサが生成するメソッドは、そのロールの中で生成されるのではなく、ロールがなにかのクラスに組み込まれてから作成される、ということです。

メソッド

ロールの中で定義されるメソッドは次の通りです。簡単ですよね?

必須メソッド

ロールは、取り込む側のクラス(ないしロール)が特定のメソッドを提供していることを要求することがあります。クラスがそのメソッドを提供していない場合は致命的なエラーになります。組み込み先がロールの場合は、メソッドの要件をそのロールに引き継ぎます(致命的なエラーにはなりません)。

必須アトリビュート

ロールは、特定のメソッドを必要とすることがあるのと同様に、特定のアトリビュートを必要とすることもあります。この場合、対象となるアトリビュートは、最低でも必要とされる要件を満たしている必要があります。つまり、たとえばあるロールが読み取り専用のアトリビュートを要求している場合、そのアトリビュートは少なくとも読み取り用のアクセサを持っている必要があります(ただし、書き込み用のミューテータを持っていてもかまいません)。ロールが配列リファレンス型のアトリビュートを要求している場合、そのアトリビュートの型は配列リファレンスあるいはそのサブタイプでなければなりません。

オーバーライドされるメソッド

overridesuperはロールの中でも使えますが、その挙動はクラスの場合とは異なります。クラスのsuperは直接そのクラスのスーパークラスを指しますが、ロールのsuperは遅延評価され、クラスに合成されるまで意味を持ちません。合成されてからは、superはそのクラスのスーパークラスを指すようになります。

ロールは階層構造を持たないということはぜひ覚えておいてください。だからこそ、「スーパー」ロールを持つことはありえないのです。

メソッドモディファイア

Mooseのクラスが提供しているbeforearoundafterといったモディファイアも利用できますが、こちらも実際にロールがクラスに合成されるまでは適用されないという違いがあります(アトリビュートやoverrideの場合と同じです)。

ロールの組み込み

クラスに組み込む場合

排除ロール
必須メソッド
必須アトリビュート
アトリビュート
メソッド
オーバーライドされるメソッド
メソッドモディファイア(before、around、after)

インスタンスに組み込む場合

ロールに組み込む場合

排除ロール
必須メソッド
必須アトリビュート
アトリビュート
メソッド
オーバーライドされるメソッド
メソッドモディファイア(before、around、after)

ロールの合成

あるロールに(with @rolesを利用して)複数のロールを組み込んだ場合は対称差を利用して合成されます。このようにしてできたロールを合成ロールといいます(Moose::Meta::Role::Composite)。

排除ロール
必須メソッド
必須アトリビュート
アトリビュート

同じ名前を持つアトリビュートが複数ある場合は衝突を起こして回復不能なエラーとみなされます。アトリビュートのほかの要素は考慮されません。アトリビュート名が衝突しているだけで十分です。

このようにアトリビュートの衝突判定が非常に早く、厳しいものになっているのは、アトリビュートは変動の幅が大きいのですぐに収拾がつかなくなってしまうためです。また、判定のルールも非常に煩雑なものになってしまいますし、そのような手間をかけるだけの価値はないと筆者は考えています。

メソッド

同じ名前を持つメソッドが複数ある場合は衝突を起こしますが、エラーにはなりません。そのかわりにそのメソッド名が新しくできた合成ロールの「必須」メソッドのリストに追加されます。

これを集合論の観点から見ると、それぞれのロールはメソッドの集合を持つことができると言えます。そして、この2つの集合の対称差が新たに合成ロールのメソッドの集合になり、2つの集合の積集合が衝突になります。例を示すと、このようになります。

   Role A has method set { a, b, c }
   Role B has method set { c, d, e }

   The composite role (A,B) has
       method   set { a, b, d, e }
       conflict set { c }
オーバーライドされるメソッド

オーバーライドされたメソッドが衝突を起こす場合は2通りあります。

まず、同じ名前を持つ別のオーバーライドされたメソッドがある場合。これは回復不能なエラーとみなされます。これがエラーになるのは自明でしょう。同じクラスの中でひとつのメソッドを2度オーバーライドすることはできません。

2つめは、オーバーライドされたメソッドと通常のメソッドが同じ名前を持つ場合です。これも回復不能なエラーです。この2つのメソッドを合成する方法はありませんし、両者をどこかでひとつのクラスに合成することもできないからです。

ロールの中でオーバーライドするのはトリッキーですが、気をつけて使えば非常に強力なツールになることもあります。

メソッドモディファイア(before、around、after)

メソッドモディファイアだけはロールを合成するときでも順序を気にしなければなりません。これはメソッドモディファイアそのものの性質のためです。

メソッドは複数のメソッドモディファイアを持つことがあるため、ロールを組み込む段階ではモディファイアは適用されません。クラスに組み込んだときに同じ順番で適用できるよう収集されるだけです。

一般論として、ロールの中でメソッドモディファイアを使うときは細心の注意を払ってください。このように順序の問題があるため、使いすぎると微妙で見つけづらいバグのもとになることもあります。人生なにごとも節度が肝心です。

特殊な組み込み例

ここでは混乱しやすい複雑な特殊をいくつか紹介します。これは問題を明確にして、中でなにが起こっているを説明しようというものです。

ロールメソッドのオーバーライド

取り込もうとしているロールのメソッドを「オーバーライド」したがる人は多いのですが、これは相手がクラスであればうまくいくものの(ローカルなクラスメソッドの方がロールのメソッドより優先されるためです)、ロールが相手の場合はかなりトリッキーなことになります。メソッドの衝突が起こると、どちらのメソッドも組み込まれず、「必須」メソッドになってしまうのです。

この(正しくない)オーバーライドの例をあげます。

    package Role::Foo;
    use Moose::Role;

    sub foo { ... }

    package Role::FooBar;
    use Moose::Role;

    with 'Role::Foo';

    sub foo { ... }
    sub bar { ... }

この場合、fooメソッドが衝突を起こしていますので、Role::FooBarは取り込む側のクラスやロールにfooの実装を要求します。これはたいていの場合期待した動作ではないはずです。

こちらは(正しい)オーバーライドの例です。もっとも、これも次に説明するように、オーバーライドはいっさい起こっていません。

    package Role::Foo;
    use Moose::Role;

    sub foo { ... }

    package Role::Bar;
    use Moose::Role;

    sub foo { ... }
    sub bar { ... }

    package Role::FooBar;
    use Moose::Role;

    with 'Role::Foo', 'Role::Bar';

    sub foo { ... }

これが動作するのは、Role::FooとRole::Barを合成するとfooが衝突するため、fooが(withを使って合成されたRole::FooとRole::Barの)合成ロールの必須メソッドになり、その要件をRole::FooBarが満たすからです。

大事なのは、Role::FooBarは単にfooメソッドの要件を満たしているだけで、fooをオーバーライドしている「わけではない」ということ。これは大きな違いになります。

さらにもうひとつ(正しい)オーバーライドの例を見ましょう。今度はexcludesオプションを使います。

    package Role::Foo;
    use Moose::Role;

    sub foo { ... }

    package Role::FooBar;
    use Moose::Role;

    with 'Role::Foo' => { excludes => 'foo' };

    sub foo { ... }
    sub bar { ... }

合成するときにfooメソッドを明示的に排除すると、Role::FooBarに自前のfooを定義できるようになります。

参照

トレート(Traits)

ロールはSmalltalkコミュニティで生まれたトレートが元になっています。

http://www.iam.unibe.ch/~scg/Research/Traits/

ここは元になったトレート論文のメインサイトです。

Class::Trait

私が数年前に上でリンクした論文を読んでトレートを実装したものです(このモジュールは現在Ovidがメンテナンスしています。私はもう関与していません)。

ロール

ロールという概念は比較的新しいものですし、おそらくMooseの実装がもっとも成熟しているので、あまりリンクするべきところもないのですが、多少なりとも見る価値があるものを紹介します(ほとんどはPerl 6関連のものです)。

http://www.oreillynet.com/onlamp/blog/2006/08/roles_composable_units_of_obje.html

これはchromaticによるロールの解説です。彼は中心的な旗振り役のひとりでした(です)から、一読の価値はあります。

http://svn.perl.org/perl6/doc/trunk/design/syn/S12.pod

このPerl 6の概要第12章ではPerl 6のオブジェクトシステムの全貌が語られているのですが、もちろんここにはロールについての話も含まれています。

作者

Stevan Little <stevan@iinteractive.com>

コピーライト & ライセンス

Copyright 2007-2009 by Infinity Interactive, Inc.

http://www.iinteractive.com

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.