日向夏特殊応援部隊

俺様向けメモ

Moose::Cookbook::Recipe10 - role, requires, with -

Perl OOPinterface, abstract の概念を持ち込む role, requires, with の話です。

ソースコード

ちょっと変えてあります。

package Equivalent;

use Moose::Role;

requires 'equal_to';

sub not_equal_to {
    my ($self, $other) = @_;
    not $self->equial_to($other);
}

package Comparable;

use Moose::Role;

with 'Equivalent';
requires 'compare';

sub equal_to {
    my ($self, $other) = @_;
    $self->compare($other) == 0;
}

sub greater_than {
    my ($self, $other) = @_;
    $self->compare($other) == 1;
}

sub less_than {
    my ($self, $other) = @_;
    $self->compare($other) == -1;
}

package Printable;

use Moose::Role;

requires 'to_string';

package JP::Currency;

use Moose;

with 'Comparable', 'Printable';

has 'amount' => ( is => 'rw', isa => 'Num', default => 0 );

sub compare {
    my ($self, $other) = @_;
    $self->amount <=> $other->amount;
}

sub to_string {
    my $self = shift;
    sprintf('\%0.2f YEN', $self->amount);
}

package main;

use Data::Dump qw(dump);
use Perl6::Say;
use Test::More qw(no_plan);

eval {
    package InvalidEquivalentImpl;

    use Moose;

    with 'Equivalent';
};
if (my $err = $@) {
    ok($err, 'Not implements');
    undef $@;
}

my @currencies = map { JP::Currency->new( amount => int(rand(10000)) + 1 ); } (0 .. 10);

say dump (map { $_->amount } @currencies);

my @sorted = sort { $a->compare($b) } @currencies;

say dump (map { $_->amount } @sorted);

for (my $i = 0; $i < @sorted; $i++) {
    if ($i > 0) {
        ok($sorted[$i]->greater_than($sorted[$i - 1]), 'greater than');
    }

    ok($sorted[$i]->equal_to($sorted[$i]), 'equal_to');
    say $sorted[$i]->to_string;

    if ($i < @sorted - 1) {
        ok($sorted[$i]->less_than($sorted[$i + 1]), 'less than');
    }
}

解説

use Moose::Role した package と requires

これは interface ないしは abstract class (実装も書ける点で) な役割を持ちます。

package Equivalent;

### この package が role であると言う宣言
use Moose::Role;

### equil_to メソッドを実装する事を期待する
requires 'equal_to';

### 実装されるであろう equal_to に依存するコードを Role に直接 impl 
sub not_equal_to {
    my ($self, $other) = @_;
    not $self->equial_to($other);
}

はい、適宜コメントふりました。

public abstract class Equivalent {
  public abstract Boolean equal_to(Equivalent other) {}
  public Boolean not_equal_to(Equivalent other) {
    return this.equal_to(other);
  }
}

ちと java のソースは動作するか知らないけどw*1 ニュアンスはこういう感じ。
もう説明する必要は無いと思います。

with
package Comparable;

use Moose::Role;

with 'Equivalent';
requires 'compare';

sub equal_to {
    my ($self, $other) = @_;
    $self->compare($other) == 0;
}

再び Comparable role を作るんですが、その際に Equivalent を実装する事も可能です。role を実装するぜって宣言が with になると。

ちなみに不正な実装した場合
eval {
    package InvalidEquivalentImpl;

    use Moose;

    with 'Equivalent';
};
if (my $err = $@) {
    ok($err, 'Not implements');
    undef $@;
}

Equivalent は requires 'equal_to' を実装する事を期待してるので、これはダメです。
こういうコードを書いた場合は実行時に即座にエラーとなります。

これが例えばインスタンス化した時にエラーになるんではやりづらいので、こうした定義時にエラー検出してくれるのは非常に嬉しいですね。

まとめ

  • use Moose::Role すると interface 的な物を作れる
    • role 中でも具体的な(concreteな)メソッドを定義出来る
    • requires で abstract method を宣言出来る (その role を実装するクラスに、requires で指定したメソッドの実装を強制出来る)
  • with で特定の role を実装すると言う宣言を行う事が出来る
    • role の要求に沿ってない場合は即座にエラーとなる

role かわゆす。

*1:と言うか構文的に誤りありそうだおw