Moose & Mouse基本文法最速マスター/The Fastest Way to Mastering Moose & Mouse

| 0 Comments | 0 TrackBacks | このエントリーをはてなブックマークに追加 このエントリーのはてなブックマーク件数

Moose & Mouse基本文法最速マスター/The Fastest Way to Mastering Moose & Mouse

はじめまして。gardejoこと守屋と申します。

この文書はPerlの拡張モジュールMooseによるポストモダンオブジェクト指向プログラミングの文法や作法などを簡易的にまとめたものです。Perl5の基本文法に習熟していて、かつ、言語を問わず一般的なオブジェクト指向についての知識がある人を想定読者としています。

内容はMooseのバージョン0.98に基づきます。Mouseのバージョン0.50_01時点の内容も付記しています。

誤っている点, ご不明な点, 冗長すぎてかえって初学者を混乱させかねないため削除すべき点などがありましたら、コメントやトラックバックなどでお寄せいただければ幸いです。適宜改訂します。

目次/Table of Contents

クラス/Classes

package My::Class;
use Moose;

パッケージをMooseに基づいたクラスにします。

use URI;                            # Mooseに基づかないクラスのロード
my $uri = URI->new;                 # クラスのインスタンスの生成

use My::Class;                      # Mooseに基づいたクラスのロード
my $my_instance = My::Class->new;   # クラスのインスタンスの生成

Mooseに基づいたクラスは外部からは(Mooseクラスであることを意識せずに)伝統的なPerl5クラスと同様に取り扱えます。

プラグマの有効化とシュガー関数のインポート/Turning on Pragmas and Importing Sugar Functions

use Moose;すると、strictwarningsプラグマが有効になる他、クラスを設計するための構文糖/syntax sugarである各種のシュガー関数/sugar functionsCarp::confess(), Scalar::Util::blessed()がインポートされます。

アトリビュート/Attributes

クラスは複数のアトリビュート/attributesを持つことができます。これはクラスの状態/stateを示すものです。伝統的なPerl5クラスに於いてはblessされたハッシュに相当します。C++やJavaのメンバー変数に相当するものです。

cf. has

なお、Perl 5.6.0以降の「サブルーチンアトリビュート」/subroutine attributesのことではありません。

メソッド/Methods

クラスは複数のメソッド/methodsを実装することができます。これはクラスの振る舞い/behaviorを示すものです。

sub foo { my ($self, @args) = @_; ... }

メソッドは通常のPerl5構文の通りに記述します。

スーパークラス/Superclasses

クラスは複数のスーパークラス/superclassesを継承/inheritすることができます。

cf. extends

Mooseクラスを継承する場合、MooseX::NonMooseを利用することを推奨します。

ロール/Roles

クラスは複数のロール/rolesを消費/consumeすることができます。

cf. with, ロール/Roles

ロール/Roles

package My::Role;
use Moose::Role;

パッケージをMoose::Roleに基づいたロールにします。

ロールとは、「APIで何ができるか(does)」に着目した実装方法です。スーパークラスの継承/inheritanceによる、「クラスが何であるか(isa)」に着目した実装方法よりも柔軟に、アトリビュート, インターフェース, メソッドを実装できます。

クラスにメソッドが生えていることを要求するrequiresはJavaのインターフェース/interfacesと同様の概念です。ただし、Mooseのロールにはアトリビュートや実際のメソッドも実装できるところが違いです。

ロールにメソッドを定義して、そのロールをクラスで消費することで、クラスにメソッドが生えているように取り扱えます。これはRubyのミックスイン/mix-inと同様の概念です。

これにより、継承ツリーに悩まされることなくオブジェクト指向プログラミングを行えますので、lestrratさん「オブジェクト指向の革命」だと宣言しています。

有効化されるプラグマやエクスポートされるシュガー関数はuse Moose;の場合とほぼ同じです。extendsはクラスでのみ使え、requiresはロールでのみ使えます。

cf. with, requires

シュガー関数/Sugar Functions

MooseクラスとMooseロールで使用可能なシュガー関数は以下の通りです。

アトリビュートの設定/Attribute Installation

has $attribute_name => %option;

クラスにアトリビュート/attributesを設定するシュガー関数。

cf. アトリビュート構築オプション/Attribute Constructor Options

has [ @attribute_names ] => %option;

複数のアトリビュートをまとめて設定することもできます。

has "+$attribute_name" => %option;

継承したスーパークラスや消費したロールに存在するアトリビュートの設定を拡張します。

ただし、むやみな上書きは(特にisaによる型制約の上書きは)クラスやロールのAPIを崩すので、避ける方が賢明とされています。

クラスの継承/Class Inheritance

extends @superclasses;

クラスにスーパークラス/superclassesを継承/inheritさせるシュガー関数。

伝統的なPerl5オブジェクト指向プログラミングに於けるuse base @superclasses構文の代わりに使います。@ISAを再設定しないことがuse baseとの違いです。

このシュガー関数はクラスにしか記述できません。つまり、ロールには記述できません。

ロールの消費/Consuming Roles

with @roles;

クラスにロール/rolesを消費/consumeさせるシュガー関数。

ロールをwithした場所に当該ロールのソースをコピー&ペーストしたかのように動作します。

ロールの中で別のロールをwithすることもできます。

cf. ロール/Roles

with $role => { %option };

ロールの消費時にオプションを与えることもできます。

インターフェース/Interfaces

requires @methods;

ロールをwithで消費/consumeするクラスに於いて、@methodsのメソッドが呼び出せることを要求するシュガー関数。

このシュガー関数はロールにしか記述できません(クラスには記述できません)。

cf. ロール/Roles

メソッドモディファイヤー/Method Modifiers

before @method_names => sub { my ($self, @args) = @_; ... };
after  @method_names => sub { my ($self, @args) = @_; ... };

継承したスーパークラスや消費したロールに存在するメソッドを修飾するシュガー関数。修飾されたメソッドの前(before)や後(after)に動かす処理をコードリファレンスで定義します。

beforeのコードリファレンス内では修飾されたメソッドへの引数が、afterのコードリファレンス内では修飾されたメソッドからの戻り値が、それぞれ無視されます。それらを編集したい場合にはaroundを使ってください。

around @method_names => sub { my ($next, $self, @args) = @_; ...; my @retun_values = $self->$next(@args); ... };

メソッドを修飾するシュガー関数の一つ。修飾されたメソッドの前後で動かす処理をコードリファレンスで定義します。

コードリファレンスからは修飾されたメソッドを$nextで呼べます。修飾されたメソッドの引数と戻り値はコードリファレンス内で編集することもできます。$nextを呼ばなければ修飾されたメソッドは呼ばれません。

before qr{ $method_name_pattern } => sub { ... };   # Moose 0.45+, Mouse 0.50_01+, only in a class
around [ @method_names ] => sub { ... };            # Moose 0.95+

上記のように修飾するメソッド名を指定することもできます。正規表現はdannさんの素敵なパッチによるものです。

before 2, before 1,
    around 2, around 1,
        wrapped method,
    around 1, around 2,
after 1, after 2

修飾されたメソッドはbefore, around, afterの順番に修飾されます。複数修飾した場合には、修飾されたメソッドに「近い」ものからクラス継承順やロール修飾順に修飾されます。

override $method_name => sub { my ($self, @args) = @_; ...; super(); ...; };

メソッドを修飾するシュガー関数の一つ。継承したスーパークラスに存在するメソッドを明示的にオーバーライド/overrideします。消費したロールで実装されているメソッドはoverrideできません。

コードリファレンスからは修飾されたメソッドをsuper()で呼べます。その際の引数は不要です。修飾されたメソッドへも($self, @args)の引数が渡ります。$self->SUPER::method(@args);とほぼ同様ですが、@argsを編集できない点が異なります。

伝統的なPerl5オブジェクト指向プログラミングのように、sub $method_name { ... }のようにスーパークラスやロールにあるメソッドと同名のメソッドを単に実装しても暗黙的にオーバーライドできます。

augment $method_name => sub { my ($self, @args) = @_; ...; inner(); ...; };

メソッドを修飾するシュガー関数の一つで、overrideおよびsuper()と逆の動作をします。

コードリファレンスからはサブクラス/subclassesのメソッドをinner()で呼べます。引数の取り扱いもaroundに於けるsuper()と同様です。

保守開発でさらなるサブクラスを作成しうることに鑑みると、最末端クラスでもinner()を呼んでおくと手堅いです(最末端でのinner()は何も処理しません)。

アトリビュート構築オプション/Attribute Constructor Options

has $attribute_name => %optionのオプションについて、is以外の指定は任意です(Moose 0.84未満のバージョンでは、全てのオプションが任意です)。また、一部のオプションは排他指定であったり、別のオプションを一緒に指定する必要があります。

なお、未知の(綴りを間違えた)キーを指定すると、警告が出ます(Moose 0.84以降, Mouse 0.50_01以降)。

MooseX::*拡張モジュールなどにより、アトリビュートのオプションも拡張できます。

cf. Mooseアトリビュートのオプション指定順についての私案 : あくまでネタです。

アトリビュートの拡張/Attribute Extension

metaclass => $metaclass_name

アトリビュートをメタクラス/metaclassesによって拡張します。$metaclassは一つしか指定できません。

完全修飾パッケージ名Moose::Meta::Attribute::Custom::$metaclass_name$metaclass_nameの文字列を指定します。

traits => [ @trait_names ]

アトリビュートを@trait_namesのトレート/traitsによって拡張します。

完全修飾パッケージ名Moose::Meta::$kind::Custom::Trait::$trait_name$trait_nameの文字列を指定します。

Moose 0.89_01以降ではArrayなどのネイティブトレートが組み込まれています。

アクセッサーメソッドの生成/Accessor Methods Generation

is => 'rw'
is => 'ro'
is => 'bare'

読み書き両用(rw: read-write)または読み取り専用(ro: read only)のアクセッサーメソッド/accessor methodをクラスへ生成します。メソッド名は$attribute_nameと同じです。

isオプションがないとアクセッサーメソッドは生成されません。Moose 0.84以降では警告が出るので、明示的にis => 'bare'を指定する必要があります。

isはアクセッサーメソッドの生成のみを司っています。アトリビュートに「書き込み禁止フラグ」のようなものがあるわけではありません。従って、is => 'ro'としてもwriterで書き込みメソッドを別途指定すれば、アトリビュートへの書き込み手段が存在することになります。

クラス外部へ晒す内部実装は少ない方がよいため、必要に迫られない限りは'ro''bare'と指定しておくと手堅いです。

accessor => $accessor_method_name

メソッド名を明示的に指定して、アクセッサーメソッドをクラスへ生成します。引数なしでは読み取りメソッドとして働き、引数ありでは書き込みメソッドとして働きます。is'rw'でなくてはなりません。

reader => $reader_method_name

メソッド名を明示的に指定して、読み取りメソッド/reader methodをクラスへ生成します。

writer => $writer_method_name

メソッド名を明示的に指定して、書き込みメソッド/writer methodをクラスへ生成します。

is'ro'と指定しても、writerを指定した場合には、書き込みメソッドが存在することになります。has 'foo' => (is => 'ro', writer => '_foo')で、書き込みメソッド名を(先頭にアンダースコア_を付けることによって)慣習的隠蔽状態にするなどの利用法があります。init_argも参照してください。

なお、『Perlベストプラクティス』風にreader => 'get_foo', writer => 'set_foo'のように指定するにはMooseX::FollowPBPを使います。また、reader => 'foo', writer => 'set_foo'の指定はMooseX::SemiAffordanceAccessorで代替可能です。

型制約と型変換/Type Constraints and Coercions

isa => $type_constraint
isa => "$some_type_constraint | $other_type_constraint"

指定した型制約にアトリビュートの値が適合していることを要求します。doesとは排他指定です。

cf. 型制約/Type Constraints

does => $role_name

アトリビュートの値であるオブジェクトのクラスが$role_nameのロールを消費していることを要求します。isaとは排他指定です。

cf. with, ロール/Roles

coerce => $bool

真の場合、isaで指定した型制約にアトリビュートの値が適合しない時に型変換を試みます。weak_refと排他指定です。

cf. 型変換/Type Coercions

weak_ref => $bool

真の場合、アトリビュートの値(リファレンス)を「弱いリファレンス/weak reference」にします。coerceと排他指定です。

これは循環参照/circular refearenceを防止する用途に使われます。

auto_deref => $bool

真の場合、読み取りメソッドの戻り値を自動的にデリファレンス/dereferenceします。isaの指定が必須です。

Perlデータ構造体への委譲を行う方が手堅いです。

コンストラクター引数/Constructor Arguments

init_arg => $argument_key
init_arg => undef

コンストラクターnewの引数(正確にはBUILDARGSメソッドの戻り値)のハッシュキー名を明示的に指定します。

オプション無指定時には$attribute_nameと同じキー名を取ります。undefを指定すると、コンストラクター引数を取りません。

required => $bool

アトリビュートに値が設定されていることを要求します。

コンストラクターnewの引数(正確にはBUILDARGSメソッドの戻り値)のハッシュにinit_argで指定されたキー(init_argが未指定ならアトリビュートと同名のキー)が存在していなければ、実行時に例外が送出され、インスタンスを生成できません。

cf. コンストラクターのフック

lazylazy_buildと一緒に指定すると、例外は送出されませんが意図した動作となりません。遅延設定の方が優先され、newに引数を渡さない場合でも例外が送出されません。

なお、ロールのシュガー関数requiresと混同しないように気を付けてください。

値の遅延設定と設定状況/Lazy Building, Predication and Cleaning

lazy => $bool

真の場合、アトリビュートを遅延設定します。

コンストラクターnewの引数へ(正確にはBUILDARGSメソッドの戻り値で)当該アトリビュートを指定せずにインスタンスを生成した場合、遅延設定されたアトリビュートの値は未設定状態となっています。また、一度設定されたアトリビュートの値を消去用メソッド/clearer methodsで消去した場合も未設定状態となります。

こうしたアトリビュートの値が未設定状態の場合、次にそのアトリビュートにアクセスされた時点でデフォルト値が設定されます。

遅延設定する場合、アクセス時に遅延設定されるデフォルト値として、defaultまたはbuilderの指定が必須です。

lazy_build => $bool

真の場合、lazy => 1, predicate => "has_$attribute_name", clearer => "clear_$attribute_name", builder => "_build_$attribute_name"と同じ指定になります。

predicate => $predicater_method_name

アトリビュートに値が設定されていれば真を返す断定用メソッド/predicator methodを、指定したメソッド名でクラスへ生成します。

なお、undefという値が設定されていても、真が返ります。値が設定されていない状態と、値に未定義値undefが設定されている状態は異なるからです。伝統的なハッシュベースのPerl5クラスでは、if exists $self->{foo}に相当します。

clearer => $clearer_method_name

アトリビュートの値を未設定状態にする消去用メソッド/clearer methodを、指定したメソッド名でクラスへ生成します。

triggerと組み合わせると、アトリビュートが絡み合うクラスを簡潔に実装できます。

cf. Mooseのtriggerの効果的な使い方 ~ lazyとclearerとの併用

デフォルト値/Default Values

default => $default_value
default => sub { my ($self) = @_; ... }

遅延設定時のデフォルト値を指定します。

デフォルト値をリファレンスにしたい場合、sub { [ ... ] }などのようにコードリファレンス内で指定します。直接指定するとコンパイル時に例外が送出されます。

全インスタンスで参照先の値(リファレント)を共有させたい場合、my @foo = (); ... has bar => ( is => 'ro', default => sub { \@foo } );my $foo = []; .... has bar => ( is => 'ro', default => sub { $foo } )のように、コードリファレンスの外で生成した値をコードリファレンス内で使ってください。

builder => $builder_method_name

デフォルト値を返すビルダーメソッド/builder methodsのメソッド名を指定します。

default => \&builder_method_nameの指定とほぼ同様です。

ロールやサブクラスでビルダーメソッドを実装したり上書きしたりできるので、defaultよりも好ましい指定です。

builderを直接またはlazy_build経由で指定した場合、当該クラス, クラス継承ツリー, 消費ロール群のいずれにもビルダーメソッドが存在しないと、デフォルト値を設定するタイミングで例外が送出されます(コンパイル時ではありません)。

トリガー/Triggers

trigger => sub { my ($self, $new_value) = @_; ... }

アトリビュートに値が設定された後に実行するコードリファレンスです。

書き込みメソッド実行後の他、コンストラクターnewの引数で値を指定した場合にも動作します。ただし、デフォルト値が設定された場合には動作しません。

委譲設定/Delegation Setting

handles => [ @delegated_method_names ]
handles => { $delegating_method_name => $delegated_method_name }
handles => { $curried_method_name => [ $delegated_method_name, @args ] }
handles => qr{ $pattern_of_delegated_method_names }
handles => $role_name
handles => sub { ... }

アトリビュートの値(オブジェクトなど)に対する委譲メソッドを、クラスへ設定します。

isaの指定が必須です。

cf. 委譲/Delegation

文書化/Documentation

documentation => $documentation_string

アトリビュートを説明する文書を指定します。has 'ssn' => (is => 'ro', documentation => 'Social Security Number');など。

型/Types

型制約/Type Constraints

package Foo;
use Moose;
has bar => ( is => 'rw', isa => 'Int' );

package main;
use Foo;
my $foo = Foo->new(bar => 42);
$foo->bar('baz');               # 'baz'は文字列(整数以外)なので例外を送出

isaで型制約を指定します。ただし、Mooseの型制約はPerl6にあるような「本物の」型ではありません。コンパイル時ではなく実行時に(アトリビュートに値が設定された時点で)型チェックされます。

組み込みの型制約/Build-in Type Constraints

Any : 制約なし
Maybe[TypeName] : UndefかTypeName
Item : 制約なし
    Bool : 真偽値
    Undef : 未定義(取扱注意)
    Defined : 真値
        Value : プリミティブ値
            Num : 数値('NaN', 'Inf', '0 but true'などはStr型)
                Int : 整数
            Str : 文字列
                RoleName : ロード済みのロール名(Moose 0.84+では非推奨)
                ClassName : ロード済みのクラス名
        Ref : リファレンス
            ScalarRef : スカラーリファレンス
            ArrayRef or ArrayRef[TypeName] : 配列リファレンス
            HashRef or HashRef[TypeName] : ハッシュリファレンス
            CodeRef : コードリファレンス
            RegexpRef : 正規表現リファレンス
            GlobRef : グロブリファレンス
                FileHandle : ファイルハンドル
            Object : オブジェクト
                Role : ロール

Moose組み込みの型制約は型階層/type hierarchyをなしています。例えばStr型制約を満たす文字列値'foo'Value型制約も満たします。

パラメーター化された型/Parameterized Types

上記の[TypeName]は、データの中身などについて型をTypeNameに制約する指定です。ドキュメントには[`a]などと記載されています。例えばArrayRef[Int]は「全要素の値がInt型制約を満たす配列」という制約を意味します。

自動生成された型/Automatically Created Types

isa => 'DateTime'

未知の文字列はクラス名とみなされて型が自動生成されます。

型結合/Type Unions

isa => 'Str | ArrayRef'

StrまたはArrayRef」の制約を意味します。

ただし、独自の型を新設して型変換/coerceする方が手堅いです。例えばクラス内部でStrArrayRefに異なった処理を実装するよりも、アトリビュートに値を入れる時点でStrを1要素だけ持つArrayRefにまとめてしまった方が簡潔です。

独自の型の定義/To Define Your Own Types

use Moose::Util::TypeConstraints;

型制約や型変換用のシュガー関数をインポートします。

type $type_name
    => where { ... }
    => message { ... };

独自の型制約を新設します。

whereブロックでは、アトリビュートに与えられた値$_を使って、それが型制約に合致するか否かを示す真偽値を返します。

messageブロックの記述は任意です。whereが偽であった場合のconfess用エラーメッセージを文字列で返せます。$_も使えます。

subtype $sub_type_name
    => as $parent_type_name
    => where { ... }
    => message { ... };

既存の型のサブタイプ/subtypesを新設します。

asで親のタイプ(スーパータイプ)を指定します。新設したサブタイプは、親のタイプの制約と、whereブロック内の制約を共に満たす必要があります。

whereブロックを記述しない場合、親のタイプと全く同じ制約が適用されます。

enum $enum_name => @values;

列挙型/enumerated typeの指定です。

subtype $sub_type_name
    => as class_type 'SomeClassName';

クラス用の型を明示的にサブタイプ化する指定です。

型の自動生成は、実際にはこの構文と同じ動作をします。

type 'My::App::Types::Foo'
    ...

型はグローバルな名前空間に登録されるので、自分の型は自分用の名前空間に所属させることが推奨されています。

なお、MooseX::Types::*名前空間には、様々な型制約・型変換モジュールが登録されています。

型変換/Type Coercions

coerce $type_name
    => from $some_type_name
        => via { ... }
    => from $other_type_name
        => via { ... };

$some_type_nameなどの型に合致する場合に、viaブロック内で$type_nameへの型変換を試みる指定です。viaブロックではアトリビュートに与えられた値$_を変換して、型制約に合致する値を返します。

型を適用するアトリビュートをhasする際にcoerce => 1することも忘れずに。

subtype 'My::App::StrOrHashToArray'
    => as 'ArrayRef';
corece 'My::App::StrOrHashToArray'
    => from 'Str'
        => via { [$_] }
    => from 'Hash'
        => via { [%$_] };

StrHashからArrayRefに変換する例です。

型はグローバルな名前空間に登録されるので、組み込みの型や自動生成される型はそのままcoerceせず、必ずsubtype化してからcoerceしましょう。

MooseX::Types

MooseX::Typesを使うと、名前空間を自動で掘ったり、型を裸のワード/raw wordsで呼べたりできます。

MooseX::TypesのAPIはMoose::Util::TypeConstraintsとほぼ同様ですが、裸のワードを使う都合上、(左辺値を暗黙的にクォートする)ファットカンマ(=>)ではなく、シンカンマ(,)を使うことになります。

委譲/Delegation

委譲はhandlesで設定します。

オブジェクトへの委譲/Delegation to an Object

has datetime => (
    is      => 'rw',
    isa     => 'DateTime',
    handles => [qw(ymd hms)],
);

DateTimeインスタンスのメソッドへの委譲を行う例。$my_instance->ymdで、$my_instance->datetime->ymdと同じ処理を実現できます。

has datetime => (
    is      => 'rw',
    isa     => 'DateTime',
    handles => {
        yyyymmdd => 'ymd',
        hhmmss   => 'hms',
    },
);

handlesにハッシュリファレンスを指定した場合、クラスに生やすメソッド => 委譲先のメソッドという構文となります。$my_instance->yyyymmddで、$my_instance->datetime->ymdと同じ処理を実現できます。

メソッドのカリー化/Method Currying

has datetime => (
    is      => 'rw',
    isa     => 'DateTime',
    handles => {
        yyyymmdd => [ 'ymd' => ( ''  ) ],
        hhmmss   => [ 'hms' => ( '_' ) ],
    },
);

委譲先のメソッドをカリー化/curryingできます(Moose 0.89_01以降, Mouse 0.50_02以降)。

上記の例では、$my_instance->yyyymmdd$my_instance->datetime->ymd('')と同じ処理を実現でき、デリミターなしのYYYYMMDD形式の文字列を得られます。また、$my_instance->hhmmss$my_instance->datetime->hms('_')と同じ処理を実現できます。

Perlの通常データ構造体への委譲/Delegation to Common Perl Data Structures

Moose 0.89_01以降には、ネイティブトレート/native traitsが組み込まれています。

has friends => (
    traits  => [qw(Array)],
    is      => 'rw',
    isa     => 'ArrayRef',
    handles => {
        all_friends  => 'elements',
        sort_friends => 'sort_in_place',
    },
);

配列用ネイティブトレートArrayを使った委譲の例。(オブジェクトではない)単なる配列リファレンスに対するヘルパーメソッドを生成しています。この場合、$my_instance->all_friends@{ $my_instance->friends }と同じ処理を実現できます。これはauto_deref => 1として$my_instance->friendsで配列を得るよりも手堅い手法です。

この他にも、よく使う関数をヘルパーメソッドとして使えます。

$my_instance->friends( [ sort @{ $my_instance->friends } ] );

例えば上記の処理は

  1. 配列リファレンスとしての値をアトリビュートから得て
  2. デリファレンスして
  3. リファレントである配列をsortして
  4. その配列をリファレンスとしてアトリビュートの値に戻す

という手間を掛けていますが、これを

$my_instance->sort_friends;

だけで済ませられます。つまり、内部実装(friendsが配列リファレンスであること)を隠蔽できて、クラスを使用する側も余計なことを考えなくて済むようになります。

MooseX::AttributeHelpers

この機能の取込元であるMooseX::AttributeHelpersでもほぼ同様の機能を実現できますが、Moose本体への取込に伴い、現在は非推奨/deprecatedとなっています。

Arrayネイティブトレートは、MooseX::AttributeHelpersCollection::Arrayメタクラスに相当します。

また、Mouse0.50_01時点ではMoose相当のネイティブトレートが存在しないので、MouseX::AttributeHelpersを使います。または、ネイティブトレート相当の使用感があるMouseX::NativeTraitsを使います。

ネイティブトレートによるヘルパーメソッド/Helper Methods from Native Traits

数値/Number
set($new_number)                    # $number = $new_number
add($adding_number)                 # $number += $adding_number
sub($subtracting_number)            # $number -= $subtracting_number
mul($multiplying_number)            # $number *= $multiplying_number
div($deviding_number)               # $number /= $deviding_number
mod($deviding_number)               # $number %= $deviding_number
abs()                               # $number = abs $number
カウンター/Counter
set($new_count)                     # $count = $new_count
inc()                               # $count ++
dec()                               # $count --
reset()                             # $count = $default_value
文字列/String
inc()                               # $string ++ (from 'a' to 'b', dec() is not available)
append($other_string)               # $string .= $other_string
prepend($other_string)              # $string = $other_string . $string
replace($pattern, $replacement)     # $string =~ s/$pattern/$replacement/ ($pattern accepts qr{}xmsi)
match($pattern)                     # $string =~ m/$pattern/ ($pattern accepts qr{}xmsi)
chop()                              # chop $string
chomp()                             # chomp $string
clear()                             # $string = q{}
length()                            # length $string
substr(@args)                       # substr $string, $offset, $length, $replacement
真偽値/Bool
set()                               # $bool = 1
unset()                             # $bool = 0
toggle()                            # from 1 to 0, from 0 to 1
not()                               # not $bool
ハッシュリファレンス/Hash
get(@keys)                          # $hashref->{$key}, @$hashref{@keys}
set(%keys_and_values)               # $hashref = { $key => $value, ... }
delete(@keys)                       # delete $hashref->{$key}, delete @$hashref{@keys}
keys()                              # keys %$hashref
exists($key)                        # exists $hashref->{$key}
defined($key)                       # defined $hashref->{$key}
values()                            # values %$hashref
kv()                                # key/value pairs as an array of array references
elements()                          # key/value pairs as a flattened list
clear()                             # %$hashref = ()
count()                             # scalar keys %$hashref
accessor($key)                      # get($key)
accessor($key, $value)              # set($key => $value)
配列リファレンス/Array
count()                             # scalar @$arrayref
is_empty()                          # if scalar @$arrayref
elements()                          # @$arrayref
get($index)                         # $arrayref->[$index]
pop()                               # pop @$arrayref
push(@values)                       # push @$arrayref, @values
shift()                             # shift @$arrayref
unshift(@values)                    # unshift @$arrayref, @values
splice($offset, $length, @values)   # splice @$arrayref, $offset, $length, @values
first(sub { ... })                  # List::Util::first { ... } @$arrayref
grep(sub { ... })                   # grep { ... } @$arrayref
map(sub { ... })                    # map { ... } @$arrayref
reduce(sub { ... })                 # List::Util::reduce { ... } @$arrayref (Moose 0.89_02+)
sort()                              # sort @$arrayref
sort(sub { $_[0] cmp $_[1] })       # sort { $a cmp $b } @$arrayref
sort_in_place()                     # @$arrayref = sort @$arrayref
sort_in_place(sub { ... })          # @$arrayref = sort { ... } @$arrayref
shuffle()                           # List::Util::shuffle @$arrayref (Moose 0.89_02+)
uniq()                              # List::MoreUtils::uniq @$arrayref (Moose 0.89_02+)
join($string)                       # join $string, @$arrayref
set($index, $value)                 # $arrayref->[$index] = $value
delete($index)                      # delete $arrayref->[$index]
insert($index, $value)              # splice @$arrayref, $index, 0, $value
clear()                             # @$arrayref = ()
accessor($index)                    # get($index)
accessor($index, $value)            # set($index, $value)
natatime($n, $code)                 # $i = List::MoreUtils::natatime $n, @$arrayref; while (@v = $i->()) { $code->(@v) } (Moose 0.89_02+)
コードリファレンス/Code

Moose 0.90以降の機能です。

execute(@args)                      # &$coderef(@args)
execute_method(@args)               # $invocant->$coderef(@args)

インスタンスの生成と破棄/Construction and Destruction of Instances

Mooseに基づくクラスのインスタンスはMy::Class->new;で生成します。

Perl5の素のオブジェクト指向で作ったインスタンスと同様、インスタンスの破棄は参照カウント/reference count0になった時に行われます。

コンストラクターのフック/Hooks into Constructor

sub BUILDARGS { my ($class, @init_args) = @_; ... }

MooseクラスやMooseロールでは、コンストラクターであるクラスメソッドnewを上書きしてはなりません。ただし、BUILDARGSクラスメソッドをフックすることで、newへ渡る引数を編集することができます。

ハッシュかハッシュリファレンスを要求するnewに対して、配列やスカラー値なども受け容れるようにしたり、引数を編集したりするために使えます。

sub BUILDARGS {
    my ($class, @init_args) = @_;

    if (@init_args == 0 && ! ref $init_args[0]) {
        return { foo => $init_args[0] };
    }
    else {
        return $class->SUPER::BUILDARGS(@init_args);
    }
}

BUILDARGSではフォールバックとしてスーパークラスのBUILDARGSを呼ぶことが推奨されています(Mooseに基づくクラスは内部的にはMoose::Objectを継承しているので、何もextendsしていないクラスでもSUPERがあります)。

around BUILDARGS => sub {
    my ($next, $class, @init_args) = @_;

    if (@init_args == 0 && ! ref $init_args[0]) {
        return { foo => $init_args[0] };
    }
    else {
        return $class->$next(@init_args);
    }
};

サブクラスやロールでさらに細かい処理をする場合もあるので、BUILDARGSaroundメソッドモディファイヤーで修飾すると便利です。

sub BUILD { my ($self, @init_args) = @_; ... }

インスタンス生成後の後処理を実装します。$self->SUPER::BUILDを呼んではいけません。

この時点では既にインスタンスは生成されています。従って、isarequiredなどへの対策はBUILDARGSで実装する必要があります。

BUILDの戻り値は無視されます(newの戻り値は$selfとなります)。

デストラクターのフック/Hooks into Destructor

sub DEMOLISH { my ($self, $arg) = @_; ... }

MooseクラスやMooseロールでは、デストラクターであるメソッドDESTROYを上書きしてはなりません。代わりにDEMOLISHクラスメソッドをフックすることで、デストラクターの起動のタイミングで任意の処理を実行することができます。

$self->SUPER::DEMOLISHを呼んではいけません。

その他の内容の触り/Other Tidbits

名前空間の汚染への対策/Measures against Namespace Pollution

Moose関連シュガー関数は、use Mooseしたパッケージの名前空間を汚染します。つまり、そのままでは外部からMy::Class->hasなどで呼ばれてしまいます。

no Moose;

use Mooseした後、シュガー関数を使い終わったら、Moose関連シュガー関数をアンインポートします。

Moose::RoleMoose::Util::TypeConstraintsuseした場合は、それらも同様にnoしてください。

use namespace::autoclean;

namespace::autocleanモジュールで名前空間を掃除することも可能です。

use namespace::clean -except => [qw(meta)];

上記のnamespace::cleanによる代替です。use Mooseした後にuse namespace::cleanすることと、(use Mooseの場合は)metaを掃除対象外にとすることに留意してください。

メタオブジェクトプロトコルとClass::MOP/Meta Object Protocol and Class::MOP

MooseはMOP(メタオブジェクトプロトコル/Meta Object Protocol)に基づくオブジェクト指向プログラミング機構を提供するClass::MOPのラッパーです。

例えば、シュガー関数はClass::MOPの各種メソッドを簡潔にしたものです。

メタクラス/Metaclasses

use Moose;すると、当該パッケージに紐付くメタクラス/metaclassesオブジェクトが生成され、かつ、Moose::Objectが自動的に継承されます。

「クラスにはアトリビュートがある」や「クラスへメソッドを生やす」などと書いてきましたが、実際にはこのメタクラスオブジェクトに対して内部的な操作を行っています。

これらはMooseホームページClass::MOP object model diagramで図解されています。

この関係はRubyのクラスが実際にはクラスオブジェクトであることと似ています。

イントロスペクション/Introspection
my $meta = __PACKAGE__->meta;

パッケージに紐付くメタクラスを取得します。

アトリビュートやメソッドや消費ロールなど、Moose::Meta::*にあるような様々な情報を実行時に参照および編集できます。

メタクラスの不変化による高速化/Speed Up by Immutabilizing Metaclasses

__PACKAGE__->meta->make_immutable;

パッケージに紐付くメタクラスを不変化します(実行時のメタ情報の編集を禁止します)。これにより、コンストラクターの処理が高速になります。

hasなどでメタクラスを編集した後に記述します。戻り値が1なので、パッケージ末尾の1;を兼ねて記述することも可能です。特別なことをしない限り、常にmake_immutableするとよいでしょう。

make_immutableした後に動的にメタ情報を編集したい場合には、__PACKAGE__->meta->make_mutableで可変化します。

Mouse

MouseMooseとほぼ同様のAPIを持つ高速版モジュールです。MOPの奥深い処理を除いて、幅広い互換性を持っています。

MouseClass::MOP関連の処理を省いていたり、XSで実装されている部分が多かったりするので、効率的な実装がなされているためMouseのロード, インスタンスの生成, Mouseクラスに於ける処理の実行時のいずれもMooseより高速に動作します。

package My::Class;
use Mouse;

パッケージをMouseに基づいたクラスにします。

package My::Role;
use Mouse::Role;

パッケージをMouse::Roleに基づいたロールにします。

package My::Class;
use Any::Moose;

package My::Role;
use Any::Moose '::Role';

既にMooseをロード済みであればそちらを使うように、または将来的にMooseがさらに高速化した場合にMooseに切り替えられるように、Any::Mooseモジュールを利用すると手堅いです。

なお、『モダンPerl入門』で言及されているSquirrelは、現在では非推奨/deprecatedとなっています。

MooseX拡張モジュール/MooseX Expansion Modules

Mooseの拡張モジュールはMooseX::*名前空間にあります。Mouse版の名前空間はMouseX::*です。

Moose::Manual::MooseXでは、お勧めのモジュールがいくつか紹介されています。

oose

perl -Moose=Foo -e 'has bar => (is => q(ro), default => q(baz)); print Foo->new->bar'

ooseはワンライナー/one linerに於いてMooseのシュガー関数を使えるようにするモジュールです。Mouseにもouseがあります。

インターフェースの消費タイミング/Timing to Consuming Interfaces

@methodsが消費元のクラスに実装されていなくても、消費元のクラスのスーパークラスや、消費元クラスが当該ロールを消費する前に既に消費している別のロールなどに実装されていれば、当該ロールをwithした時点で@methodsを呼び出せるため、問題ありません(インターフェースのロールとメソッドを実装してあるロールを一緒にwithすることも可能です)。

package My::Interface;
use Moose::Role;
requires qw(foo);

package My::Class;
use Moose;
# with qw(My::Interface);   # ここでwithするとエラー
has foo => (
    is => 'ro',
);
with qw(My::Interface);     # ここでwithする必要がある

アトリビュートへのアクセッサーメソッドをrequiresする際には注意が必要です。requiresを記述したロールをwithで消費する前に(コードの前方で)、アトリビュートがクラス上に存在していなければなりません。

package My::WithFoo;
use Moose::Role;
has foo => (
    is => 'ro',
);

package My::Class;
use Moose;
# with qw(My::Interface My::WithFoo);   # 同時に消費すると例外
with qw(My::WithFoo);                   # 先にfooを使えるようにしてから......
with qw(My::Interface);                 # インターフェースを適用する

アトリビュートを別のロールに実装している場合も同様です。

メソッド衝突対策/Measures against Method Conflict

ロールとロール消費元のクラスで同名のメソッドが実装されている場合、クラス側に実装されているメソッドが優先され、ロール側で実装されているメソッドは呼ばれません。

クラスが消費している複数のロールに同名のメソッドが実装されている場合、メソッドが衝突/conflictするのでコンパイル時に例外が送出されます。

with (
    $some_role => {
        -alias    => { $method_name => $some_method_name },
        -excludes => { $some_method },
    },
);

この場合、withのオプションで元のメソッドを排除/exclusionして別名化/aliasingする必要があります。ただし、ロールのAPIを崩しているので、どうしても必要な場合を除いては避けた方がよいでしょう。

テスト対策/Measures against Testing

Test::Perl::Criticでは、依存するPerl::Criticのバージョン1.094以降、strictなどのプラグマを有効化するMooseなどのモジュールに対応していますので、use Moose;use Moose::Role;があればuse strict;がないというチェックには引っ掛かりません。

ただし、それ以外に注意しておく点がいくつかあります。

Mooseを使うか否か/To Moose or not to Moose

MooseはPerl5に於いてポストモダンなオブジェクト指向プログラミングを行えるフレームワークです。もう「Perl5のオブジェクト指向は後付けだから汚い」などとはdisられません。

Mooseを使ったクラスは伝統的な手作りのクラスやClass::Accessor::Fastなどを利用したクラスなどに比べて以下のような利点があり、人気を博しています。

  • DSL(ドメイン固有言語/Domain Specific Language)によって宣言的/declativeにクラスを記述できる
  • 従来より遙かに少ない労力で格段に強力なオブジェクト指向プログラミングを行える(Mooseのイディオムを伝統的なPerl5で書くとどうなるかが、Moose::Manual::Unsweetenedや、ichikawayさんによるperl-mogers.orgのMooseに入門してみたよの記事などで言及されています)
  • APIに着目した契約プログラミングというクラス設計を行えるため、従来より安全性や保守性が増す

Perlで最も有名なWAF(ウェブアプリケーションフレームワーク/Web Application Framework)であるCatalystがバージョン5.8以降でMooseを採用するなど、大規模モジュールへの適用事例も数多いので、Mooseは業務アプリケーションへも十分に導入可能です。

しかし、Mooseに基づくクラスにはPerl5の素のクラスと比較してオーバーヘッドがあります。また、本稿で紹介したMoose特有の構文を覚える必要もあります。

これに伴い、Mooseを使うかどうかという議論が巻き起こったこともあります。それらはgfxさんMooseの速度が遅いという議論のまとめと感想という記事に簡潔にまとめられています。その後も、Perl論壇では以下のような考えが表明されています。

こうした議論に一通り目を通した上で、自分なりに(或いは会社やチーム内で)案件毎のMooseの採否を決めることをお勧めします。

関連情報/See Also

Mooseマニュアル/Manuals for Moose

この文書はあくまで「つまみ食い」したものに過ぎません。Mooseにはまだまだ色々な機能や作法があります。実用する前に以下の一次資料へ一通り目を通しておくことをお勧めします。

Moose関連のその他の情報源/Sources about Moose

Perl関連の基礎文法最速マスター/The Fastest Ways for Mastering Basic Perl Grammar

基礎文法最速マスターのまとめ/Collection of the Fastest Ways for Mastering a Programming Language

正誤一覧/Errata

誤りや漏れが少なからずありました。ごめんなさい。

  • 題名で「基本文法」の"Basic Grammar of"への訳出がすっかり抜け落ちていました。パーマリンクを今から変えるのは忍びないので、題名もこのままとしておきます。
  • requiresとJavaのインターフェースとの類似性についての記述を、ロールの説明部へ移動しました。さらに、ロールに実装するメソッドについてRubyのミックスインとの類似性を追記しました。また、ロールの素晴らしさについても追記しました。
  • has [ @attribute_names ] => ...構文を追記しました。
  • extendsuse baseとの相違点の記述について、gfxさんからご指摘(1, 2)をいただきました。@ISAを上書きすることと、@ISAを動的に(実行時に)再設定しない作法とを混同して書いてしまいました。敢えて書くまでもない相違点だと考えたので削除しました。
  • dannさんのパッチに相当する、メソッドモディファイヤーの正規表現指定に言及しました。
  • gfxさんがMouseでのmethod modifiers + regexp構文の対応について言及されていたので、取り込ませていただきました。
  • accessorにはis => 'rw'が必須であることを記述しました。
  • 型の例文のmy $foo = Foo->new(baz => 1);の初期化引数bazbarに直しました。また、真偽値ではなく数値であることを明確に表現するため、142にしました。
  • Role型がMoose 0.84+で非推奨になったことを追記しました。
  • Counterネイティブトレートの記述が抜けていたので追加しました。
  • Stringネイティブトレートのreplacematchで、正規表現のパターンマッチ演算子xmsiが自動適用されているような補記が誤っていたので改めました。
  • Stringネイティブトレートのclearで、clearer methodとの混同に注意を促す記述は削除しました。
  • Arrayネイティブトレートのuniqの元ネタはList::UtilではなくList::MoreUtilsでした。いつも間違えます......(コードで書き間違えないようにするにはUtil::Anyがお勧め)。
  • Arrayネイティブトレートのnatatimeの記述が抜けていたので追加しました。
  • Arrayネイティブトレートのreduce, shuffle, uniq, natatimeMoose 0.89_02+であることを追記しました。
  • CodeネイティブトレートがMoose 0.90+であることを追記しました。
  • MouseX::NativeTraitsへの言及を加えました。
  • BUILDの"isaやrequiredへの対策は"に「など」を加えました。doesなども関係しているためです。
  • Mouseのロードが速い理由について、tokuhiromさんからご指摘をいただきました。どうもありがとうございます。私の単なる憶測による記述でしたので、記述を改めました。
  • Any::Mooseの"将来的にMooseが高速化した場合"に「さらに」を加えました。JPA後援のgfxさんの高速化案件など、MooseClass::MOPは定期的な高速化が図られているためです。
  • withalias, excludeの引数をMoose 0.89_01+対応の作法に直して-alias, -excludesにしました。
  • 『モダンPerl入門』をMooseの観点でお勧めする理由を補記しました。
  • 拙作のチートシートを参考情報に加えました。
  • 目次のa要素のhref属性に絶対URLを追加しました。個別エントリー以外のページでのハイパーリンクが効かないためです。
  • 目次などから飛ぶ見出し(heading)用id属性は、a要素ではなくspan要素に付けるようにしました。

No TrackBacks

TrackBack URL: http://perl-mongers.org/MT/mt-tb.cgi/94

Leave a comment

About this Entry

This page contains a single entry by gardejo published on February 18, 2010 1:47 AM.

CPAN モジュールインストール時にデフォルトで yes と答える方法 was the previous entry in this blog.

実用! PerlでコマンドラインからTwitter投稿(OAuth対応) is the next entry in this blog.

Find recent content on the main index or look in the archives to find all content.

Categories

Pages

Creative Commons License
This blog is licensed under a Creative Commons License.
Powered by Movable Type 4.21-en