Moose

Yuval Kogman

Moose はなにではないか

Moose Is Not

Mooseとはなにか

Moose Is

Mooseとはなにか

Moose Is

シンプルな例

A Simple Example


package Person;

use strict;
use warnings;

sub new {
    my ($class) = @_;

    return bless {
        name => '',
        age  => undef,
    }, $class;
}

sub name {
    my ($self, $name) = @_;
    $self->{'name'} = $name if $name;
    return $self->{'name'};
}

sub age {
    my ($self, $age) = @_;
    $self->{'age'} = $age if $age;
    return $self->{'age'}; 
}

1;

シンプルなMooseの例

A Simple Moose Example


package Person;
use Moose;

has name => (is => 'rw');
has age  => (is => 'rw');

1;

シンプルなMooseの例(つづき)

A Simple Moose Example (cont.)

シンプルなMooseの例(つづき)

A Simple Moose Example (cont.)

今度はアトリビュートの機能を説明していくよ Now we’re going to discuss more features of the attributes

Mooseの例のバリエーション

Variations on a Moose Example


package Person;
use Moose;

has name => (
    is => 'rw', 
    isa => 'Str'
    default => 'Bob'
);

has staff => (
    is      => 'ro',
    isa     => 'ArrayRef',
    lazy    => 1,
    default => sub { [qw(Bob Alice Tim)] },
);

default と isa が追加されてます Adds default, isa

Mooseの例のバリエーション(つづき)

Variations on a Moose Example (cont.)

デフォルトの話 discusses default

リファレンスでないと想定外の共有がむずかしくなる non refs make accidental sharing hard

Mooseの例のバリエーション(つづき)

Variations on a Moose Example (cont.)


            has 'date' => (isa => 'DateTime'); # DWIM

isa, 型の制約 isa, type constraints

Typical Family


        subtype 'Ref'
            => as 'Defined'
            => where {  ref($_) };

        subtype 'Object'
            => as 'Ref'
            => where { blessed($_) }

型の階層構造 type hierarchy

型の強制変換

Some Type of Coercion


package Employee;
use Moose;
use Moose::Util::TypeConstraints;
extends qw(Person);

class_type 'Manager';

coerce 'Manager' 
    => from 'Str'
    => via { Manager->new( name => $_) };

has manager =>  (
    is => 'ro',
    isa => 'Manager',
    required => 1, 
    coerce => 1,
);

型の強制変換(つづき)

Some Type of Coercion (cont.)


# 型制約のキーワードをインポート(import type constraint keywords)
use Moose::Util::TypeConstraints;


# オブジェクトのサブタイプであるマネージャーを定義(define Manager, a subtype of Object)
class_type "Manager";


# 変換を定義する(define the conversion)
coerce 'Manager' 
    => from 'Str'
    => via { Manager->new( name => $_) };


# アトリビュートごとに有効にする(enable it per attribute)
has manager =>  (
    …
    coerce => 1,
);

例を細かく見ていくよ breakdown of the example

クラスの型はMooseのクラスすべてに自動的に用意されます class types are automatically created for all Moose classes

伝統的な委譲

Conventional Delegates


package Employee;
use Moose;
extends qw(Person);

has manager =>  (
    is => 'ro',
    isa => 'Manager',
    handles => {
        manager_name => 'name',
        coworkers    => 'staff',
    }
);

伝統的な委譲(つづき)

Conventional Delegates (cont.)


has phone => (
    ...
    handles => [qw(number extension)],
);

伝統的な委譲(つづき)

Conventional Delegates (cont.)


has phone => (
    isa => "Phone"
    handles => qr/$method_regex/,
);

伝統的な委譲(つづき)

Conventional Delegates (cont.)


has phone => (
    ...
    handles => "Dialing", # a role
);

伝統的じゃない委譲

UnConventional Delegates


package Company;
use Moose;
use MooseX::AttributeHelpers;

has employees => (
    metaclass => 'Collection::Array',
    isa => 'ArrayRef[Employees]',
    is => 'rw',
    provides => {
        push  => 'add_employee',
        pop   => 'remove_employee',
        count => 'number_of_employees',
        empty => 'any_employees',
    }
);

メソッド変更のセカイ

Modified Methods


before 'employees' => sub { warn 'calling employees' };

after 'employees' => sub { warn 'finished calling employees' };

メソッド変更のセカイ(つづき)

Modified Methods (cont.)


around 'employees' => sub { 
    my ($next, $self, @args) = @_;
    ...
    my @return = $self->$next(@args);
    ...
    return @return;
};

メソッド変更のセカイ(つづき)

Modified Methods (cont.)


package Employee;
use Moose;

sub do_work {
    my $self = shift;

    $self->punch_in;

    inner(); # call subclass here

    $self->punch_out;
}

メソッド変更のセカイ(つづき)

Modified Methods (cont.)


package Employee::Chef;
use Moose;

extends qw(Employee);

augment do_work => sub {
    my $self = shift;

    while ( @burgers ) {
        $self->flip_burger(shift @burgers);
    }
}

$chef->do_work; # punch in, flip burgers, punch out

型についての余談

Some Type of Digression


has employees => (
    is => 'rw',
    isa => 'ArrayRef[Employee]',
);

has shopping_carts => (
    is => 'rw',
    isa => 'ArrayRef[ArrayRef[ShinyBead]]'
);

型システムの機能についてちょっと説明していくよ Going to go into features of the type system for a bit

パラメータ付きの型 Parametrized types

型についての余談(つづき)

Some Type of Digression (cont.)


has language => (
    is => 'rw',
    isa => 'English | Welsh | Scots | Gaelic',
);  

has member => (
    is => 'rw',
    isa => 'Employee | ArrayRef[Employee|Group]',
);

型の結合 Union types

型についての余談(つづき)

Some Type of Digression (cont.)


package Foo;
use Moose;
use Moose::Util::TypeConstraints;
use Test::Deep qw(eq_deeply ...);

type 'SomethingTricky' 
    => where {
        eq_deeply( $_, ... );
    };

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

Test::Deppのカスタムバリデータ Test::Deep custom validator

CPANからどんなバリデータでも持ってこられる Can use any validation from the CPAN

パラメータ付きの型の強制変換

Some Parametrized Type of Coercion


use Moose::Util::TypeConstraints;   
subtype 'ArrayRef[Employee]' => as 'ArrayRef';

coerce 'ArrayRef[Employee]' 
    => from 'ArrayRef[Str]' 
    => via { [ map { Employee->new( name => $_ ) } @$_ ] };

has staff => (
    is         => 'ro',
    isa        => 'ArrayRef[Employee]',
    lazy       => 1,
    default    => sub { [qw(Bob Alice Tim)] },
    coerce     => 1,
);

ArrayRef[Str] から ArrayRef[Employee] に強制変換 coerce parametrized ArrayRef[Employee] from ArrayRef[Str]

強制変換は ‘default’ の返り値にも適用されます coercions can be applied to ‘default’ return values

MooseのRole

Role of the Moose

MooseのRole(つづき)

Role of the Moose (cont.)

再利用可能な小さな動作の例 Some examples of small reusable behaviors

Paramは連携に便利 Param is good for interacting with e.g. CGI::Expand or similar modules

MooseのRole(つづき)

Role of the Moose (cont.)


package Minion;
use Moose;

extends qw(Employee);

with qw(Salaried::Hourly);

package Boss;
use Moose;

extends qw(Employee);

with qw(Salaried::Monthly);

MooseのRole(つづき)

Role of the Moose (cont.)


package Salaried;
use Moose::Role;

requires qw('paycheck_amount');

MooseのRole(つづき)

Role of the Moose (cont.)


package Salaried::Hourly;
use Moose::Role;

with qw(Salaried);

has hourly_rate => (
    isa => "Num",
    is  => "rw",
    required => 1,
);

has logged_hours => (
    isa => "Num",
    is  => "rw",
    default => 0,
);

sub paycheck_amount {
    my $self = shift;

    $self->logged_hours * $self->hourly_rate;
}

MooseのRole(つづき)

Role of the Moose (cont.)

Roleはアトリビュートとメソッドを持てる roles can have attributes and methods Roleはインターフェースだけでなく動作を提供するもの roles provide behavior, not just interface

MooseのRole(つづき)

Role of the Moose (cont.)

MooseのRole(つづき)

Role of the Moose (cont.)

喧嘩両成敗というのは優先順位がないということ。ふたつのRoleが同じものを定義しようとした場合はコンパイル時にエラーになる(直さないといけない) symmetric composition means no precedence - if two roles try to define the same thing you get a compile time error that needs to be resolved 多重継承の場合はだまって最初のクラスを使うもんだと想定してしまう multiple inheritence silently assumes you want the first class

Roleは多重継承と違ってコンパイル時にエラーを吐く roles cause errors at compile time, unlike multiple inheritence

Roleは簡単にエラーを修正する方法も用意している roles also provide easy ways to fix the errors

MooseのRole(つづき)

Role of the Moose (cont.)


package First;
use Moose::Role;

sub dancing { ... }

package Second;
use Moose::Role

sub dancing { ... }

package Foo;
use Moose;

# KABOOM
with qw(
    First
    Second
);

衝突 conflicts

MooseのRole(つづき)

Role of the Moose (cont.)


package Ent::Puppy;
use Moose;

with (
    Tree => {
        alias => {
            bark => "tree_bark",
        },
    },
    Dog => {
        alias => {
            bark => "bark_sound",
        }
    },
);

sub bark {
    my $self = shift;

    if ( $condition ) {
        $self->tree_bark;
    } else {
        $self->bark_sound;
    }
}

組み込むときにパラメータをつける Composition parameters 衝突を解決するのも簡単だし Easier conflict resolution よりきめ細かいコントロールができるようになる Finer grained control

MOPはキレイ

MOPs Mean Cleanliness


my $class = $obj->meta; # $objのメタクラス($obj's metaclass)
my $meta = MyApp->meta; # MyAppのメタクラス(MyApp's metaclass)
my $emo  = $obj->meta->meta # メタメタ(even more meta)!

warn  $obj->meta->name;

内側からみてみる

Looking in From the Inside


my $metaclass = $self->meta; 

$metaclass->superclasses;

$metaclass->linearized_isa; # すべての先祖クラスを得ます(returns all ancestors)

$metaclass->has_method("foo");

$metaclass->compute_all_applicable_methods; # すべてのメソッド(継承されたものもふくめて)(returns all methods (inherited too))

$metaclass->has_attribute("bar");

# … lots more

simple introspection

内側からみてみる(つづき)

Looking in From the Inside (cont.)


Moose::Meta::Class->create( Bar =>
      version      => '0.01',
      superclasses => [ 'Foo' ],
      attributes => [
          Moose::Meta::Attribute->new( bar => ... ),
          Moose::Meta::Attribute->new( baz => ... ),
      ],
      methods => {
          calculate_bar => sub { ... },
          construct_baz => sub { ... }
      }
);

クラスはプログラム的につくることもできる Classes can be created programmatically

無名クラスも可 Anonymous classes also possible

内側からみてみる(つづき)

Looking in From the Inside (cont.)


has foo => ( is => "rw" );

__PACKAGE__->meta->add_attribute( foo => is => "rw" );

メタクラスのタンゴ

The Metaclass Tango


has employees => (
    metaclass => 'Collection::Array',
    ...
);

メタフレームを使う

Working in the Meta Frame

メタフレームを使う(つづき)

Working in the Meta Frame (cont.)

メタフレームを使う(つづき)

Working in the Meta Frame (cont.)

メタフレームを使う(つづき)

Working in the Meta Frame (cont.)

メタフレームを使う(つづき)

Working in the Meta Frame (cont.)

Moose の欠点

Drawbacks of Moose

Mooseのイイ!とこ

Benefits of Moose

Mooseのイイ!とこ(つづき)

Benefits of Moose (cont.)

Mooseのイイ!とこ(つづき)

Benefits of Moose (cont.)

Mooseのイイ!とこ(つづき)

Benefits of Moose (cont.)

Mooseのイイ!とこ(つづき)

Benefits of Moose (cont.)

Mooseのイイ!とこ(つづき)

Benefits of Moose (cont.)

おまけ

Bonus Material

Autobox


package Units::Bytes;
use Moose::Role;
use Moose::Autobox;

sub bytes     { $_[0]                   }    
sub kilobytes { $_[0] * 1024            }
sub megabytes { $_[0] * 1024->kilobytes }
sub gigabytes { $_[0] * 1024->megabytes }
sub terabytes { $_[0] * 1024->gigabytes }

Moose::Autobox->mixin_additional_role(SCALAR => 'Units::Bytes');

Autobox (つづき)

Autobox (cont.)


use Units::Bytes;
use Moose::Autobox;

is(5->bytes,     5,             '... got 5 bytes');
is(5->kilobytes, 5120,          '... got 5 kilobytes');
is(2->megabytes, 2097152,       '... got 2 megabytes');
is(1->gigabytes, 1073741824,    '... got 1 gigabyte');
is(2->terabytes, 2199023255552, '... got 2 terabyte');

perl -Moose


perl -Moose -e'has foo => (is=>q[rw]); Class->new(foo=>1)'

MooseX::POE


package Counter;
use MooseX::POE;

has count => (
    isa     => 'Int',
    is      => 'rw',
);

sub START {
    my ($self) = @_;
    $self->yield('increment');
}

event increment => sub {
    my ($self) = @_;
    warn "Count is now " . $self->count;
    $self->count( $self->count + 1 );
    $self->yield('increment') unless $self->count > 3;
};

Counter->new( count => 0 );
POE::Kernel->run();

Fin