Moose-0.92 > Moose::Manual::FAQ

題名

Moose::Manual::FAQ - Mooseについてのよくある質問

よくある質問

モジュールの安定度

Mooseは「実用レベルに達して」いますか

はい! 誰でも知っているような有名サイトの中にもMooseを使って高トラフィックなサービスを構築しているところがたくさんあります。ほかにもMooseを本番環境で使っている人は数え切れないほどいます。

これを書いている時点で、Mooseは数百個のCPANモジュールの依存モジュールになっています。http://cpants.perl.org/dist/used_by/Moose

MooseのAPIは安定していますか

はい。95%のユーザが利用するシュガー関数のAPIについては非常に安定していますし、変更があっても100%後方互換にします

メタAPIについてはそれほど盤石ではありません。一部については効率や一貫性を向上させるために微調整する権利を留保します。ただし、軽々しく変更することはしません。機能を廃止する際にはかならず周知期間を設けていますし、みなさんのコードを壊してばつの悪い思いをするのは「本当に」いやなことだと思っています。リファクタリングしたときにうっかりみなさんのコードを壊してしまうような変更が入らないようにするいちばん確実な方法は、テストケースを送っていただくことです。

Mooseは遅いと聞きましたが本当ですか

これも答えづらい質問ですが、答えはイエスであり、ノーでもあります。

最初にお断りしておくと、世の中にタダで手に入るものは「ありません」。また、Mooseの機能の中には、たしかにほかの機能よりコストのかかるものもあります。ただし、使った機能の分しかコストを請求しないのがMooseのポリシーでもあります。私たちはできる限りの努力を払って、コードを実行するときに使っていない機能のせいで余計な負荷がかかることはないようにしています。もちろんMooseを使うこと自体いくらかのオーバーヘッドをともなうことですが(もっとも、そのほとんどはコンパイル時のものです)、いまのところ速度が必要な方にはいくつかのオプションがあります。

現在利用できるオプションとしては、クラスを不変化して高速化するという方法があります。こうするとコンパイル時のコストは多少大きくなりますが、実行時の速度(特にオブジェクトの生成時)はかなり速くなります。これは次のようなコードで実現できます。

  MyClass->meta->make_immutable();

私たちは定期的にClass::MOPが熱暴走を起こしそうなところをXSで書き直しています。また、誰でも非常に高速なオブジェクト指向を楽しめるように、現在フロリアン・ラグヴィッツ(Florian Ragwitz)とユーヴァル・コグマン(Yuval Kogman)がアクセサやインスタンスを直接Cにコンパイルする方法を模索中です。

Moose 1.0はいつ出ますか

Mooseはもう使えます! スティーヴン・リトル(Stevan Little)は2007年3月にリリースした0.18で「実用できるようになった」ことを宣言しました。

コンストラクタ

Mooseで独自のコンストラクタを書くにはどうすればよいですか

理想的には、決して独自のnewメソッドは書かないようにしてください(オブジェクト生成時に特別な処理が必要な場合は、Mooseのほかの機能を使って対処してください)。ここではいくつかのシナリオと、Mooseではどのように解決するかを紹介します。

インスタンス生成後に初期化コードを呼ぶ必要がある場合はBUILDメソッドを使ってください(この機能はPerl 6から直接持ってきたものです)。インスタンスを生成すると、すぐにインスタンスチェーンにあるすべてのBUILDメソッドが(正しい順序で)呼ばれるので、スーパークラスもすべて適切に、また確実に初期化することができます。これはクラスのサブクラス化が非常に簡単になりますので、(可能なときは)最善のアプローチです。

インスタンスが実際に生成される前にコンストラクタのパラメータをいじる必要がある場合は、いくつかのオプションがあります。

パラメータ処理をまるごと変更したい場合は、BUILDARGSメソッドを利用できます。デフォルトの実装ではキー/値のペアか、ハッシュリファレンスを受け取るようになっていますが、オーバーライドすれば、順番通りに引数を受け取ったり、別のフォーマットの引数を受け取ったりできるようになります。

個々のパラメータの扱いを変える場合は「型変換」があります(型変換の完全な例や説明についてはMoose::Cookbook::Basics::Recipe5をご覧ください)。型変換を利用すると引数の値を期待された通りの型に変換できます。このアプローチがもっとも柔軟で頑丈です(学習曲線はややきつくなってしまいますが)。

Mooseを使っていないコンストラクタをMooseで利用するにはどうすればよいですか

ふつうMooseを使っていないクラスをサブクラス化するときの正しいアプローチは委譲することです(これはhandlesキーワードや型変換、lazy_buildを使えば簡単にできます)。だから、サブクラス化するのは往々にして理想的なやり方ではありません。

とはいえ、本当にMooseを使っていないクラスを継承する必要があるなら、Moose::Cookbook::Basics::Recipe11にやり方が載っています。あるいはCPANにあるMooseX::NonMooseをご覧ください。

アクセサ

Mooseにgetアクセサとsetアクセサを使うよう指示するにはどうすればよいですか

もっとも簡単なのはreaderwriterというアトリビュートオプションを使う方法です。

  has 'bar' => (
      isa    => 'Baz',
      reader => 'get_bar',
      writer => 'set_bar',
  );

これらのメソッドを生成しても型制約やトリガなどは利用できます。

こんなにたくさんタイプしたくないし、このようなget/setアクセサをデフォルトにしたいという方は、MooseX::FollowPBPをご覧ください。このモジュールを使うとこのように書けるようになります。

  has 'bar' => (
      isa => 'Baz',
      is  => 'rw',
  );

これで、Mooseはbarという単一のメソッドを作るかわりに、get_barset_barという別々のメソッドを生成するようになります。

barset_barにしたい場合はMooseX::SemiAffordanceAccessorをご覧ください。

注意:これをグローバルに設定することはできません。そうしてしまうとMooseで作られたほかのクラスを壊してしまうからです。ただし、新たにMooseのシュガー関数をエクスポートするMyApp::Mooseを定義してMooseX::FollowPBPを有効にすればタイプ数を節約できます。Moose::Cookbook::Extending::Recipe4をご覧ください。

アクセサの中で値をオブジェクトにしたり、その逆を行うにはどうすればよいですか

まず、最初に確認したいのは、本当に値からオブジェクトへの変換とオブジェクトから値への変換の双方が必要なのか、ということ。

値からオブジェクトに変換できればよいのであれば、型変換を使うことをおすすめします。DateTimeオブジェクトに変換する基本的なサンプルコードはこうなります。

  class_type 'DateTime';

  coerce 'DateTime'
      => from 'Str'
      => via { DateTime::Format::MySQL->parse_datetime($_) };

  has 'timestamp' => (is => 'rw', isa => 'DateTime', coerce => 1);

ここではDateTimeオブジェクト用に独自のサブタイプを作成して、そのサブタイプに型変換を追加しています。timestampアトリビュートが期待しているのはDateTime型の値ですが、型変換も試すよう指示されているので、timestampアクセサにStr型の値が渡されると、viaブロックにあるコードを使ってDateTimeオブジェクトへの型変換を行おうとします。

より包括的な型変換の例についてはMoose::Cookbook::Basics::Recipe5をご覧ください。

アトリビュートに渡されたオブジェクトを値に変換する必要がある場合、いまのところベストプラクティスはアクセサにaroundモディファイアを追加することです。

  # a timestamp which stores as
  # seconds from the epoch
  has 'timestamp' => (is => 'rw', isa => 'Int');

  around 'timestamp' => sub {
      my $next = shift;
      my $self = shift;

      return $self->$next unless @_;

      # assume we get a DateTime object ...
      my $timestamp = shift;
      return $self->$next( $timestamp->epoch );
  };

型変換を使ってオブジェクトを値に変換することもできるのですが、これは概して非常に複雑で、多くのサブタイプがを必要とするものになります。こちらの例はこのドキュメントで扱う範囲を越えていますので、#mooseでたずねるか、メーリングリストに投稿してください。

さらにもうひとつ、独自のアトリビュートメタクラスを書くというオプションがあります。これもこのドキュメントで扱う範囲を超えていますが、#mooseやメーリングリストでなら喜んで説明します。

アトリビュートは作ったけれど、アクセサはどこにあるの

アクセサは何もしなくても作られるものではありません。Mooseにアクセサを作るよう指示する必要があるのです。おそらくこんなコードになっているのではありませんか。

  has 'foo' => (isa => 'Bar');

本当はこうしたかったのでしょう。

  has 'foo' => (isa => 'Bar', is => 'rw');

このようになっている理由は、アクセサが「なくても」使い方としてはまったく問題ないからです。もっとも単純なのは、自前のアクセサを書きたい場合です。Mooseが自動的にアクセサを作ってしまうと、クラスの生成順序の都合で、自前のアクセサが上書きされてしまうのです。それでは困りますよね。

メソッドモディファイア

beforeを使って@_の値を変更するにはどうすればよいですか

実はできないのです。beforeが実行されるのはメインメソッドの前だけなので、メソッド本体の実行にはおいそれと影響を与えられないのです。

同様に、afterを使ってメソッドの返り値に影響を与えることもできません。

beforeafterに制限を加えているのは、もっと簡潔なコードを書けるようにしたいからです(こうしておけば、もとのメソッドに@_を渡したり、返り値を(コンテクストを保つよう気をつけながら)転送することを心配する必要がなくなります)。

aroundメソッドモディファイアを使えばこのような制限はありませんが、少し冗長になります。

beforeを使ってメソッドの実行を中断できますか

できますが、止められるのは例外を発生させたときだけです。それでは過激すぎるということであれば、かわりにaroundを使うことをおすすめします(メインメソッドの実行を余裕を持って中断できるのはaroundメソッドモディファイアだけです)。例はこうなります。

    around 'baz' => sub {
        my $next = shift;
        my ($self, %options) = @_;
        unless ($options->{bar} eq 'foo') {
            return 'bar';
        }
        $self->$next(%options);
    };

$nextメソッドを呼ばないようにすれば、メインメソッドの実行を中断できます。

afterモディファイアの中で返り値が見られないのはなぜですか

beforeモディファイアと同様に、afterモディファイアも単にメインメソッドの「あとに」呼ばれるだけです。渡されるのはもともとの@_の中身であって、メインメソッドの返り値ではありません

これもなぜこうなっているかの議論は長すぎるので割愛しますが、beforeの場合と同じく、aroundモディファイアを使うことをおすすめします。サンプルコードはこのようになります。

  around 'foo' => sub {
      my $next = shift;
      my ($self, @args) = @_;
      my @rv = $next->($self, @args);
      # do something silly with the return values
      return reverse @rv;
  };

型制約

型制約に独自のエラーメッセージを用意するにはどうすればよいですか

サブタイプを作成するときにmessageオプションを利用してください。

  subtype 'NaturalLessThanTen'
      => as 'Natural'
      => where { $_ < 10 }
      => message { "This number ($_) is not less than ten!" };

値がNaturalLessThanTenの型チェックに失敗すると、このmessageブロックが呼ばれます。

型制約のチェックを無効にすることはできますか

まだできませんが、将来のリリースではこのオプションが入るかもしれません。

ロール

合成したロールでBUILDが呼ばれないのはなぜですか

BUILDは合成したロールでは決して呼ばれません。そのおもな理由は、ロールは順番の影響を受けないからです(ロールは合成の順序が影響しないような形で合成されます。詳しい理論はhttp://www.iam.unibe.ch/~scg/Research/Traits/にある大本の論文をご覧ください)。

ロールには本質的に順序がないため、BUILDメソッドを実行する順序を決めることはできません。

ただし、それ以外の解決策はいくつかあります。

  • アトリビュートに遅延評価とデフォルト値を組み合わせておくと、初期化を遅らせることができます(遅延評価やデフォルトの使い方についてはクックブックのMoose::Cookbook::Basics::Recipe3にあるバイナリ木の例が好例なのでご覧ください)。

  • アトリビュートのトリガを使うと(これはアトリビュートの値が設定されたときに実行されます)初期化を簡単にできます。これについてはMooseのドキュメントに説明されているほか、テストスイートに例があります。

一般的に、ロールは初期化を必要とすべきではありません。まともなデフォルト値を用意しておくか、具体的に初期化が必要であるとドキュメント化しておくべきです。「ドキュメント化」のひとつの方法としては、アトリビュートの初期化メソッドを別に用意して、ロールの必須メソッドとすることです。例としてはこのようになります。

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

  has 'height' => (
      is      => 'rw',
      isa     => 'Int',
      lazy    => 1,
      default => sub {
          my $self = shift;
          $self->init_height;
      }
  );

  requires 'init_height';

この場合、このロールはinit_heightメソッドを用意しているクラスにしか合成できません。

このような解決策でもうまくいかない場合、ロールは問題を解決するのに最適なツールではないのかもしれません(本当にクラスを使う必要があるのかもしれません)。少なくとも、問題のロールの機能を削って、初期化を必要としないようにした方がよいでしょう。

トレートとはなんですか。ロールとはどう違うのですか

Mooseのトレートはロールとほとんど同じものです。ただし、トレートの方は通常登録されているので(「MyApp::Role::Big」を「Big」という風に)短い名前で呼ぶことができます。

Mooseの文脈では、「ロール」はふつうコンパイル時に「クラス」に合成されます。一方の「トレート」は、ふつうは実行時にクラスのインスタンスに合成して、そのインスタンスだけに振る舞いを追加したり修正を加えたりするものです。

Mooseの文脈を離れると、トレートとロールは一般的にはまったく同じ意味です。元々の論文ではトレートと呼ばれていましたが、Perl 6ではロールと呼ぶことになっています。

Mooseとサブルーチンアトリビュート

スーパークラスから継承したサブルーチンアトリビュートが動かないのはなぜですか

いまのところextendsキーワードを使ったモジュールのサブクラス化は実行時に行われますが、アトリビュートはコンパイル時にチェックされるため、アトリビュートを有効にするにはextendsBEGINブロックの中に入れて、コンパイル時にアトリビュートハンドラを利用できるようにしなければなりません。

  BEGIN { extends qw/Foo/ }

念のため、ここで話題にしているのはPerlのサブルーチンアトリビュートについてです。Mooseのアトリビュートについてではありません。

  sub foo : Bar(27) { ... }

作者

Stevan Little <stevan@iinteractive.com>

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

Copyright 2006-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.