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.

78244476bb128a3a10522fc215bd2e83?s=128

Stevan Little

June 17, 2008
Tweet

Transcript

  1. None
  2. rumor on the Intertubes is that Perl is Dead

  3. is a member of the zombie horde

  4. (Moose)(is)(a) [postmodern) (object] [system)

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

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

    ! in production! Rich Ancestry CLOS Smalltalk Perl6 …
  7. (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
  8. (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
  9. Moose is not a toy It is serious Business!

  10. MOOSE is an Amplifier

  11. 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
  12. The Old Way package Person; use strict; use warnings; use

    base 'Class::Accessor'; __PACKAGE__->mk_accessor(qw[name age]); 1;
  13. 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;
  14. package Person; use Moose; has ‘name’ => (is => ‘rw’);

    has ‘age’ => (is => ‘rw’, default => 0); 1; The Moose Way
  15. 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
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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
  22. 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
  23. Wait Just a Minute!

  24. Wait Just a Minute! Isn’t Moose slow?

  25. None
  26. Moose is mostly compile time overhead

  27. Moore’s Law

  28. Moore’s Law Developer Speed Invention of Jolt

  29. Moore’s Law Developer Speed Developer Salaries Invention of Jolt

  30. Moore’s Law Developer Speed Developer Salaries Inflation Invention of Jolt

  31. Moore’s Law Developer Speed Developer Salaries Inflation Rate of Bullsh*t

    in my graph Invention of Jolt
  32. 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.
  33. Lets do some more Moose

  34. package Employee; use Moose; extends 'Person'; has 'job_title' => (

    is => 'rw', isa => 'Str’ );
  35. 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 { [] }, );
  36. 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 { [] }, );
  37. 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', } );
  38. 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); };
  39. 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); };
  40. 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; };
  41. 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; }
  42. package Salaried; use Moose::Role; requires 'paycheck_amount';

  43. 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; }
  44. 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 }
  45. 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'; # ...
  46. Benefits of Moose

  47. Benefits of Moose Code is less tedious

  48. Benefits of Moose Code is less tedious Code is shorter

  49. Benefits of Moose Code is less tedious Code is shorter

    Less low-level testing
  50. Benefits of Moose Code is less tedious Code is shorter

    Less low-level testing Code is more descriptive
  51. The Pit of Moose Despair

  52. The Pit of Moose Despair Compile Time Cost

  53. The Pit of Moose Despair Compile Time Cost Some Features

    Are Slow
  54. The Pit of Moose Despair Compile Time Cost A Lot

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

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