Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥

Moose - A post modern object system for Perl

Moose - A post modern object system for Perl

This is an early talk I gave on Moose at YAPC::NA 2008 in Chicago, IL.

Stevan Little

June 17, 2008
Tweet

More Decks by Stevan Little

Other Decks in Programming

Transcript

  1. (Moose)(is)(a) [postmodern) (object] [system) Stable! 2+ years old! Used widely

    ! in production! Rich Ancestry CLOS Smalltalk Perl6 …
  2. (Moose)(is)(a) [postmodern) (object] [system) Stable! 2+ years old! Used widely

    ! in production! Rich Ancestry CLOS Smalltalk Perl6 … Perlish Plays well with CPAN and vanilla perl 5 OOP
  3. (Moose)(is)(a) [postmodern) (object] [system) Stable! 2+ years old! Used widely

    ! in production! Rich Ancestry CLOS Smalltalk Perl6 … Perlish Plays well with CPAN and vanilla perl 5 OOP
  4. package Person; use strict; use warnings; sub new { my

    ($class, %args) = @_; bless { name => $args{name}, age => $args{age} || 0, } => ref($class) || $class; } sub name { my $self = shift; $self->{name} = shift if @_; $self->{name}; } sub age { my $self = shift; $self->{age} = shift if @_; $self->{age}; } 1; The Old Way
  5. The Old Way package Person; use strict; use warnings; use

    base 'Class::Accessor'; __PACKAGE__->mk_accessor(qw[name age]); 1;
  6. The Old Way package Person; use strict; use warnings; use

    base 'Class::Accessor'; __PACKAGE__->mk_accessor(qw[name age]); sub new { my ($class, $params) = @_; $params = {} unless defined $params; $params->{age} = 0; $class->SUPER::new($params); } 1;
  7. package Person; use Moose; has ‘name’ => (is => ‘rw’);

    has ‘age’ => (is => ‘rw’, default => 0); 1; The Moose Way
  8. package Person; use Moose; use Moose::Util::TypeConstraints; use DateTime::Duration; class_type 'DateTime::Duration';

    coerce 'DateTime::Duration' => from 'Int' => via { DateTime::Duration->new(years => $_) } => from 'HashRef' => via { DateTime::Duration->new( %$_ ) }; has 'name' => ( is => 'rw', isa => subtype('Str' => where { length $_ > 0 }), required => 1, ); has 'age' => ( is => 'rw', isa => 'DateTime::Duration', coerce => 1, lazy => 1, default => sub { DateTime::Duration->new }, ); 1; The Moose Way
  9. package Person; use Moose; use Moose::Util::TypeConstraints; use DateTime::Duration; class_type 'DateTime::Duration';

    coerce 'DateTime::Duration' => from 'Int' => via { DateTime::Duration->new(years => $_) } => from 'HashRef' => via { DateTime::Duration->new( %$_ ) }; has 'name' => ( is => 'rw', isa => subtype('Str' => where { length $_ > 0 }), required => 1, ); has 'age' => ( is => 'rw', isa => 'DateTime::Duration', coerce => 1, lazy => 1, default => sub { DateTime::Duration->new }, ); 1; The Moose Way
  10. package Person; use Moose; use Moose::Util::TypeConstraints; use DateTime::Duration; class_type 'DateTime::Duration';

    coerce 'DateTime::Duration' => from 'Int' => via { DateTime::Duration->new(years => $_) } => from 'HashRef' => via { DateTime::Duration->new( %$_ ) }; has 'name' => ( is => 'rw', isa => subtype('Str' => where { length $_ > 0 }), required => 1, ); has 'age' => ( is => 'rw', isa => 'DateTime::Duration', coerce => 1, lazy => 1, default => sub { DateTime::Duration->new }, ); 1; The Moose Way
  11. package Person; use Moose; use Moose::Util::TypeConstraints; use DateTime::Duration; class_type 'DateTime::Duration';

    coerce 'DateTime::Duration' => from 'Int' => via { DateTime::Duration->new(years => $_) } => from 'HashRef' => via { DateTime::Duration->new( %$_ ) }; has 'name' => ( is => 'rw', isa => subtype('Str' => where { length $_ > 0 }), required => 1, ); has 'age' => ( is => 'rw', isa => 'DateTime::Duration', coerce => 1, lazy => 1, default => sub { DateTime::Duration->new }, ); 1; The Moose Way
  12. package Person; use Moose; use Moose::Util::TypeConstraints; use DateTime::Duration; class_type 'DateTime::Duration';

    coerce 'DateTime::Duration' => from 'Int' => via { DateTime::Duration->new(years => $_) } => from 'HashRef' => via { DateTime::Duration->new( %$_ ) }; has 'name' => ( is => 'rw', isa => subtype('Str' => where { length $_ > 0 }), required => 1, ); has 'age' => ( is => 'rw', isa => 'DateTime::Duration', coerce => 1, lazy => 1, default => sub { DateTime::Duration->new }, ); 1; The Moose Way
  13. package Person; use Moose; use Moose::Util::TypeConstraints; use DateTime::Duration; class_type 'DateTime::Duration';

    coerce 'DateTime::Duration' => from 'Int' => via { DateTime::Duration->new(years => $_) } => from 'HashRef' => via { DateTime::Duration->new( %$_ ) }; has 'name' => ( is => 'rw', isa => subtype('Str' => where { length $_ > 0 }), required => 1, ); has 'age' => ( is => 'rw', isa => 'DateTime::Duration', coerce => 1, lazy => 1, default => sub { DateTime::Duration->new }, ); 1; The Moose Way
  14. package Person; use Moose; use Moose::Util::TypeConstraints; use DateTime::Duration; class_type 'DateTime::Duration';

    coerce 'DateTime::Duration' => from 'Int' => via { DateTime::Duration->new(years => $_) } => from 'HashRef' => via { DateTime::Duration->new( %$_ ) }; has 'name' => ( is => 'rw', isa => subtype('Str' => where { length $_ > 0 }), required => 1, ); has 'age' => ( is => 'rw', isa => 'DateTime::Duration', coerce => 1, lazy => 1, default => sub { DateTime::Duration->new }, ); 1; The Moose Way
  15. package Person; use strict; use warnings; use DateTime::Duration; use Scalar::Util

    'blessed', 'looks_like_number'; use Carp 'confess'; sub new { my $class = shift; my %params; if (@_ == 1 && ref $_[0] eq 'HASH') { %params = %{$_[0]}; } else { %params = @_; } (exists $params{name} && length $params{name} > 0) || confess "You must supply a name"; if (exists $params{age}) { $params{age} = _coerce_date_time_duration($params{age}); } return bless \%params => ref $class || $class; } sub _coerce_date_time_duration { my ($val) = @_; if (blessed $val) { return $val if $val->isa('DateTime::Duration'); confess "A blessed value must be a DateTime::Duration object, not $val"; } elsif (ref $val) { return DateTime::Duration->new( %$val ) if ref $val eq 'HASH'; confess "We can only convert HASH refs to DateTime::Duration objects, not $val"; } elsif (looks_like_number($val)) { return DateTime::Duration->new(years => $val); } else { confess "Cannot coerce $val into DateTime::Duration object"; } } sub name { my $self = shift; if (@_) { my $name = shift; (length $name > 0) || confess "You must supply a name"; $self->{name} = $name; } return $self->{name}; } sub age { my $self = shift; if (@_) { $self->{age} = _coerce_date_time_duration(shift); } else { $self->{age} ||= DateTime::Duration->new; } return $self->{age}; } 1; The Old Way
  16. Moore’s Law Developer Speed Developer Salaries Inflation Rate of Bullsh*t

    in my graph Invention of Jolt Moose *will* get faster Class::MOP 0.49 - 2x speedup (XS) Class::MOP 0.57 - 20-25% speedup Class::MOP 0.59 - 25-30% speedup Computers will get faster, perl will get faster and Moose will get faster ... but your brain never will.
  17. package Employee; use Moose; extends 'Person'; has 'job_title' => (

    is => 'rw', isa => 'Str’ ); package Manager; use Moose; extends 'Employee'; has '+job_title' => (default => 'Manager'); has 'staff' => ( is => 'ro', isa => 'ArrayRef[Employee]', default => sub { [] }, );
  18. package Employee; use Moose; extends 'Person'; has 'job_title' => (

    is => 'rw', isa => 'Str’ ); has 'manager' => ( is => 'ro', isa => 'Manager', handles => { coworkers => 'staff', } ); package Manager; use Moose; extends 'Employee'; has '+job_title' => (default => 'Manager'); has 'staff' => ( is => 'ro', isa => 'ArrayRef[Employee]', default => sub { [] }, );
  19. package Employee; use Moose; extends 'Person'; has 'job_title' => (

    is => 'rw', isa => 'Str’ ); has 'manager' => ( is => 'ro', isa => 'Manager', handles => { coworkers => 'staff', } ); package Manager; use Moose; use MooseX::AttributeHelpers; extends 'Employee'; has '+job_title' => (default => 'Manager'); has 'staff' => ( metaclass => 'Collection::Array', is => 'ro', isa => 'ArrayRef[Employee]', default => sub { [] }, provides => { count => 'staff_size', empty => 'has_staff', push => 'add_to_staff', pop => 'lay_off_new_guy', } );
  20. package Employee; use Moose; extends 'Person'; has 'job_title' => (

    is => 'rw', isa => 'Str’ ); has 'manager' => ( is => 'ro', isa => 'Manager', handles => { coworkers => 'staff', } ); package Manager; use Moose; use MooseX::AttributeHelpers; extends 'Employee'; has '+job_title' => (default => 'Manager'); has 'staff' => ( metaclass => 'Collection::Array', is => 'ro', isa => 'ArrayRef[Employee]', default => sub { [] }, provides => { count => 'staff_size', empty => 'has_staff', push => 'add_to_staff', pop => 'lay_off_new_guy', } ); after 'add_to_staff' => sub { my ($self, $employee) = @_; $employee->manager($self); };
  21. package Employee; use Moose; extends 'Person'; has 'job_title' => (

    is => 'rw', isa => 'Str’ ); has 'manager' => ( is => 'ro', isa => 'Manager', clearer => 'remove_manager', predicate => 'has_manager', handles => { coworkers => 'staff', } ); package Manager; use Moose; use MooseX::AttributeHelpers; extends 'Employee'; has '+job_title' => (default => 'Manager'); has 'staff' => ( metaclass => 'Collection::Array', is => 'ro', isa => 'ArrayRef[Employee]', default => sub { [] }, provides => { count => 'staff_size', empty => 'has_staff', push => 'add_to_staff', pop => 'lay_off_new_guy', } ); after 'add_to_staff' => sub { my ($self, $employee) = @_; $employee->manager($self); };
  22. package Employee; use Moose; extends 'Person'; has 'job_title' => (

    is => 'rw', isa => 'Str’ ); has 'manager' => ( is => 'ro', isa => 'Manager', clearer => 'remove_manager', predicate => 'has_manager', handles => { coworkers => 'staff', } ); package Manager; use Moose; use MooseX::AttributeHelpers; extends 'Employee'; has '+job_title' => (default => 'Manager'); has 'staff' => ( metaclass => 'Collection::Array', is => 'ro', isa => 'ArrayRef[Employee]', default => sub { [] }, provides => { count => 'staff_size', empty => 'has_staff', push => 'add_to_staff', pop => 'lay_off_new_guy', } ); after 'add_to_staff' => sub { my ($self, $employee) = @_; $employee->manager($self); }; around 'lay_off_new_guy' => sub { my $next = shift; my $self = shift; my $new_guy = $self->$next(@_); $new_guy->remove_manager; $new_guy; };
  23. The Old Way package Employee; use strict; use warnings; use

    base 'Person'; sub new { my ($class, %params) = @_; my %params; if (@_ == 1 && ref $_[0] eq 'HASH') { %params = %{$_[0]}; } else { %params = @_; } (blessed $params{manager} && $params{manager}->isa('Manager')) || confess "You must supply a name"; my $self = $class->SUPER::new(%params); $self->{manager} = $params{manager}; $self->{job_title} = $params{job_title} if exists $params{job_title}; return $self; } sub job_title { my $self = shift; if (@_) { $self->{job_title} = shift; } return $self->{job_title}; } sub manager { my $self = shift; if (@_) { my $manager = shift; (blessed $manager && $manager->isa('Manager')) || confess "You must supply a manager"; $self->{manager} = $manager; } return $self->{manager}; } sub remove_manager { my $self = shift; delete $self->{manager} } sub has_manager { my $self = shift; exists $self->{manager} ? 1 : 0 } sub coworkers { my $self = shift; $self->manager->staff } package Manager; use strict; use warnings; use base 'Employee'; sub new { my ($class, %params) = @_; my %params; if (@_ == 1 && ref $_[0] eq 'HASH') { %params = %{$_[0]}; } else { %params = @_; } if (exists $params{staff}) { (ref $params{staff} eq 'ARRAY') || confess "Staff must be an ARRAY ref"; foreach (@{$params{staff}}) { ($_->isa('Employee')) || confess "You staff elements must “ . “be Employee objects"; } } else { $params{staff} = []; } my $self = $class->SUPER::new(%params); $self->{staff} = $params{staff}; $self->{job_title} = $params{job_title} || 'Manager'; return $self; } sub staff { my $self = shift; if (@_) { my $staff = shift; if (defined $staff) { (ref $staff eq 'ARRAY') || confess "Staff must be an ARRAY ref"; foreach (@{$staff}) { ($_->isa('Employee')) || confess "You staff elements must be Employee objects"; } } else { $staff = []; } } return $self->{staff}; } sub staff_size { my $self = shift; $self->{staff} ||= []; $#{ $self->{staff} } } sub has_staff { my $self = shift; $self->{staff} ||= []; @{ $self->{staff} } ? 1 : 0 } sub add_to_staff { my ($self, $employee) = @_; (blessed $employee && $employee->isa('Employee')) || confess "You can only add employee instances"; $self->{staff} ||= []; push @{ $self->{staff} } => $employee; $employee->manager($self); } sub lay_off_new_guy { my $self = shift; return undef if defined $self->{staff} my $new_guy = pop @{ $self->{staff} }; $new_guy->remove_manager; $new_guy; }
  24. package Salaried; use Moose::Role; requires 'paycheck_amount'; package Salaried::Hourly; use Moose::Role;

    with 'Salaried'; has hourly_rate => ( is => 'rw', isa => 'Int', required => 1, ); has logged_hours => ( is => 'rw', isa => 'Int', default => 0, ); sub paycheck_amount { my $self = shift; $self->logged_hours * $self->hourly_rate; }
  25. package Salaried; use Moose::Role; requires 'paycheck_amount'; package Salaried::Hourly; use Moose::Role;

    with 'Salaried'; has hourly_rate => ( is => 'rw', isa => 'Int', required => 1, ); has logged_hours => ( is => 'rw', isa => 'Int', default => 0, ); sub paycheck_amount { my $self = shift; $self->logged_hours * $self->hourly_rate; } package Salaried::Yearly; use Moose::Role; with 'Salaried'; has salary => ( is => 'rw', isa => 'Int', required => 1, ); sub paycheck_amount { my $self = shift; $self->salary / 12 }
  26. package Salaried; use Moose::Role; requires 'paycheck_amount'; package Salaried::Hourly; use Moose::Role;

    with 'Salaried'; has hourly_rate => ( is => 'rw', isa => 'Int', required => 1, ); has logged_hours => ( is => 'rw', isa => 'Int', default => 0, ); sub paycheck_amount { my $self = shift; $self->logged_hours * $self->hourly_rate; } package Salaried::Yearly; use Moose::Role; with 'Salaried'; has salary => ( is => 'rw', isa => 'Int', required => 1, ); sub paycheck_amount { my $self = shift; $self->salary / 12 } package Employee; use Moose; extends 'Person'; with 'Salaried::Hourly'; # ... package Manager; use Moose; extends ’Employee'; with 'Salaried::Yearly'; # ...
  27. Benefits of Moose Code is less tedious Code is shorter

    Less low-level testing Code is more descriptive
  28. The Pit of Moose Despair Compile Time Cost A Lot

    of Kool-aid Some Features Are Slow
  29. The Pit of Moose Despair Compile Time Cost A Lot

    of Kool-aid Some Features Are Slow Sometimes doesn’t! play well with others