Moose-0.92 > Moose::Manual::Attributes

題名

Moose::Manual::Attributes - Mooseによるオブジェクトのアトリビュート

はじめに

Mooseのアトリビュートには多くのプロパティがあります。アトリビュートはおそらくMooseの機能としてはずば抜けて強力で柔軟なものですし、アトリビュートを宣言するだけで強力なクラスを作れます。実際、アトリビュートの宣言しかないクラスを作ることもできます。

アトリビュートは、あるクラスに属するすべてのメンバーが持つプロパティです。たとえば、「Personオブジェクトにはかならず姓と名がある」といえます。また、アトリビュートは省略可能にすることもできるので、「Personオブジェクトの中には社会保障番号を持つものもある(持たないものもある)」ということもできます。

もっとも単純な例では、アトリビュートは(ハッシュのように)読み書き可能な名前付きの値とも考えられます。ただし、アトリビュートの場合はデフォルト値や型制約があったり、委譲などを行うこともできます。

アトリビュートは、ほかの言語ではスロットやプロパティと呼ばれることもあります。

アトリビュートのオプション

アトリビュートを宣言するときにはhas関数を使います。

  package Person;

  use Moose;

  has 'first_name' => ( is => 'rw' );

これは、Personオブジェクトはすべて省略可能で読み書きもできる「first_name」というアトリビュートを持つ、ということです。

読み書き可能と読み取り専用

アトリビュートのプロパティはhasに渡したオプションによって定義されます。オプションにはさまざまなものがありますが、もっともシンプルな例では、設定する必要があるのはisだけです。これはrw (read-write)またはro (read-only)の値を取ります。rwなアトリビュートはアクセッサーに値を渡すことで保持されている値を変更することができますが、roの場合はアクセッサを使って現在の値を読み込むことしかできません。

実際にはisすら省略できますが、そうすると生成されたアトリビュートにはアクセサがなくなり、handlesなどの他のオプションと組み合わせて便利な使い方もできます。しかし、アトリビュートが(handlesなどが生成するものも含めて)一個もアクセサを生成しない場合Mooseはプログラマーが何かを書き忘れた場合と判断し、警告を発します。もし本当に一個もアクセサを生成しない場合はisオプションにbareと指定することでこの警告を止めることができます。

アクセサメソッド

オブジェクトのアトリビュートにはそれぞれひとつ以上のアクセサメソッドがあります。アクセサというのはそのアトリビュートの値を読み書きするためのものです。

デフォルトではアクセサメソッドはアトリビュートと同じ名前になります。アトリビュートがroであると宣言した場合はアクセサも読み取り専用になり、読み書き用と宣言した場合は読み書き用のアクセサが得られます。簡単ですね。

先ほどのPersonの例でいうと、これで私たちはPersonオブジェクトのfirst_nameアトリビュートの値を読み書きできるfirst_nameアクセサをひとつ用意できた、ということになります。

お望みであれば、アトリビュートの値を読み書きするのに使うメソッド名を明示的に指定することもできます。これは特にアトリビュートの読み取りはパブリックにしたいけれど書き込みはプライベートにしたい場合に便利です。一例をあげましょう。

  has 'weight' => (
      is     => 'ro',
      writer => '_set_weight',
  );

weightがほかのメソッドに基づいて計算される場合はこうしておいた方が便利かもしれません。eatメソッドが呼ばれるたびにweightを修正するようなことがあるかもしれないからです。こうしておけば、weightの値を変更する実装の詳細は隠したまま、クラスを使う人にweightの値を提供できるようになります。

また、読み取り用のメソッド名と書き込み用のメソッド名は区別したいという人もいるかもしれません。ダミアン・コンウェイはPerl Best Practicesの中で読み取り用のメソッドは「get_」で、書き込み用のメソッドは「set_」で始めることを推奨しています。

これはreaderメソッドとwriterメソッドの双方にメソッド名を指定すればまさにその通りのことができます。

  has 'weight' => (
      is     => 'rw',
      reader => 'get_weight',
      writer => 'set_weight',
  );

こんなことを何度も繰り返すのは頭がおかしくなるほど退屈な作業だと思った方、正解です! ありがたいことに、Mooseには強力な拡張システムがありますから、デフォルトの命名規則をオーバーライドすることもできます。詳しくはMoose::Manual::MooseXをご覧ください。

断定用のメソッド(predicate)とクリア用のメソッド(clearer)

Mooseを使うと、偽または未定義値が入っているアトリビュートと、値がセットされていないアトリビュートとを明示的に区別できます。この情報を利用するにはアトリビュートにクリア用のメソッドと断定用のメソッドを定義する必要があります。

断定用のメソッドはあるアトリビュートにいま値がセットされているかどうかを返すものです。なお、アトリビュートに明示的にundef、またはほかの偽値をセットした場合でも断定用のメソッドは真値を返しますのでご注意ください。

クリア用のメソッドはアトリビュートの値をセットされていない状態にするものです。これはundef値をセットするのと同じ「ではありません」。ただし、これを区別できるのは断定用のメソッドを定義したときのみです!

ここでアクセサと断定用のメソッド、クリア用のメソッドの関係を示すコードを見てみましょう。

  package Person;

  use Moose;

  has 'ssn' => (
      is        => 'rw',
      clearer   => 'clear_ssn',
      predicate => 'has_ssn',
  );

  ...

  my $person = Person->new();
  $person->has_ssn; # false

  $person->ssn(undef);
  $person->ssn; # returns undef
  $person->has_ssn; # true

  $person->clear_ssn;
  $person->ssn; # returns undef
  $person->has_ssn; # false

  $person->ssn('123-45-6789');
  $person->ssn; # returns '123-45-6789'
  $person->has_ssn; # true

  my $person2 = Person->new( ssn => '111-22-3333');
  $person2->has_ssn; # true

デフォルトでは、Mooseは断定用のメソッドやクリア用のメソッドは用意してくれません。自分で明示的にメソッド名を指定する必要があります。

必須かどうか

デフォルトでは、アトリビュートはすべて省略可能です。また、オブジェクトの生成時に用意されている必要もありません。アトリビュートを必須にしたければrequiredオプションを真にするだけで結構です。

  has 'name' => (
      is       => 'ro',
      required => 1,
  );

ただし、この「required」が実際に何を意味しているかについては何点か注意しておいた方がよいでしょう。

基本的に、requiredの意味は、このアトリビュート(name)はコンストラクタに渡す必要がある、ということだけです。値については何も指定していませんから、undefということもありえます。

また、たとえ必須アトリビュートであってもクリア用のメソッドが定義されていれば値をクリアすることはできるので、オブジェクト生成後は値がセットされていない、ということもありえます。

つまり、本当にアトリビュートを必須にしたいのであれば、クリア用のメソッドを定義するのはあまり意味がありません。ただし、場合によっては必須アトリビュートに「プライベートの」clearerpredicateを用意しておくのは便利なこともあります。

デフォルト値とビルダーメソッド

アトリビュートにはデフォルト値を持たせることもできます。Mooseでデフォルト値を指定するには2通りの方法があります。

もっとも簡単なのは、defaultオプションに単にリファレンスではないスカラ値を渡す方法です。

  has 'size' => (
      is        => 'ro',
      default   => 'medium',
      predicate => 'has_size',
  );

コンストラクタにsizeアトリビュートが渡されなかった場合、その値はmediumになります。

  my $person = Person->new();
  $person->size; # medium
  $person->has_size; # true

また、defaultにサブルーチンのリファレンスを渡すこともできます。このリファレンスはオブジェクトのメソッドという形で呼ばれます。

  has 'size' => (
      is => 'ro',
      default =>
          sub { ( 'small', 'medium', 'large' )[ int( rand 3 ) ] },
      predicate => 'has_size',
  );

くだらない例ですが、新しいオブジェクトを生成するたびにこのサブルーチンが呼ばれるというポイントはおわかりでしょうか。

defaultのサブルーチンリファレンスを渡した場合は、追加のパラメータなしのオブジェクトメソッドとして呼ばれます。

  has 'size' => (
      is => 'ro',
      default => sub {
          my $self = shift;

          return $self->height > 200 ? 'big' : 'average';
      },
  );

オブジェクト生成時にdefaultが呼ばれても、ほかのアトリビュートにはまだ値がセットされていないこともあります。アトリビュートのデフォルト値がそのオブジェクトのほかの状態に依存している場合は、そのアトリビュートをlazyにできます。遅延評価については次のセクションで扱います。

デフォルト値に何らかのリファレンスを渡したい場合は、サブルーチンの返り値として渡す必要があります。そうしないと、Perlはそのリファレンスを1回だけインスタンス化したあと、それをすべてのオブジェクトで使い回してしまうためです。

  has 'mapping' => (
      is      => 'ro',
      default => {}, # wrong!
  );

Mooseはデフォルト値にサブルーチンリファレンス以外の裸のリファレンスを渡した場合はエラーが発生します。

これは、裸のリファレンスを渡せるようにしてしまうと、デフォルトでマッピングされるアトリビュートが多くのオブジェクトで共有されてしまうことになりがちだからです。だから、裸のリファレンスを渡すのではなく、サブルーチンリファレンスでくるんでください。

  has 'mapping' => (
      is      => 'ro',
      default => sub { {} }, # right!
  );

ちょっとわかりづらいですが、これがPerlのやり方なのです。

また、サブルーチンリファレンスを使うかわりに、アトリビュートにbuilderメソッドを定義する方法もあります。

  has 'size' => (
      is        => 'ro',
      builder   => '_build_size',
      predicate => 'has_size',
  );

  sub _build_size {
      return ( 'small', 'medium', 'large' )[ int( rand 3 ) ];
  }

こちらにはいくつか利点があります。まず、コードのかたまりを独自の名前を持つメソッドに移せますから、読みやすくなりますし、コードもすっきりします。

だから、ごく単純なデフォルト値を渡すとき以外は、defaultのかわりにbuilderを使うことを強くおすすめします。

builderも、defaultと同じく追加のパラメータなしのオブジェクトメソッドとして呼ばれます。

ビルダーを使うとサブクラス化できるようになります

builderは「名前で」呼ばれますから、Perlがメソッド解決を行います。つまり、builderメソッドは継承もオーバーライドもできるということです。

Personクラスをサブクラス化する場合、_build_sizeもオーバーライドできます。

  package Lilliputian;

  use Moose;
  extends 'Person';

  sub _build_size { return 'small' }

ビルダーはロールから合成することもできます

builderは名前で呼ばれますから、ロールとも共存できます。たとえば、ロールの方でアトリビュートを提供して、builderは取り込む側のクラスに要求するということも可能です。

  package HasSize;
  use Moose::Role;

  requires '_build_size';

  has 'size' => (
      is      => 'ro',
      lazy    => 1,
      builder => '_build_size',
  );

  package Lilliputian;
  use Moose;

  with 'HasSize';

  sub _build_size { return 'small' }

ロールについてはMoose::Manual::Rolesをご覧ください。

遅延評価とlazy_build

アトリビュートをlazyにすると、アトリビュートの初期化を遅らせることができます。

  has 'size' => (
      is      => 'ro',
      lazy    => 1,
      builder => '_build_size',
  );

lazyが真の場合、デフォルト値が生成されるのはオブジェクト生成時ではなく、はじめて読み取り用のメソッドが呼ばれたときになります。遅延評価した方がよい場合はいくつかあります。

まず、アトリビュートのデフォルト値がほかのアトリビュートに依存している場合です。この場合、アトリビュートは「かならず」lazyにしなければなりません。オブジェクト生成時にはデフォルト値が予想通りの順便で生成されるとは限らないので、デフォルト生成時にほかのアトリビュートが初期化されていることを期待するわけにはいかないのです。

次に、必要になるまではデフォルトを生成する理由がない場合も多々あります。アトリビュートをlazyにしておけば、必要になるまでアトリビュートの生成コストはかかりません。そのアトリビュートが「一度も」必要とされなかった場合は、CPU時間をいくらか節約できます。

だから、当然ながらビルダーを使っていたり、デフォルト値が単純とはいえないアトリビュートについてはかならずlazyにすることをおすすめします。

設定を簡単にしたい場合は単にlazy_buildというアトリビュートオプションを指定するだけでも結構です。こうするといくつかのオプションをまとめてくれます。

  has 'size' => (
      is         => 'ro',
      lazy_build => 1,
  );

これは、以下のすべてのオプションを指定するのと同じ働きをします。

  has 'size' => (
      is        => 'ro',
      lazy      => 1,
      builder   => '_build_size',
      clearer   => 'clear_size',
      predicate => 'has_size',
  );

アトリビュート名がアンダースコア(_)で始まる場合、クリア用のメソッドと断定用のメソッドもアンダースコアで始まります。

  has '_size' => (
      is         => 'ro',
      lazy_build => 1,
  );

上の例は、このようになります。

  has '_size' => (
      is        => 'ro',
      lazy      => 1,
      builder   => '_build__size',
      clearer   => '_clear_size',
      predicate => '_has_size',
  );

ビルダー名もアンダースコアが2つになっていることに注意してください。内部的には単にアトリビュート名の前に「_build_」をつけているだけです。

lazy_buildが生成する名前が気に入らない場合は、いつでも自分で名前を指定できます。

  has 'size' => (
      is         => 'ro',
      lazy_build => 1,
      clearer    => '_clear_size',
  );

自分で明示的に指定したオプションはかならずMoose内部のデフォルトより優先されます。

コンストラクタのパラメータ (init_arg)

アトリビュートは、デフォルトではアトリビュート名をキーにしてクラスのコンストラクタに値を渡せるようになっていますが、場合によってはコンストラクタのパラメータとしては別の名前を使いたいとか、コンストラクタ経由ではアトリビュートに値をセットできないようにしたいこともあるかもしれません。

いずれの場合でも、これはinit_argオプションを利用すれば実現できます。

  has 'bigness' => (
      is       => 'ro',
      init_arg => 'size',
  );

これで「bigness」という名前がついているものの、コンストラクタに渡すときにはsizeという名前を使うアトリビュートができます。

もっと役に立つ例としては、コンストラクタ経由ではアトリビュートに値をセットできないようにできます。これはプライベートなアトリビュートの場合、特に便利です。

  has '_genetic_code' => (
      is         => 'ro',
      lazy_build => 1,
      init_arg   => undef,
  );

init_argundefにすると、新しいオブジェクトを生成するときにこのアトリビュートに値をセットすることはできなくなります。

ウィークリファレンス

Mooseにはウィークリファレンスのサポートが組み込まれています。weak_refオプションを真値にすると、アトリビュートに値が書き込まれたときはかならずScalar::Util::weakenを呼ぶようになります。

  has 'parent' => (
      is       => 'rw',
      weak_ref => 1,
  );

  $node->parent($parent_node);

これは循環参照になるかもしれないオブジェクトを作るときには非常に便利です。

トリガ

triggerはアトリビュートに値がセットされたときにかならず呼ばれるサブルーチンです。

  has 'size' => (
      is      => 'rw',
      trigger => \&_size_set,
  );

  sub _size_set {
      my ( $self, $size, $old_size ) = @_;

      my $msg = $self->name;

      if ( @_ > 2 ) {
          $msg .= " - old size was $old_size";
      }

      $msg .= " - size is now $size";
      warn $msg;
  }

トリガは新しい値がセットされた「あと」で呼び出されます。このトリガはメソッドとして呼び出され、新しい値と古い値を引数として呼び出されます。もしアトリビュートの値がその前に一回も設定されていない場合は、新しい値のみが渡されます。この特性を利用することにより、アトリビュートが新規に値を設定された時と、前の値がundefだった場合の違いを認識することができます。

これは、afterメソッドモディファイアとは2つの点で異なります。まず、トリガの方は、アクセサメソッドが呼ばれたときかならず(読み込んだときも書き込んだときも)呼ばれるのではなく、アトリビュートに値がセットされたときだけ呼ばれます。また、アトリビュートの値がコンストラクタに渡されたときにも呼ばれます。

ただし、トリガはdefaultbuilderで値が初期化されたときには呼ばれ「ません」。

アトリビュートの型

アトリビュートは特定の型しか受け入れないように制限できます。

  has 'first_name' => (
      is  => 'ro',
      isa => 'Str',
  );

こうすると、first_nameアトリビュートはかならず文字列でなければならなくなります。

また、アトリビュートが特定のロールを持つオブジェクトだけを受け入れるように指定するショートカットもあります。

  has 'weapon' => (
      is   => 'rw',
      does => 'MyApp::Weapon',
  );

型システムについての詳細はMoose::Manual::Typesのドキュメントをご覧ください。

委譲

アトリビュートの値に処理を委譲するだけのメソッドを定義することもできます。

  has 'hair_color' => (
      is      => 'ro',
      isa     => 'Graphics::Color::RGB',
      handles => { hair_color_hex => 'as_hex_string' },
  );

こうすると、オブジェクトにhair_color_hexという新しいメソッドが追加されるのですが、hair_color_hexを呼んでも、内部的にはただ$self->hair_color->as_hex_stringを呼び出すだけです。

委譲メソッドの設定の仕方についてはMoose::Manual::Delegationをご覧ください。

メタクラスとトレート

Mooseでもっとも便利な機能のひとつに、独自のメタクラスやメタクラスのトレートを使えばあらゆる点で拡張できることがあげられます。

アトリビュートを宣言するときも、アトリビュートのメタクラスやトレートを宣言できます。

  has 'mapping' => (
      metaclass => 'Hash',
      is        => 'ro',
      default   => sub { {} },
  );

この例でいうと、Hashというメタクラスは、実際にはMoose::Meta::Attribute::Native::Trait::Hashを指します。この他にも NumberCounterStringBoolArrayのネイティブトレートを提供します。

アトリビュートにはひとつないし複数のトレートを組み込むこともできます。

  use MooseX::MetaDescription;

  has 'size' => (
      is          => 'ro',
      traits      => ['MooseX::MetaDescription::Meta::Trait'],
      description => {
          html_widget  => 'text_input',
          serialize_as => 'element',
      },
  );

トレートの利点は、複数のトレートを簡単に組み合わせられることです(トレートは、実はロールに皮をかぶせたものなのです)。

CPANにはアトリビュートに便利なメタクラスやトレートを提供するMooseXモジュールがいくつもあります(Moose::Manual::MooseXにいくつか例が紹介されています)。また、自前のメタクラスやトレートを書くこともできます。例についてはMoose::Cookbookの「メタMoose」および「Mooseを拡張する」以下に記載されているレシピをご覧ください。

アトリビュートの継承

デフォルトでは、子クラスは親(祖先)クラスのすべてのアトリビュートをそのまま継承します。ただし、子クラスの中で継承したアトリビュートの一部の特徴を明示的に変更することもできます。

サブクラスでオーバーライドできるオプションは次の通りです。

  • default

  • coerce

  • required

  • documentation

  • lazy

  • isa

  • handles

  • builder

  • metaclass

  • traits

アトリビュートをオーバーライドするのは、アトリビュート名の前にプラス記号(+)をつけるだけでできます。

  package LazyPerson;

  use Moose;

  extends 'Person';

  has '+first_name' => (
      lazy    => 1,
      default => 'Bill',
  );

これでLazyPersonfirst_nameアトリビュートは遅延評価をするようになり、デフォルト値は'Bill'になります。

継承したアトリビュートの型(isa)を変える場合は慎重にすることをおすすめします。

複数アトリビュート定義時のショートカット

名前以外の定義が同じアトリビュートを複数定義する場合は、それらを一度に定義することができます:

  package Point;

  use Moose;

  has [ 'x', 'y' ] => ( is => 'ro', isa => 'Int' );

もちろん、hasはただの関数ですので、ループ内で呼べば同じ事ができます:

  for my $name ( qw( x y ) ) {
      my $builder = '_build_' . $name;
      has $name => ( is => 'ro', isa => 'Int', builder => $builder );
  }

もっと詳しく知りたい方は

Mooseのアトリビュートは複雑なテーマです。このドキュメントではそのいくつかの側面をさらっと紹介したにすぎません。さらに理解を深めたい方は、Moose::Manual::DelegationMoose::Manual::Typesのドキュメントを読むことをおすすめします。

その他のオプション

Mooseのアトリビュートには多くのオプションがあります。ここで紹介するものはよりモダンな機能で置き換えられてしまいましたが、完璧を期すために取り上げました。

documentationオプション

アトリビュートのドキュメントとなる文字列を指定できます。

  has 'first_name' => (
      is            => 'rw',
      documentation => q{The person's first (personal) name},
  );

Mooseがこの情報を利用することはいっさいありません。保存しておくだけです。

auto_derefオプション

アトリビュートが配列リファレンスまたはハッシュリファレンスの場合、auto_derefオプションが有効になっていると、読み取り用のメソッドから値を返すときにデリファレンスするようになります。

  my %map = $object->mapping;

このオプションが有効なのはアトリビュートが明示的にArrayRefまたはHashRefに型付けされている場合のみです。

ただし、そのようなアトリビュートには、さらにアトリビュートのアクセスや操作の幅が広がるMoose::Meta::Attribute::Nativeを利用することをおすすめします。

initializerオプション

Mooseにはinitializerというアトリビュートオプションがあります。これはbuilderと同じようなものですが、呼ばれるのはオブジェクト生成時「のみ」というところが異なります。

このオプションはClass::MOPから継承しているのですが、(Moose専用の) builderを使うことをおすすめします。

作者

Dave Rolsky <[email protected]>

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

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