Introduction to Moose
Italian Perl Workshop 2009
Mike Whitaker - BBC / EnlightenedPerl.org
About me
About me
• BBC iPlayer team
About me
• BBC iPlayer team
• ex-Yahoo Europe (News team)
About me
• BBC iPlayer team
• ex-Yahoo Europe (News team)
• ex-CricInfo.com
About me
• BBC iPlayer team
• ex-Yahoo Europe (News team)
• ex-CricInfo.com
• Board member of EnlightenedPerl.org
Why Moose?
Why Moose?
• For that, we need a bit of history:
In the Beginning...
In the Beginning...
• ...there was Perl 5
In the Beginning...
• ...there was Perl 5
• DIY OO
In the Beginning...
• ...there was Perl 5
• DIY OO
• perldoc perltoot
In the Beginning...
• ...there was Perl 5
• DIY OO
• perldoc perltoot
package Foo;
sub new {
my $self = {};
return bless $self;
}
In the Beginning...
package Foo;
• ...there was Perl 5 sub new {
my $self = {};
• DIY OO }
return bless $self;
• perldoc perltoot sub bar {
my $self = shift;
my $val = shift;
package Foo; if (defined $val) {
$self->{bar} = $val;
sub new { }
my $self = {}; else {
return bless $self; return $self->{bar};
} }
}
Perl 5 Native OO
Perl 5 Native OO
• A bit of a hack
Perl 5 Native OO
• A bit of a hack
• Not really a first-class part of the language
Perl 5 Native OO
• A bit of a hack
• Not really a first-class part of the language
• Have to roll your own
So...
People started writing classes to make it
easier....
What is Moose?
• (Post)modern Perl OO framework
• Deals with the overhead of implementing
OO, allows you to get on with coding
What is Moose?
• (Post)modern Perl OO framework
• Deals with the overhead of implementing
OO, allows you to get on with coding
• Meta-object implementation - allows you to
introspect your classes and objects
What is Moose?
• (Post)modern Perl OO framework
• Deals with the overhead of implementing
OO, allows you to get on with coding
• Meta-object implementation - allows you to
introspect your classes and objects
• Already used in production software
My First Class
package Person;
use Moose;
has name => (
is => 'rw',
);
no Moose;
Using the class
use Person;
my $person = Person->new();
$person->name('Mike');
print $person->name();
> Mike
Adding a method
package Person;
use Moose;
has name => ( is => 'rw' );
sub introduce {
print "I'm " . $self->name;
}
Using it
use Person;
my $person = Person->new();
$person->name('Mike');
$person->introduce();
> I'm Mike
Read-only attributes
package Person;
use Moose;
has name => ( is => 'ro' );
sub introduce {
print "I'm " . $self->name;
}
Read-only attributes (2)
use Person;
my $person = Person->new();
$person->name('Mike');
# boom!
Read-only attributes (3)
use Person;
my $person = Person->new(
{ name => 'Mike' },
);
# no boom today!
Types
package Person;
use Moose;
has name => (
is => 'rw',
isa => 'Str',
);
Types (2)
use Person;
my $person = Person->new();
$person->name('Mike');
# shiny!
$person->name( [] );
# boom!
# Not a Str
Types (3)
package Person;
use Moose;
has name => ( is => 'rw', isa => 'Str' );
has dob => (
is => 'rw',
isa => 'DateTime',
);
Types (4)
use Person;
my $person = Person->new(
{ name => 'Mike' }
);
my $dob = DateTime->new (
year => 1963, month => 8, day => 5,
);
$person->dob($dob);
print $person->dob->year;
> 1963
Subclassing
package Student;
use Moose;
extends qw/Person/;
has overdraft => (
isa => 'Bool',
is => 'rw',
);
Compound Types
package Student;
use Moose;
extends qw/Person/;
has classes => (
isa => 'HashRef[Str]',
is => 'rw',
);
Required attributes
package Student;
use Moose;
extends qw/Person/;
# ...
has course => (
isa => 'Str',
is => 'ro',
required => 1,
);
Setting defaults
package Student;
use Moose;
has 'overdraft' => (
isa => 'Bool',
default => 1,
);
But, what if...
package BankAccount;
sub balance {
# yadda yadda
}
And then...
package Student;
use Moose;
extends qw/Person/;
has 'account' => (
isa => 'BankAccount',
is => 'rw',
required => 1,
);
Lazy default
has 'overdraft' => (
isa => 'Bool',
is => 'ro',
lazy => 1,
default => sub {
shift->account->balance < 0;
}
);
Lazy builder
has 'overdraft' => (
isa => 'Bool',
is => 'ro',
lazy_build => 1,
init_arg => undef,
);
sub _build_overdraft {
return shift->account->balance < 0;
}
Method modifiers
package Student;
# yadda yadda
after 'introduce' => sub {
my $self = shift;
print ', studying ' . $self->course;
}
Using it
use Student;
my $person = Student->new(
name => 'Mike',
course => 'Computer Science',
);
$person->introduce();
> I'm Mike, studying Computer Science
Method modifiers (2)
package Student;
# yadda yadda
around 'introduce' => sub {
my ($next, $self, @args) = @_;
print "Hi, ";
$self->$next(@args);
print ', studying ' . $self->course;
}
Using around
use Student;
my $person = Student->new(
name => 'Mike',
course => 'Computer Science',
);
$person->introduce();
> Hi, I'm Mike, studying Computer Science
Roles
Roles
• Code fragments that define and provide a
small, reusable behaviour
Roles
• Code fragments that define and provide a
small, reusable behaviour
• Not inherited - methods become part of
consuming class
Roles
• Code fragments that define and provide a
small, reusable behaviour
• Not inherited - methods become part of
consuming class
• can be overriden by comsuming class
Roles
• Code fragments that define and provide a
small, reusable behaviour
• Not inherited - methods become part of
consuming class
• can be overriden by comsuming class
• like MI but better!
Example role
package Age;
use Moose::Role;
has dob => (
isa => 'DateTime', is =>'ro'
);
sub age {
return DateTime->now
->subtract( shift->dob() )
->years;
}
Using a role
package Person;
use Moose;
with qw/Age/;
has name => ( isa => 'Str', is => 'ro');
sub introduce {
print "I'm " . $self->name .
', age ' . $self->age;
}
Using a role (2)
use Student;
my $person = Student->new(
name => 'Mike',
dob => ... # yadda yadda
course => 'CS',
);
$person->introduce();
> Hi, I'm Mike, age 45, studying CS
What we'd really like
use Student;
my $person = Student->new(
name => 'Mike',
dob => '05-08-1963',
course => 'CS',
);
Redefine our attr...
package Age;
use Moose::Role;
has dob => (
isa => 'DateStr',
is =>'ro',
);
Types to the rescue
use Moose::Util::TypeConstraints;
subtype 'DateStr'
=> as 'Str'
=> where {
/^dd-dd-dddd$/
};
Types to the rescue (2)
use Moose::Util::TypeConstraints;
class_type 'DateTime';
coerce 'DateTime' => from 'Str'
=> via {
my ($d, $m, $y) = split /-/, $_;
return DateTime->new(
year => $y, month => $m, day => $d
);
};
And then...
package Age;
use Moose::Role;
has dob => (
isa => 'DateTime',
is =>'ro',
coerce => 1, # very important!
);
Et voila...
use Student;
my $person = Student->new(
name => 'Mike',
dob => '05-08-1963',
course => 'CS',
);
print $person->age();
> 45
Roles as interfaces
package Earnings;
use Moose::Role;
requires qw/annual_income/;
sub monthly_income {
return shift->annual_income / 12;
}
Roles as interfaces (2)
package Person;
with qw/Earnings/;
package Student;
extends qw/Person/;
sub annual_income {
my $self = shift;
return $self->grant_amount;
}
Role gotchas
Role gotchas
• Roles cannot use extends
Role gotchas
• Roles cannot use extends
• requires only works with actual methods :(
Role gotchas
• Roles cannot use extends
• requires only works with actual methods :(
• for now, at least
Role gotchas
• Roles cannot use extends
• requires only works with actual methods :(
• for now, at least
• watch out for method and attribute
conflicts
Method Delegation
package Degree;
use Moose;
use Moose::Utils::TypeConstraints;
enum 'Grade' => qw/I IIi IIii III/;
has 'grade' => ( isa => 'Grade' );
has 'awarded' => (
isa => 'DateTime',
coerce => 1,
);
Method Delegation (2)
package Graduate;
use Moose;
extends qw/Student/;
has 'degree' => (
isa => 'Degree',
is => 'rw',
);
More on attributes
package Student;
use MooseX::Util::TypeConstraints;
use Moose;
extends qw/Person/;
enum 'Result' => qw/pass fail/;
has classes => (
isa => 'HashRef[Result]',
is => 'rw',
predicate => 'has_classes',
);
Even more Java-like?
use MooseX::Declare;
class Student extends Person with
Earnings {
has 'id' => ( isa => 'StudentID' );
method attend ( Str $class, ... ) {
# yadda yadda
}
}
How is this
implemented?
Lasciate ogne
speranza,
voi ch'intrate
But seriously...
But seriously...
• Devel::Declare sticks the interpreter on
pause while it plays with your source
But seriously...
• Devel::Declare sticks the interpreter on
pause while it plays with your source
• NOT a source filter
But seriously...
• Devel::Declare sticks the interpreter on
pause while it plays with your source
• NOT a source filter
• You don't need to know what's going on...
Why Moose?
Why Moose?
• Less code = fewer errors
Why Moose?
• Less code = fewer errors
• Don't waste time on class implementation
Why Moose?
• Less code = fewer errors
• Don't waste time on class implementation
• Better object model
Why Moose?
• Less code = fewer errors
• Don't waste time on class implementation
• Better object model
• "We do this so you don't have to"
Why Moose?
• Less code = fewer errors
• Don't waste time on class implementation
• Better object model
• "We do this so you don't have to"
• Less need for low level tests
Why Moose?
• Less code = fewer errors
• Don't waste time on class implementation
• Better object model
• "We do this so you don't have to"
• Less need for low level tests
• More descriptive code
Further Reading
Further Reading
• http://iinteractive.com/moose/
Further Reading
• http://iinteractive.com/moose/
• Moose::Cookbook (CPAN)
Further Reading
• http://iinteractive.com/moose/
• Moose::Cookbook (CPAN)
• http://www.stonehenge.com/merlyn/
LinuxMag/col94.html
Complex Example
package Student;
use Moose;
# yadda
has 'id' => (
isa => 'StudentID',
is => 'rw',
);
Complex Example (2)
use MooseX::ClassAttribute;
class_has 'db' => (
isa => 'Str', is => 'rw',
default => 'dbi:SQLite:dbname=s.db'
);
class_has _schema => (
isa => 'Person::Schema',
lazy_build => 1, is => 'ro',
);
Complex Example (3)
sub _build__schema {
my $self = shift;
return Person::Schema->connect(
$self->db
);
}
Complex Example (4)
use Config::General;
override BUILDARGS => sub {
my $args = super;
my %conf = Config::General
->new( 'db.conf' )->getall();
$args->{db} = $conf{db}
if exists $conf{db};
};
Complex Example (5)
has _row => {
isa => 'DBIx::Class::Row',
lazy_build => 1,
}
sub _build__row {
return shift->_schema
->resultset('Student')
->find($self->id);
}
Complex Example (6)
has _row => {
isa => 'DBIx::Class::Row',
lazy_build => 1,
handles => [qw/address gender/],
}
Complex Example (7)
my $student = Student->new(
name => 'Mike', id => '3056',
);
print $st->address();
# What happens here?
What happens?
What happens?
• address method is handled by _row
What happens?
• address method is handled by _row
• _row is lazy_build, so calls _build__row
What happens?
• address method is handled by _row
• _row is lazy_build, so calls _build__row
• which requires _schema, ALSO
lazy_build
What happens?
• address method is handled by _row
• _row is lazy_build, so calls _build__row
• which requires _schema, ALSO
lazy_build
• which uses id to find the right row in db
What happens?
• address method is handled by _row
• _row is lazy_build, so calls _build__row
• which requires _schema, ALSO
lazy_build
• which uses id to find the right row in db
• Tadaa!
But...
# What if the student's ID changes?
has 'id' => (
isa => 'StudentID',
is => 'rw',
trigger => '_build__row',
);
0 comments
Post a comment