Introduction to Moose
YAPC 2009
Moose Summed Up
- Declarative OO sugar
- Introspectable
- Extensible
Concepts
- Moose builds on Perl 5's native OO
- Borrows ideas from other languages, notably Perl 6
- Provides semantics for common operations
Classes
-
Classes have ...
- Attributes
- Methods
- Superclasses
- Method modifiers
- Constructor and destructor
- One metaclass object
- Classes do roles
Class Example
package Person;
use Moose;
- Poof, a Moose-based class!
Attributes
- Aka property, slot, field, member variable
- A piece of data owned by an object
Attributes
-
Attributes have ...
- Access-control (read-only vs read-write)
- An optional type
- Accessor methods
- Delegation methods
- Optional default value
- Many more features
- Stored in the object, but don't worry about that
Attribute Example
package Person;
use Moose;
has 'first_name' => ( is => 'rw' );
Methods
- Nothing fancy here, just Perl subroutines
package Person;
use Moose;
sub greet { ... }
Roles
- Classes do (or consume) roles
- Similar to mixins and Java interfaces
Roles
- Like classes, can have attributes, methods, do roles
- Roles can require methods
- Roles are composed (flattened) into classes
Role Example
package HasPermissions;
use Moose::Role;
has is_admin => ( is => 'rw' );
Role Example
And then ...
package Person;
use Moose;
with 'HasPermissions';
Method Modifiers
- AKA advice
- "Before foo(), do this first"
- "Do this after foo()
- "Put this code around foo()"
Before & After
before 'foo'
=> sub { warn 'About to call foo()' };
after 'foo'
=> sub { warn 'Leaving foo()' };
Around
around 'foo' => sub {
my $real_foo = shift;
my $self = shift;
warn 'Just before foo()';
my @return =
$self->$real_foo( @_, bar => 42 );
return ( @return, 'modify return values' );
};
Type Constraints
- NOT A FULL-BLOWN TYPE SYSTEM!
- But still darn useful
- Constrain attribute values
- Coerce from other types
Type Constraint Example
package Person;
use Moose;
has 'weight' => ( is => 'ro', isa => 'Int' );
# kaboom
Person->new( weight => 'fat' );
Delegation
- Attributes can define delegations
- Lets you hide some implementation details
- Fewer objects to chase around
Delegation
package Person;
use Moose;
has 'blog_uri' => (
is => 'rw',
isa => 'URI',
handles => { 'blog_hostname' => 'host' },
);
$person->blog_hostname;
# really calls $person->blog_uri->host
Constructors
- Moose creates
new()
for you
- Provide an optional
BUILDARGS()
and BUILD()
Destructors
- Provide an optional
DEMOLISH()
Moose Meta-API
- Answers questions like ...
- What methods does this class have?
- What are its parents?
- What attributes does it have (including inherited attributes)?
- What roles does it do?
- Much, much, more
Moose Meta-API
- Not just for introspection ...
- Add methods, attributes, roles, etc
- Extend and alter core features
Why Moose?
- A quick bit of propoganda ...
With Moose
package Person;
use Moose;
has last_name => (
is => 'rw',
isa => 'Str',
);
Without Moose
package Person;
use strict;
use warnings;
use Carp 'confess';
sub new {
my $class = shift;
my %args = @_;
my $self = {};
if (exists $args{last_name}) {
confess "Attribute (last_name) does not pass the type constraint because: "
. "Validation failed for 'Str' with value $args{last_name}"
if ref($args{last_name});
$self->{last_nane} = $args{last_name};
}
return bless $self, $class;
}
sub last_name {
my $self = shift;
if (@_) {
my $value = shift;
confess "Attribute (last_name) does not pass the type constraint because: "
. "Validation failed for 'Str' with value $value"
if ref($value);
$self->{last_name} = $value;
}
return $self->{last_name};
}
Side by side
package Person;
use Moose;
has last_name => (
is => 'rw',
isa => 'Str',
);
|
package Person;
use strict;
use warnings;
use Carp 'confess';
sub new {
my $class = shift;
my %args = @_;
my $self = {};
if (exists $args{last_name}) {
confess "Attribute (last_name) does not pass the type constraint because: "
. "Validation failed for 'Str' with value $args{last_name}"
if ref($args{last_name});
$self->{last_nane} = $args{last_name};
}
return bless $self, $class;
}
sub last_name {
my $self = shift;
if (@_) {
my $value = shift;
confess "Attribute (last_name) does not pass the type constraint because: "
. "Validation failed for 'Str' with value $value"
if ref($value);
$self->{last_name} = $value;
}
return $self->{last_name};
}
|
Side by side
package Person;
use Moose;
has last_name => (
is => 'rw',
isa => 'Str',
);
|
package Person;
use strict;
use warnings;
use Carp 'confess';
sub new {
my $class = shift;
my %args = @_;
my $self = {};
if (exists $args{last_name}) {
confess "Attribute (last_name) does not pass the type constraint because: "
. "Validation failed for 'Str' with value $args{last_name}"
if ref($args{last_name});
$self->{last_nane} = $args{last_name};
}
return bless $self, $class;
}
sub last_name {
my $self = shift;
if (@_) {
my $value = shift;
confess "Attribute (last_name) does not pass the type constraint because: "
. "Validation failed for 'Str' with value $value"
if ref($value);
$self->{last_name} = $value;
}
return $self->{last_name};
}
|
Side by side
package Person;
use Moose;
has last_name => (
is => 'rw',
isa => 'Str',
);
|
package Person;
use strict;
use warnings;
use Carp 'confess';
sub new {
my $class = shift;
my %args = @_;
my $self = {};
if (exists $args{last_name}) {
confess "Attribute (last_name) does not pass the type constraint because: "
. "Validation failed for 'Str' with value $args{last_name}"
if ref($args{last_name});
$self->{last_nane} = $args{last_name};
}
return bless $self, $class;
}
sub last_name {
my $self = shift;
if (@_) {
my $value = shift;
confess "Attribute (last_name) does not pass the type constraint because: "
. "Validation failed for 'Str' with value $value"
if ref($value);
$self->{last_name} = $value;
}
return $self->{last_name};
}
|
Side by side
package Person;
use Moose;
has last_name => (
is => 'rw',
isa => 'Str',
);
|
package Person;
use strict;
use warnings;
use Carp 'confess';
sub new {
my $class = shift;
my %args = @_;
my $self = {};
if (exists $args{last_name}) {
confess "Attribute (last_name) does not pass the type constraint because: "
. "Validation failed for 'Str' with value $args{last_name}"
if ref($args{last_name});
$self->{last_nane} = $args{last_name};
}
return bless $self, $class;
}
sub last_name {
my $self = shift;
if (@_) {
my $value = shift;
confess "Attribute (last_name) does not pass the type constraint because: "
. "Validation failed for 'Str' with value $value"
if ref($value);
$self->{last_name} = $value;
}
return $self->{last_name};
}
|
Side by side
package Person;
use Moose;
has last_name => (
is => 'rw',
isa => 'Str',
);
|
package Person;
use strict;
use warnings;
use Carp 'confess';
sub new {
my $class = shift;
my %args = @_;
my $self = {};
if (exists $args{last_name}) {
confess "Attribute (last_name) does not pass the type constraint because: "
. "Validation failed for 'Str' with value $args{last_name}"
if ref($args{last_name});
$self->{last_nane} = $args{last_name};
}
return bless $self, $class;
}
sub last_name {
my $self = shift;
if (@_) {
my $value = shift;
confess "Attribute (last_name) does not pass the type constraint because: "
. "Validation failed for 'Str' with value $value"
if ref($value);
$self->{last_name} = $value;
}
return $self->{last_name};
}
|
Side by side
package Person;
use Moose;
has last_name => (
is => 'rw',
isa => 'Str',
);
|
package Person;
use strict;
use warnings;
use Carp 'confess';
sub new {
my $class = shift;
my %args = @_;
my $self = {};
if (exists $args{last_name}) {
confess "Attribute (last_name) does not pass the type constraint because: "
. "Validation failed for 'Str' with value $args{last_name}"
if ref($args{last_name});
$self->{last_nane} = $args{last_name};
}
return bless $self, $class;
}
sub last_name {
my $self = shift;
if (@_) {
my $value = shift;
confess "Attribute (last_name) does not pass the type constraint because: "
. "Validation failed for 'Str' with value $value"
if ref($value);
$self->{last_name} = $value;
}
return $self->{last_name};
}
|
Side by side
5 lines |
21 lines |
92 characters |
741 characters |
package Person;
use Moose;
has last_name => (
is => 'rw',
isa => 'Str',
);
|
package Person;
use strict;
use warnings;
use Carp 'confess';
sub new {
my $class = shift;
my %args = @_;
my $self = {};
if (exists $args{last_name}) {
confess "Attribute (last_name) does not pass the type constraint because: "
. "Validation failed for 'Str' with value $args{last_name}"
if ref($args{last_name});
$self->{last_nane} = $args{last_name};
}
return bless $self, $class;
}
sub last_name {
my $self = shift;
if (@_) {
my $value = shift;
confess "Attribute (last_name) does not pass the type constraint because: "
. "Validation failed for 'Str' with value $value"
if ref($value);
$self->{last_name} = $value;
}
return $self->{last_name};
}
|
Typo?
sub new {
my $class = shift;
my %args = @_;
my $self = {};
if (exists $args{last_name}) {
confess "Attribute (last_name) does not pass the type constraint because: "
. "Validation failed for 'Str' with value $args{last_name}"
if ref($args{last_name});
$self->{last_nane} = $args{last_name};
}
return bless $self, $class;
}
Typo?
if (exists $args{last_name}) {
confess "Attribute (last_name) does not pass the type constraint because: "
. "Validation failed for 'Str' with value $args{last_name}"
if ref($args{last_name});
$self->{last_nane} = $args{last_name};
}
Typo?
$self->{last_nane} = $args{last_name};
Typo?
$self->{last_nane}
Why Moose?
package Person;
use Moose;
has last_name => (
is => 'rw',
isa => 'Str',
);
Moose Classes
Moose Classes
- Moose classes are Perl packages which
use Moose
Moose.pm and Your Class
package Person;
use Moose;
Moose.pm
provides declarative sugar
- Creates metaclasses for your class:
Person->meta
- Moose classes automatically inherit from
Moose::Object
What Moose::Object
Provides
- Constructor -
new()
- Calls your
BUILDARGS()
and/or BUILD()
- Calls your
DEMOLISH
during object destruction
extends
extends
is sugar for declaring parent classes
package Employee;
use Moose;
extends 'Person';
extends
- Each call to
extends
resets your parents
WRONG
package BadEmployee;
use Moose;
extends 'Person';
extends 'Thief';
RIGHT
package BadEmployee;
use Moose;
extends 'Person', 'Thief';
Extending un-Moose-y Parents
package My::LWP;
use Moose;
extends 'LWP';
- No
Moose::Object
, so ...
- No attribute-handling
new()
- No
BUILDARGS()
or BUILD()
- No
DEMOLISH()
- But see
MooseX::NonMoose
for a workaround
Cleaning Up Moose Droppings
package Person;
use Moose;
# true
Person->can('extends');
Cleaning Up Moose Droppings
package Person;
use Moose;
...
no Moose;
# false
Person->can('extends');
No Moose
no Moose
at the end of a package is a best practice
- Just do it
Immutability
- Stevan's Incantation of Fleet-Footedness
package Person;
use Moose;
__PACKAGE__->meta->make_immutable;