YAPC::Asia2009の特別研修「Moose入門、モダーンなオブジェクト指向システム」が超良かった!
- Shawn Moore(Sartak)の講義でした。同時通訳。
- 感想。
- 超楽しかった!!sartakがとてもいい人だった!ちょっとした質問でもコード書きながら教えてくれた。
- もっとみんな受けたらいいのに!もう終わっちゃったけど!
- 機能毎に、講義→実習→講義→実習、の繰り返し。
- これがとても楽しかった。
- 1日最後まで楽しくコード書いて過ごせた。
- 実習の内容も、既にあるテストが通るようにモジュールを作成してテストが通ったら「やったー!」みたいな。
- gitに資料があるのでみんな落としてやったらいいよ!ccのライセンスで公開してるので。
- きっとtestファイル自体もキレイに書いてあるはずだから見たらいいと思うよ
- JPAにすごい感謝でした。3日間全部受けておけば良かったと思った。
- 以下メモ。
Part0: Mooseのコンセプト
- perl6からいろんなアイディアをもらってる
- まずはざっくりMooseの機能の概要
クラス
Attribute
- アクセスコントロール
- read-only
- read-write
- hasとis
- hasでattributeの指定
- isでアクセスコントロール
- method
- 特にいじる必要がないためそのまま
Role
Method Modifiers
- before
- メソッドの前に実行
- returnは使えない
- after
- メソッドの後に実行
- returnは使えない
- around
- 元のメソッドをラッパできる
- 元のメソッドを呼び出す場合にはパラメータは@_で明示的に渡す
- returnも変えられる
- OOのSUPERのような呼び出しが可能
- なぜこれを作ったかは後で説明する。
- 元のメソッドをラッパできる
Type Constraints
- 型を指定できる
- isa
- StrからIntにするなどもできる
Delegation
- 委譲
- 実際のメソッドは裏で呼び出せる
- 使う側からメソッドを隠せる
Constructor
- BUILDARGSとBUILDでhookできる
Destructor
- DEMOLISHでhook
Meta-API
- クラスの情報を全部metaメソッド経由で取得できる
- クラスが何のメソッドを持っているか
- grep { *{$_}{CORE} } \%Person::の実行がmeta->methodsで済む
- meta->get_method_listでクラスが実行できるメソッドのリストが出せる
- クラスが何の親を持ってるか
- 通常@{Person::ISA}と書かないといけないがPerson->meta->superclassessでOK
- *Person::first_name = sub {...}
- person->meta->add_method(first_name) = sub { ... }
- no strict 'refs'; *{$class.'::first_name'} = sub { ... }とも書けるけど、
- Mooseならglobがいらない
Part1: Mooseクラス
- 自動的にMoose::Objectを継承
- Dumpなど、便利なものも入る
Moose::Object
- new
- BUILDARG(), BUILD()
- 生成の時のhook
- DEMOLISH
BUILDARGS
- newの引数@_を変更できる
- 適当なhashrefにしてnewに渡す
- 念のためSUPER::BUILDARGS(@_)を呼び出すことを推奨
- BUILDARGSの中で一つの引数の場合だけ処理したいときif分岐でその処理 + return
- その後にSUPER::BUILDARGSを実行
- BUILDARGSの中で一つの引数の場合だけ処理したいときif分岐でその処理 + return
BUILD
- newの後に実行
- newの後にvalidationしたいとき
- 他のやり方でできるけど簡単な例として。
- newの後に必ず実行したいものがあるとき
- newの後にvalidationしたいとき
- returnは必要ない
- SUPER::BUILDは自動的に実行されるのでいらない
- 親から子供の順で実行される
オブジェクトが作られる順番
- Class->BUILDARGS(@_)
- bless
- $new_object->BUILDALL($constructor_args)
- 全てのBUILDを呼び出す
- 親から子供の順に実行
- 親側でargsを変更すれば、その変更した物が子供のBUILDに渡される
- 全てのBUILDを呼び出す
- 生成したオブジェクトをreturn
オブジェクトに対する注意
DEMOLISH
- 子供から親の順に全て実行
- 基本的に使う必要ないかも
- 使ったことはない
extends
overrideとsuper
- override work => sub { ...; super; }
- パラメータは必要ない。Mooseが勝手にやってくれる
- @_は変更できない
- @_を変更したい場合はaroundを使う
- パラメータは必要ない。Mooseが勝手にやってくれる
- super
- $self->SUPER::work(@_)
Attribute
Mooseのメソッド、関数について
immutability
- __PACKAGE__->meta->make_immutable
- 定義済みであることを宣言するようなもの
- もうこの行以降プロパティなどは追加しませんよ
- Mooseはオブジェクトの生成にコストがかかるけど、この行を書くことでオブジェクトの生成が速くなる
- クラスのロードには時間がかかるけど、それよりはオブジェクトの生成のコストが軽い方がいい
- 常にこれは書くべき
- とりあえず。Mooseを使っているうちに使わなくていい場合がわかると思う
- mainなどからPerson->meta->add_methodでの追加もできなくなる
- 追加する方法はある
- あるpakage(例えばPerson)を外から(例えばmainから)変えたい場合、
- Person->meta->mutableを使い、その後にPerson->meta_add_methodで追加する
- その後にまたPerson->meta->immutableを行えばいい
- 追加する方法はある
- immutableかどうかはPerson->meta->is_immutableで確認できる
Part2: Role
- 元々はsmall talkから取った概念
- mixin,interface,trait
- 「requires」を使って、Roleをwithしたクラスが実装しなければならないメソッドを指定することもできる
- 例:requires 'as_string'
- クラスにas_stringメソッドがないとruntime error
- Roleの中ではsub print { ...; print $self->as_string; ... }とメソッドの中でas_stringを呼び出すこともできる
- 例:requires 'as_string'
- Roleを実装する側はwithを宣言
- 例:with 'HasPermissions'
- そのままコードをクラス側に写したのとほぼ同じ。継承ではない
- 違いはPerson->does('printable')またはPerson->meta->does('printable')のように調べられるかどうか
Roleと同じメソッドがあった場合の動き
- 自分のクラスにメソッドを書いた場合は自分のメソッドが優先される
- 親クラスからメソッドを継承している場合はRoleの方のメソッドが優先される
複数のRoleを使う場合x
- with 'IsFragile', 'CanBreakdance'
- 優先順位はないので同じメソッドがあった場合はRoleを使ったクラス側で対応
- 例:breakというメソッドが衝突した場合は一旦他の名前のaliasを作る
- 優先順位はないので同じメソッドがあった場合はRoleを使ったクラス側で対応
with 'IsFragile' => { alias => { break => 'break_bone' } }, 'CanBreakdance' => { alias => { break => 'break_it_down' } };
-
-
- まだbreakメソッドは衝突しているのでexcludeもする
-
with 'IsFragile' => { alias => { break => 'break_bone' }, exclude => 'break' }, 'CanBreakdance' => { alias => { break => 'break_dance' }, exclude => 'break' };
-
- Roleのメソッドには意味があるから単純にalias,excludeすればいいと考えずにRoleのメソッドの意味はおさえておくこと
- Roleは重ねられる
- withを使ったpackageをさらにwithできる
Attributeとの名前の衝突
- 例:requires 'size'のRoleをwithした後にhas sizeを定義するとエラーが出る
- has sizeの後にwithを書かないといけない
- perl5の制限なので仕方ない
- MooseX::Declareならコンパイルを変えるのでエラーでない
- has sizeの後にwithを書かないといけない
does
- attributeでdoes => 'Comparable'とすればRoleを型にできる
オブジェクトを変えずにRole
- use Moose::Util qw( apply_all_roles );と書いて、
- apply_all_roles($person, 'IsFragile');
Part3: Basic Attributes
- Attributeが一番面白いかも
- たくさん機能を持ってる
- AttributeはたくさんのMooseXの対象になってる機能
required
- requiredは「必ず必要なプロパティ」という意味
- 引数が空ならエラーだが、undefは値なのでエラーではない
- new(first_name => undef)はエラーが出ず、OK
- 引数が空ならエラーだが、undefは値なのでエラーではない
default
- ''やundefが来た場合に設定される値
- has bank => (default => 'Spire FCU');みたいな書き方
- default => sub { Bank->new(name => 'Spire FCU') }みたいにオブジェクトをデフォルト値にもできる
- default => Bank->new()はダメ。全て同じBankオブジェクトが使われるという意味になる
- default => もダメ。arrayrefを使いたいならdefault => sub { }のようにsubで囲む
- default => sub { Bank->new(name => 'Spire FCU') }みたいにオブジェクトをデフォルト値にもできる
Builder
- defaultで呼び出すメソッド
- builder => '_build_bank'のように指定し、_build_bankというメソッドを別の所に書いておく
Default vs Builder
- defaultの方にするときは?
- 簡単なスカラ値,空のリファレンス,簡単subのリファレンス
- それ以外は全部builder
- builderはメソッドなのでbefore,afterやoverrideができる
- Roleでbuilderのメソッドをrequiresにするというやり方もある
Lazy
- attributeの遅延処理をしたいとき
- 他のattributeの値を参照して、処理するようなプロパティの場合
- 例えばbuiler => '_build_shoes'のようなプロパティで、_build_shoesの中で他のプロパティの値を使いたい時など
- 他のattributeの値を参照して、処理するようなプロパティの場合
ClearerとPredicate
- cleareは初期化したいとき
- predicateは値をチェックしたいとき
- predicateはわかりやすく'has_*'という名前にする
- 値がundefだった場合、undefは値なので真を返す
- デフォルトではこの2つは作られないので、必要なら自分で定義する
- メソッド自体はMooseが勝手に作ってくれるので作る必要はない
- cleare => '_clear_account', predicate => 'has_account'ならば_clear_acoount,has_accountのメソッドは勝手に作られる
- メソッド自体はMooseが勝手に作ってくれるので作る必要はない
init_arg
- newするときのプロパティ名を変えたいときに使う
- has shoe_size => (init_arg => 'foot_size')ならnew(foot_size => 13)のように書かないとプロパティにsetされない
- newの時に値を設定させたくない時はinit_arg => undefとする
Attributeの拡張
- 変えたいプロパティに+を付ける
- has '+first_name'のようにする
Accessor
- accessor => '_first_name'
- _first_nameでしかアクセスできなくなる
- reader,writerでset,getそれぞれの値を変えられる
- reader => 'get_', writer => 'set_'
- isを書いた上で、writerのようにも書ける
- それ用のモジュールもある
- MooseX::FollowPBP
- MooseX::SemiAffordanceAccessor
Part4: Method Modifiers
- before, after, aroundなどのメソッド拡張について
- Roleのメソッドも拡張できる
- Mooseが自動的に生成するメソッドも拡張できる
beforeとafter
- before
- 全てのパラメータは受け取れるけど変更はできない
- メソッドのデバッグにも便利
- return unless $DEBUG;でDEBUGが有効な時だけ実行するとか。
- after
- return valueを変更することはできない
- 例:workメソッドを呼び出した後に$self->work_countを+1するとか
- 例:passwordとhased_passwordというプロパティがあったときにpasswordのclearerを呼び出した後に、そのafterの中でhassed_passwordのclearerを呼び出す
- excptionを出すことはできる
around
- パラメータもreturnも変更できる
- 関数をラップする
- ラップした関数を実行するかしないかも選べる
- 例:insertのメソッドでvalidate
継承された時に呼び出される順番
- beforeは最後に書いた順に
- afterは最初に書いた順に
- araoundは最後に書いた順に
before2 before1 around2 around1 wrapped method around1 around2 after1 after2
Roleの場合
- 必ずrequiresを使うべき
- これがないとエラーがわかりにくいのでデバッグしにくくなる
- with 'A','B'なので、同じ名前があった時の順番は保証されない
augmentとinner
- 親のクラスが子のクラスが呼び出すとき
- superの逆
- inner<->super
- augment<->override
- superの逆
- roleでは使えない
- 実際にはあまり使われていないかも
- 将来的に実装する可能性もあるので、念のため一番下のクラスでもinner()を呼び出す
Part5: Types
- perlには$,@,%しかない
- ハックっぽいシステムなので全てvalidationしないといけない
- シビアなパフォーマンスが必要な時は使わない方がいいかもしれない
- MooseXモジュールはメソッドのタイプ定義を可能としてる
typeについて
- typeが正しいかどうか真偽値を返す
- 親type子typeという階層も持てる
- DateTimeと日本時間のDateTime
- Mooseで定義済みのものがいくつかある
Bool
- perlのtrue,falseと同じ
Value (and subtype)
- refでない値
ClassNameとRoleName
- チェックするclassとroleは既にロード済みでなければいけない
Parameterizable Type
- ArrayRef['a]
- ['a]は「[]の中に何か入る」という意味
- ArrayRef
- ArrayRef[Str]
- ArrayRef[MyTypeName]
- ArrayRef[HashRef[Maybe[Int]]]
Maybe['a]
- undef
- Maybe[Int]とかとか
Union
- どちらかのtype
- Int | ArrayRef[Int]
- 型縛りにならないので、自分で2つを内包するようなtypeを作ってcoerceで片方を強制変換にした方がいいかも(後で説明)
typeを作る
- use Moose::Util::TypeConstraints;
- subtypeで定義。失敗した時のエラーはmessageで定義。
subtype 'PositiveInt', as 'Int', where { $_ > 0 }, message { "The value you provided ($_)" . " was not a positive number." };
自動的にtypeになるもの
- Mooseを使って作ってClassやRoleはTypeにできる
Anonymous Subtype
- $posint = subtype as 'Int', where { $_ > 0 }のあとに、
- isa => $point
Coercions
- use Moose::Util::TypeConstrains
- coerceで強制変換ができる
- パフォーマンスが少し落ちるので自分で有効にしないと有効にはならない
- hasでcoerce => 1を宣言する
- パフォーマンスが少し落ちるので自分で有効にしないと有効にはならない
- 例:
subtype 'My::DateTime', as class_type 'DateTime'; coerce 'My::DateTime', from 'HashRef', via { DateTime->new( %{$_} ) }; coerce 'My::DateTime', from 'Int', via { DateTime->from_epoch( epoch => $_ ) };
- type unionよりもわかりやすい
- Int | ArrayRef[Int]よりも下のように書いた方がいい
coerce 'ArrayRef[Int]', from 'Int', via { [ $_ ] };
メソッドのtype
- use MooseX::Params:Validate qw(validated_list)
- タイプが多くなる
- 他の例は以下
- use MooseX::Method::Signatures
- perl6の機能
coerceの注意点
- グローバルなので名前の衝突が起こる可能性がある
- 自分が作った名前でのみやるのが良い
- My::DateTimeなどを自分で作り、そこでcoerceする
MooseX::Type
Typeについての推奨
- MooseX::Typesを使いましょう
- コンパイルタイムでエラーがわかるのと自動的にnamespaceを解決してくれるのはメリット大きい
- 基本的に全てのattributeに適用すべき
- validateが効くので
Pert6: Advanced Attributes
- あまり使わないAttributeとちょっとしたTips
Weak References
Triggers
- attributeがセットされた後に呼び出される
- afterのようなもの
- afterはコンストラクタ時に呼び出されないのでtriggerが必要
- プロパティ同士の同期に使われる
- trigger => sub { $_[0]->clear_... }
- あるプロパティをセットしたタイミングで、他のプロパティをクリア
- trigger => sub { $_[0]->clear_... }
Delegation
- attributeのtypeに指定したオブジェクトにメソッドを委譲する
- handles => ['inhale', 'exhale']のように書く
- arrayrefだとそのメソッド名が作られるし、hashrefならメソッド名を指定できる
- regexならマッチしたメソッド名ならdelegationできる
- やめるべきかも
- 内部のオブジェクトが変わった時に動きが変わる可能性がある
- subrefもできるけど複雑だから自分で勉強して
- Roleの名前を書くと、そのRoleのメソッド全てをハンドルする
- Native Delegation
- 開発版に実装中
- scalar,array,hashのメソッドが使えるようになる
- 使う時はtraitsを書く必要がある
TraitsとMetaclass
- attributeにMooseのcoreにないものを追加してmeta経由で呼び出すことができる
- classやメソッドも拡張できる
- use MooseX::LabeledAttributes
- traits => ['Labeled'], label => '...'とattributeを書くと、
- Person->meta->get_attribute('...')->labelで呼び出せる
- traitsの代わりにmetaclass => 'MooseX::Meta::Attribute::Labeled'とも書ける
- traitsの場合はarrayrefなので複数指定もできる
- traits => ['Labeled'], label => '...'とattributeを書くと、