Moose-0.92 > Moose::Manual::MOP

題名

Moose::Manual::MOP - Moose(とClass::MOP)のメタAPI

はじめに

MooseにはClass::MOPを下敷きにした強力なイントロスペクションAPIがあります。MOPというのはメタオブジェクトプロトコル(Meta-Object Protocol)の略称ですが、平たく言うと、クラスやアトリビュート、メソッドなどのイントロスペクションを行うためのAPIです。

実は、アトリビュートやbefore/after/aroundメソッドモディファイア、不変化といったMooseの中核となる機能の多くはClass::MOPが提供しているものです(ほとんどの場合、Mooseは既存のClass::MOPのクラスを利用したりサブクラス化して機能を追加しているのですが、中にはロールやaugmentモディファイア、型のように、完全にMoose独自の新機能もあります)。

MOPに興味がある方はぜひClass::MOPのことを学んでください。どのドキュメントを読めばよいかわかるようになります(探しているイントロスペクションのメソッドがMoose本体ではなくClass::MOPのクラスに定義されている、ということも多いのです)。

MOPは単に「読み取り専用」のイントロスペクションを提供するだけではありません。アトリビュートやメソッドを追加したり、ロールを組み込んだりできるようにもしてくれます(実際、Mooseの宣言的なシュガー関数はすべてMOPのAPIに薄皮をかぶせただけのものです)。

Mooseの拡張モジュールを書きたいのであれば、MOPのAPIについてもある程度学ぶ必要が出てきます。また、イントロスペクションメソッドは、ドキュメントや継承グラフを生成したり、実行時にほかのリフレクションを行ったりしたいときにも便利です。

このドキュメントは、メタAPIの完全なリファレンスではありません。ここで予定しているのは、目玉となる機能の一部を取り上げて、どのように動作するのか感覚をつかんでもらうことだけです。本当に理解するには、ほかのドキュメントをたくさん読む必要があるでしょうし、もしかすると多少はMooseの内部に潜り込む必要さえあるかもしれません。

やってみよう

メタAPIの世界に足を踏み入れる場合、クラスのメタクラスオブジェクト(Moose::Meta::Class)から始めるのがふつうです。これはクラスないしオブジェクトのmetaメソッドを呼ぶと利用できます。

  package User;

  use Moose;

  my $meta = __PACKAGE__->meta;

このmetaメソッドは、Mooseを使うとクラスに追加されます。

また、Class::MOP::Class->initialize($name)を使うと任意のクラスのメタクラスオブジェクトが得られます。クラスにメタメソッドがあるかどうかわからない場合はこちらの方が$class->metaを呼ぶより安全です。

Class::MOP::Class->initializeコンストラクタは、(Mooseなどを通じて)作成済みのメタクラスがあればそれを返し、なければ新しいClass::MOP::Classオブジェクトを返します。これはMooseを使っているクラスだけでなく、メタAPIのクラスや、Mooseをまったく使っていないクラスでも有効です。

メタクラスオブジェクトを使う

メタクラスオブジェクトを使うと、クラスのアトリビュートやメソッド、ロール、親クラスなどがわかります。たとえば、クラスのすべてのアトリビュートを見るにはこうします。

  for my $attr ( $meta->get_all_attributes ) {
      print $attr->name, "\n";
  }

get_all_attributesメソッドのドキュメントはClass::MOP::Classにあります。Mooseを使っているクラスの場合は、そのクラスと親クラスで定義されているアトリビュートのMoose::Meta::Attributeオブジェクトのリストが返ります。

メソッドのリストも得られます。

  for my $method ( $meta->get_all_methods ) {
      print $method->fully_qualified_name, "\n";
  }

今度はMoose::Meta::Methodのリストを回しています。なお、これらのオブジェクトの中には、実際にはMoose::Meta::Methodのサブクラスが混じっていることがあります(Mooseは、ラップされたメソッドや委譲されたメソッド、コンストラクタなどには別のクラスを使うためです)。

クラスの親クラスやサブクラスを見ることもできます。

  for my $class ( $meta->linearized_isa ) {
      print "$class\n";
  }

  for my $subclass ( $meta->subclasses ) {
      print "$subclass\n";
  }

なお、これらのメソッドはいずれもメタクラスオブジェクトではなく、クラスの「名前」を返します。

MOPを使ってクラスを変更する

メタクラスオブジェクトを使うとクラスに直接アトリビュートやメソッドを追加するといった変更を加えることができます。

たとえば、クラスにメソッドを加えてみましょう。

  $meta->add_method( 'say' => sub { print @_, "\n" } );

アトリビュートの場合はこうなります。

  $meta->add_attribute(
      name => 'size',
      is   => 'rw',
      isa  => 'Int',
  );

これは、PerlのシンタックスやMooseのシュガーを使ってメソッドやアトリビュートを定義するのに比べてはるかに面倒に見えますが、このAPIを使うと非常に強力な拡張モジュールを作れるようになります。

このマニュアルの別の場所でクラスを不変化する話をしたのを覚えている方もいるかもしれません。クラスを不変化するのはよい習慣ですが、不変化してからこれらの更新メソッドを呼ぶと例外が発生してしまいます。

クラスをふたたび可変にしたい場合は単純に$meta->make_mutableを呼んでください。変更が済んだら$meta->make_immutableを呼べばまた不変化できます。

ただし、このようなメタAPIがもっともよく使われているのは、Mooseの拡張モジュールの部品としてです。このような拡張モジュールは、クラスを不変化する前に実行されることを想定しているものです。

深入りしたくなったら

Mooseの拡張に興味があるなら、Moose::Cookbookの「メタ」や「拡張」についてのレシピはすべて読んでおくことをおすすめします。これらのレシピでは、MOPのさまざまな実例を紹介しています。

自分で拡張モジュールを書きたくなったとき、いちばんよい勉強法のひとつは、ほかの似たような拡張モジュールを探して、どのように動作しているか調べることです。また、おそらくさまざまなMoose::Meta::*クラスやClass::MOPディストリビューションなどのAPIドキュメントもいろいろ読む必要があるでしょう。

最後になりますが、MooseのメーリングリストやIRCでは質問も歓迎しています。メーリングリストやIRC、その他のリファレンスなどの情報はMoose.pmのドキュメントにあります。

作者

Dave Rolsky <autarch@urth.org> and Stevan Little <stevan@iinteractive.com>

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

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.