Moose > Moose::Cookbook::Basics::Recipe1

題名

Moose::Cookbook::Basics::Recipe1 - (毎度おなじみ) Pointの例

概要

  package Point;
  use Moose;

  has 'x' => (isa => 'Int', is => 'rw', required => 1);
  has 'y' => (isa => 'Int', is => 'rw', required => 1);

  sub clear {
      my $self = shift;
      $self->x(0);
      $self->y(0);
  }

  package Point3D;
  use Moose;

  extends 'Point';

  has 'z' => (isa => 'Int', is => 'rw', required => 1);

  after 'clear' => sub {
      my $self = shift;
      $self->z(0);
  };

  package main;

  # hash or hashrefs are ok for the constructor
  my $point1 = Point->new(x => 5, y => 7);
  my $point2 = Point->new({x => 5, y => 7});

  my $point3d = Point3D->new(x => 5, y => 42, z => -5);

本文

これは古典的なPointの例です。Perl 6の黙示録12番からそのまま引用してきました。K&Rの古典的なCの教科書にも同じような例が載っています。

Perl 5のクラスはすべてそうですが、Mooseのクラスもパッケージの中で定義されます。strictwarningsはMooseの方で有効にしてくれますので、use Mooseとさえ書いておけばうっかりミスを防げます。

Mooseはロードされると呼び出し元のパッケージ内にシュガー関数をいくつかエクスポートします(つまり、私たちはMooseの「キーワード」になる関数をいくつかインポートします)。もっとも、これらの関数は本当にPerl 5のキーワードになるわけではありません。あくまでも私たちのパッケージにエクスポートされたPerlの関数にすぎないものです。

Mooseは自動的に私たちのパッケージをMoose::Objectのサブクラスにします。Moose::Objectクラスは、アトリビュートに対応したコンストラクタなど、さまざまな機能を提供してくれるものです。詳しくはMoose::Objectをご覧ください。

続いてはキーワードについてです。ここで紹介する最初のキーワードはhasです。これはクラスの中でインスタンスのアトリビュートを定義するものです。

  has 'x' => (isa => 'Int', is => 'rw', required => 1);

このようにすると、xという名前のアトリビュートが生成されます。isaパラメータは、このアトリビュートに保存される値はInt型の制約を満たすことを期待している、という意味です(1)。このアトリビュート用に生成されるアクセサは読み書き可能になります。

requires => 1というパラメータは、新しいオブジェクトを生成するときにはかならずこのアトリビュートを用意しなければならないという意味です(座標情報のないpointオブジェクトはあまり意味があるとは思えないので許していません)。

アトリビュートを定義したので、今度はメソッドを定義しましょう。Mooseの場合も、ふつうのPerl 5のオブジェクト指向と同じく、メソッドはパッケージ内で定義されたサブルーチンにすぎません。

  sub clear {
      my $self = shift;
      $self->x(0);
      $self->y(0);
  }

これでPointクラスはおしまいです。

続いて、PointのサブクラスであるPoint3Dを作りましょう。スーパークラスを宣言するにはMooseのキーワードであるextendsを使います。

  extends 'Point';

このextendsキーワードはuse baseによく似て、最初に必要があればクラスをロードするのですが、baseと違って、extendsキーワードはパッケージの@ISAにどんな値が含まれていても「上書き」してしまいます(use baseはパッケージの@ISAに値を追加します)。

個人的には、extendsの振る舞いの方が直感的だと思います。(2)

次に、Point3Dzという新しいアトリビュートを作ります。

  has 'z' => (isa => 'Int', is => 'rw', required => 1);

このアトリビュートはPointxyアトリビュートと同じようなものです。

afterキーワードは、Mooseでは「メソッドモディファイア」(アスペクト指向プログラミングでいう「アドバイス」)と呼ばれる機能です。

  after 'clear' => sub {
      my $self = shift;
      $self->z(0);
  };

Point3Dオブジェクトでclearが呼ばれると、モディファイアメソッドも呼ばれます(当然ながら、モディファイアが呼ばれるのは本物のメソッドの「あと」になります)。

この例では、本物のclearメソッドはPointから継承したものです。モディファイアメソッドは修飾されるメソッドと同じ引数を受け取ります(この場合は$selfのみです)。

もちろんafterモディファイアを使うのが唯一のやり方ではありません。これはPerlですからね。このようなコードでも同じ結果が得られます。

  sub clear {
      my $self = shift;
      $self->SUPER::clear();
      $self->z(0);
  }

また、overrideという別のモディファイアを使うこともできるでしょう。

  override 'clear' => sub {
      my $self = shift;
      super();
      $self->z(0);
  };

overrideモディファイアを使うとsuperというキーワードを利用できるようになります。これは非常にRuby的なやり方でスーパークラスのメソッドにディスパッチするためのものです。

メソッドモディファイアを使うかどうか、またどのモディファイアを使うかどうかの選択は、往々にして機能の問題であるのと同じくらいスタイルの問題でもあります。

PointMoose::Objectを継承しているので、Moose::Objectのデフォルトコンストラクタも継承します。

  my $point1 = Point->new(x => 5, y => 7);
  my $point2 = Point->new({x => 5, y => 7});

  my $point3d = Point3D->new(x => 5, y => 42, z => -5);

newコンストラクタはこのクラスで定義されている各アトリビュートの名前付き引数のペアを、ハッシュまたはハッシュリファレンスの形で受け付けます。この例の場合、アトリビュートは必須ですから、引数を渡さずにnewを呼ぶとエラーになります。

  my $point = Point->new( x => 5 ); # no y, kaboom!

これで、$point$point3dはほかのPerl 5オブジェクトと同様に使えるようになりました。どんなことができるかをもっと詳しく知りたい方はt/000_recipes/moose_cookbook_basics_recipe1.tのテストファイルをご覧ください。

Mooseオブジェクトはハッシュリファレンスにすぎません

ここまで紹介してきたことはいずれもいささか魔法じみて見えるかもしれませんが、大事なのは、Mooseのオブジェクトはフードをかぶったハッシュリファレンスにすぎない、ということ(3)。たとえば、$selfData::Dumperに渡してやれば、まさに期待通りの出力が得られます。

オブジェクトのデータ構造の中をつつき回すことさえできます(まったくおすすめできませんが)。

Mooseのオブジェクトがハッシュリファレンスであるということは、Mooseを使っていないクラスであってもハッシュリファレンスでさえあれば簡単にMooseで拡張できるということ。ハッシュリファレンス以外のクラスを拡張したい場合は、MooseX::InsideOutを試してみてください。

まとめ

このレシピではいくつかの基本的なコンセプト(アトリビュート、サブクラス、簡単なメソッドモディファイア)について説明しました。

1

Mooseには最初から多くの型制約が用意されています(Intもそのひとつです)。型制約システムについての詳細はMoose::Util::TypeConstraintsをご覧ください。

(2)

extendsキーワードは多重継承をサポートしています(単にすべてのスーパークラスを配列の形でextendsに渡すだけです)。

  extends 'Foo', 'Bar', 'Baz';
(3)

Mooseはblessされたハッシュリファレンス以外のインスタンスもサポートしています(グロブリファレンスなど。MooseX::GlobRef::Objectをご覧ください)。

参照

メソッドモディファイア

メソッドモディファイアの概念はCLOSのものをそのまま借用しました。下記のリンク先にはすばらしい説明があります。

http://www.gigamonkeys.com/book/object-reorientation-generic-functions.html

作者

Stevan Little <stevan@iinteractive.com>

Dave Rolsky <autarch@urth.org>

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

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.