YAPC::Asia2009の特別研修「Moose入門、モダーンなオブジェクト指向システム」が超良かった!

  • Shawn Moore(Sartak)の講義でした。同時通訳。
  • 感想。
    • 超楽しかった!!sartakがとてもいい人だった!ちょっとした質問でもコード書きながら教えてくれた。
    • もっとみんな受けたらいいのに!もう終わっちゃったけど!
    • 機能毎に、講義→実習→講義→実習、の繰り返し。
      • これがとても楽しかった。
      • 1日最後まで楽しくコード書いて過ごせた。
      • 実習の内容も、既にあるテストが通るようにモジュールを作成してテストが通ったら「やったー!」みたいな。
    • gitに資料があるのでみんな落としてやったらいいよ!ccのライセンスで公開してるので。
      • きっとtestファイル自体もキレイに書いてあるはずだから見たらいいと思うよ
    • JPAにすごい感謝でした。3日間全部受けておけば良かったと思った。
  • 以下メモ。

Part0: Mooseのコンセプト

  • perl6からいろんなアイディアをもらってる
  • まずはざっくりMooseの機能の概要
クラス
  • 以下のものを持ってる
    • attribute
    • method
    • superclass
    • method modifiers
    • constructorとdestructor
    • metaclass
      • クラスを説明する裏のオブジェクト
  • 以下のことができる
    • role
    • use Moose;
      • これだけでMooseのクラスになるよ!
Attribute
  • アクセスコントロール
    • read-only
    • read-write
  • hasとis
    • hasでattributeの指定
    • isでアクセスコントロール
  • method
    • 特にいじる必要がないためそのまま
Role
  • 良く知っている例では
  • 「あるクラスは指定したメソッドが必要」という指定ができる
    • use Moose::Role;
      • ここで指定したメソッドはRoleを使ったクラスで使える
    • 使う時はwith
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がいらない
Mooseのクラスとそうでないクラスの比較
  • use Moose;の1行で以下のことをやってくれる
    • use strict; use warnings; use Carp 'confess';
    • sub new {}
  • だいぶ行数が少なくなるし、typoしないで済む

Part1: Mooseクラス

  • 自動的にMoose::Objectを継承
    • Dumpなど、便利なものも入る
Moose::Object
  • new
  • BUILDARG(), BUILD()
    • 生成の時のhook
  • DEMOLISH
BUILDARGS
  • newの引数@_を変更できる
    • 適当なhashrefにしてnewに渡す
  • 念のためSUPER::BUILDARGS(@_)を呼び出すことを推奨
    • BUILDARGSの中で一つの引数の場合だけ処理したいときif分岐でその処理 + return
      • その後にSUPER::BUILDARGSを実行
BUILD
  • newの後に実行
    • newの後にvalidationしたいとき
      • 他のやり方でできるけど簡単な例として。
    • newの後に必ず実行したいものがあるとき
  • returnは必要ない
  • SUPER::BUILDは自動的に実行されるのでいらない
    • 親から子供の順で実行される
オブジェクトが作られる順番
  • Class->BUILDARGS(@_)
  • bless
  • $new_object->BUILDALL($constructor_args)
    • 全てのBUILDを呼び出す
      • 親から子供の順に実行
      • 親側でargsを変更すれば、その変更した物が子供のBUILDに渡される
  • 生成したオブジェクトをreturn
オブジェクトに対する注意
  • オブジェクトのプロパティをハッシュ呼び出ししちゃダメ
    • $obj->{property}みたいな呼び出し方はダメ
      • オブジェクトの実装が変わったら呼び出せなくなる
    • カプセル化
      • もし中身を見たければDumpでできるからそれでデバッグすればいい
DEMOLISH
  • 子供から親の順に全て実行
  • 基本的に使う必要ないかも
    • 使ったことはない
extends
  • Personクラスを継承したいとすると、、、
  • use base 'Person'と同じことを行う
    • Mooseの場合はuse Moose; extends 'Person';
      • 基本はこれでOK。ただ、これだと実行時に行われるので、use baseの場合と全く同じにしたいならBEGIN { extends 'Person' }
      • Catalyst以外ならBEGINはなくても大丈夫だと思う
  • 多重継承した場合にextendsは何行にも分けて書かない
    • extends 'Person', 'Thief';のように書く
    • Mooseじゃない親クラスを継承する時はMooseX::NonMooseが使える
overrideとsuper
  • override work => sub { ...; super; }
    • パラメータは必要ない。Mooseが勝手にやってくれる
      • @_は変更できない
      • @_を変更したい場合はaroundを使う
  • super
    • $self->SUPER::work(@_)
Attribute
  • hasでプロパティ名を指定して、
    • ちなみに複数のhasをhas ['left', 'right']のように一度に指定できる
  • is
    • is => 'ro':読み出し専用
      • setしても書き変わらない
    • is => 'rw':書き出し
    • isを書かないとwarnが出る
      • Mooseをあまり使わないユーザはisを忘れやすいのでwarnを出力するようにしている
      • いらない場合はis => 'bare'にするといい
  • Attribute色々機能があってMooseの一番面白いところかも
Mooseのメソッド、関数について
  • Mooseではメソッドと関数で意味が変わる
    • extendsなどはPerson->extendsで実行可能だけど、warnが出るし、やっちゃダメ
    • use Mooseしたら最後にそういう関数をなくすためにno Mooseした方がいい
    • またはnamespace::cleanを使う
      • except => 'meta'を使う(実際に試したらなくても上手くいっちゃった。なんでだ?みたいになった)
      • namespace::autocleanっていうのもある
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を呼び出すこともできる
  • Roleを実装する側はwithを宣言
    • 例:with 'HasPermissions'
    • そのままコードをクラス側に写したのとほぼ同じ。継承ではない
      • 違いはPerson->does('printable')またはPerson->meta->does('printable')のように調べられるかどうか
Roleと同じメソッドがあった場合の動き
  • 自分のクラスにメソッドを書いた場合は自分のメソッドが優先される
  • 親クラスからメソッドを継承している場合はRoleの方のメソッドが優先される
複数のRoleを使う場合x
  • with 'IsFragile', 'CanBreakdance'
    • 優先順位はないので同じメソッドがあった場合はRoleを使ったクラス側で対応
      • 例:breakというメソッドが衝突した場合は一旦他の名前のaliasを作る
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ならコンパイルを変えるのでエラーでない
does
  • attributeでdoes => 'Comparable'とすればRoleを型にできる
オブジェクトを変えずにRole
  • use Moose::Util qw( apply_all_roles );と書いて、
    • apply_all_roles($person, 'IsFragile');
no Moose::Roleとimmutable

Part3: Basic Attributes

  • Attributeが一番面白いかも
  • たくさん機能を持ってる
    • AttributeはたくさんのMooseXの対象になってる機能
required
  • requiredは「必ず必要なプロパティ」という意味
    • 引数が空ならエラーだが、undefは値なのでエラーではない
      • new(first_name => undef)はエラーが出ず、OK
default
  • ''やundefが来た場合に設定される値
  • has bank => (default => 'Spire FCU');みたいな書き方
    • default => sub { Bank->new(name => 'Spire FCU') }みたいにオブジェクトをデフォルト値にもできる
      • default => Bank->new()はダメ。全て同じBankオブジェクトが使われるという意味になる
      • default => もダメ。arrayrefを使いたいならdefault => sub { }のようにsubで囲む
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の中で他のプロパティの値を使いたい時など
ClearerとPredicate
  • cleareは初期化したいとき
  • predicateは値をチェックしたいとき
    • predicateはわかりやすく'has_*'という名前にする
    • 値がundefだった場合、undefは値なので真を返す
  • デフォルトではこの2つは作られないので、必要なら自分で定義する
    • メソッド自体はMooseが勝手に作ってくれるので作る必要はない
      • cleare => '_clear_account', predicate => 'has_account'ならば_clear_acoount,has_accountのメソッドは勝手に作られる
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
  • 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にできる
subtypeを短く書く方法
  • use Moose::Util::TypeConstraints
  • class_typeやrole_typeと宣言する
  • duck_type
  • enum
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  { [ $_ ] };
  • no Moose::Util::TypeConstraintsを最後に書くこと
    • Moose使ってるとno ...がたくさん増えるのでuse namespace::cleanの方がいいかも
メソッドのtype
  • use MooseX::Params:Validate qw(validated_list)
    • タイプが多くなる
    • 他の例は以下
  • use MooseX::Method::Signatures
    • perl6の機能
coerceの注意点
  • グローバルなので名前の衝突が起こる可能性がある
  • 自分が作った名前でのみやるのが良い
    • My::DateTimeなどを自分で作り、そこでcoerceする
MooseX::Type
  • MooseのTypeの弱点を補うもの
  • declareでTypeを宣言
  • MooseX::Types::MooseMooseから使いたいTypeだけ読み込む
  • 使う時はクォーテーション無しで使う
    • Typoの場合はperlのコンパイルエラーが起こる
    • ::が入ったタイプは''が必要
Typeについての推奨
  • MooseX::Typesを使いましょう
    • コンパイルタイムでエラーがわかるのと自動的にnamespaceを解決してくれるのはメリット大きい
  • 基本的に全てのattributeに適用すべき
    • validateが効くので

Pert6: Advanced Attributes

  • あまり使わないAttributeとちょっとしたTips
Weak References
  • 循環参照があるときに使う
    • perlのGCがリファレンスカントなので
    • python,ruby,javaのGCは他のアルゴリズムなので問題が起こらない
  • use Scalar::Util qw( weaken );でweaken $foo->{bar}にすればリファレンスカウントが増えないのでGCできる
    • weakenはリファレンスを直接参照しないとダメ。getとかでとってきた値ではダメ
      • weaken $foo->{bar} # weaken $foo->bar() ではダメ
  • Mooseの場合はattributeにweak_ref => 1を指定する
    • 裏側でweakenを呼び出している
Triggers
  • attributeがセットされた後に呼び出される
  • afterのようなもの
  • プロパティ同士の同期に使われる
    • 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なので複数指定もできる

その他

  • Task::Moose
    • 最近使われているMooseのモジュールを教えてくれる
  • MooseX::Declare
    • YAPCでも話があったけど、このモジュールはいいよ

資料

  • git://git.moose.perl.org/moose-presentations.git
    • git cloneで取ってくるとslideとexerciseが入ってる