Moose: A Post-Modern Object-Oriented System for Perl

by Yanick Champoux

Ottawa Perl Mongers Meeting of may 2009

This talk has been reformated to fit your screen

Original talk for the FOSSLC Summercamp 2009

Content altered for Monger consumtion

All too common opinion of outsiders

"Oh yeah, Perl. Quaint scripting tool. But it's not like it's a real programming language. I mean *sneer* it doesn't even do OO."

Yeah, right...

They are right. Perl doesn't have a Object Oriented system.

It has a friggin' boatload of them!

Why Moose?

  • Brings Perl 6 concepts into Perl 5
  • Extensive yet flexible
  • Project very much alive, strong development
  • Very popular in the Perl community
  • Fully reflective, allow for lots of meta-programming fun
  • Lots of extensions available on CPAN

Postmodern?

  • In the same way that Perl is postmodern.

    Borrow the best ideas, mixes them together in a useful and eclectic manner.

  • Take care of the boring parts, so that you can concentrate on the fun stuff.

Our example for today

Summercamp2009::Person

What makes a class?

A Person is born...

In the file Person.pm

use MooseX::Declare; class Person { }

Without the MooseX::Declare sugar

package Person; use Moose; use namespace::clean -except => 'meta'; # ... __PACKAGE__->meta->make_immutable(); no Moose; 1;

A Person is born, part II

use Person; my $peep = Person->new;

TADA!

... okay, it's not that exciting yet.

Let's add some attributes

Characteristics defining the object

I can haz attributes

use MooseX::Declare; class Person { has 'name'; has 'age'; has 'gender'; }

Attributes

use Person; my $peep = Person->new( name => 'Vitru Vian', age => 43, gender => 'Male', );

Getting marginally more interesting...

Adding accessors

In Java

void set_name ( String n ) { name = n; } String get_name ( ) { return name; }

Accessors in Java, cont'd

void set_age ( int a ) { age = a; } int get_age ( ) { return age; }

Accessors in Java, cont'd

void set_gender ( Gender g ) { gender = d; } Gender get_gender ( ) { retuAAAAARGH!

Accessors, the Moose way

use MooseX::Declare; class Person { has name => ( is => 'rw' ); has age => ( is => 'rw' ); has gender => ( is => 'ro' ); }

Using accessors

$peep->name( 'Vitru Vian' ); print "Person's age is ", $peep->age;

Variants

use MooseX::FollowPBP; $peep->set_name( 'Vitru Vian' ); my $name = $peep->get_name;

use MooseX::SemiAffordanceAccessor; $peep->set_name( 'Vitru Vian' ); my $name = $peep->name;

Attribute - required, predicate, clearer

has name => ( is => 'rw', required => 1 , predicate => 'has_name', clearer => 'dename', );

Attribute - default

has age => ( is => 'rw', default => 42 );

or

has age => ( is => 'rw', default => sub { int rand 100 } );

Attribute - lazy assignation

use Twitter::Badge; has twitter_id => ( is => 'rw' ); has twitter_status => ( is => 'ro', lazy => 1, default => sub { my $self = shift; return Twitter::Badge->new( id => $self->twitter_id )->text; } );

Attribute - lazy assignation use

my $peep = Person->new( twitter_id => '12345' ); print $peep->twitter_status;

Attribute - handles

has twitter_account => ( isa => 'Twitter::Badge', handles => { twitter_status => 'text', } );

# equivalent to '$peep->twitter_account->text $peep->twitter_status

You are not my type

$peep->set_age( 'banana' );

Attribute types

Any Item Bool Maybe[`a] Undef Defined Value Num Int Str ClassName RoleName Defined (cont'd) Ref ScalarRef ArrayRef[`a] HashRef[`a] CodeRef RegexpRef GlobRef FileHandle Object Role

Attribute - isa

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

Sub-type

use Moose::Util::TypeConstraints; subtype 'Age' => as 'Int' => where { $_ > 18 and $_ < 100 } => message { 'must be between 18 and 100' };

Later on, in the class

has age => ( isa => 'Age', is => 'rw', );

Enums

enum Gender => qw/ Male Female /; has 'gender' => ( is => 'rw', isa => 'Gender' );

Let's be nice

Be strict in what you produce, and permissive in what you accept.

Coercion

has 'gender' => ( coerce => 1 is => 'rw', isa => 'Gender' ); coerce Gender => from 'Str' => via { return 'Male' if /^( man|lad|guy )/xi; return 'Female' if /^( woman|lass|gal )/xi; return $_; # not recognized };

More coercion

coerce Gender => from 'Person' => via { return $_->gender };

Coercion in action

$peep->gender( 'man' ); print $peep->gender; $ prints 'Male' # bob is like that peep dude my $bob = Person->new( gender => $peep );

Methods

In the class

method asks ( Str $question ) { $self->raise_hand; say "Sorry, but ", $question; }

In use

$peep->ask( 'what is that $self variable?' );

Arguments with checks

# question must end with question mark method ask ( Str $question where { /\?$/ } ) { $self->raise_hand; say "Sorry, but ", $question; }

I don't like my $self

Fine, let's give it a Java flavor

method ask ($this: Str $question ) { $this->raise_hand; say "Sorry, but ", $question; }

Positional arguments

method ask ( $question, $tone = 'puzzled' ) { # ... }

# $tone is 'puzzled' $peep->ask( 'why?' ); # $tone is 'curious' $peep->ask( 'how?', 'curious' )

Named arguments

method ask ( :$question, :$tone = 'puzzled' ) { # ... }

$peep->ask( tone => 'baffled', question => 'why?' );

Mixed arguments

method ask ( $question, :$tone = 'puzzled' ) { # ... }

$peep->ask( 'why?', tone => 'hysteric' );

Multi-Methods

multi method compare ( Age $age ) { return $self->age <=> $age; } multi method compare ( Gender $gender ) { return $self->gender eq $gender; }

$peep->compare( 'Male' );

before, after

In Summercamp2009::Person

method ask ( $question ) { $self->say( $question ); }

before, after

In Summercamp2009::Person::Polite

before ask ( $question ) { $self->raise_hand; } after ask ( $question ) { say "Thank you!"; }

override

In Summercamp2009::Person::HyperExcited

override ask ( $question ) { say uc $question; }

Inheritence

class Summercamp2009::Presenter extends Summercamp2009::Person { has presentation_title => ( is => 'rw', ); has '+age' => ( required => 1, ); method present { say 'Hi, my name is ', $self->name; 'and I'm going to talk about ', $self->presentation_title; } }

Roles

A set of behaviors applied to an object

Also knows as traits (Perl 6), interfaces (Java).

Unlike in java, roles don't need to be abstract.

AdmissionFee Role

role AdmissionFee { requires 'price'; has 'taxes' => ( is => 'rw' ); method print_fee { $self->set_taxes( $self->price * 0.15); say 'amount owed: ', $self->price + $self->taxes; } }

price in Summercamp2009::Person

class Summercamp2009::Person does AdmissionFee { # all the other stuff ... method price { given ( $self->age ) { when ( $_ < 21 ) { return 10; } when ( $_ > 60 ) { return 15; } default { return 25; } } } }

price in Summercamp2009::Group

class Summercamp2009::Group does AdmissionFee { # all the other stuff ... method price { my $peeps = $self->nbr_members; my $unit_price = $peeps < 10 ? 25 : 20 ; return $unit_price * $peeps; } }

In use

my $peep = Summercamp2009::Person->new( ... ); $peep->print_fee; my $group = Summercamp2009::Group->new; $group->add( $peep ); $group->print_fee;

Meta-programming

has xml_src => ( is => 'ro', isa => 'XML::LibXML::Node', ); has foo => ( is => 'ro', lazy => 1, default => sub { # extract from 'xml_src' }, );

Meta-programming

has foo => ( is => 'ro', traits => [ 'XMLExtract' ], ); has bar => ( is => 'ro', traits => [ 'XMLExtract' ], xpath => '//yadah', xml_src => 'other_src', );

Meta-programming

role XMLExtract { has 'xml_src' => ( isa => 'Str', ); has xpath => ( isa => 'Str', ); has '+lazy' => ( default => 1 ); before '_process_options' => sub { my ( $class, $name, $options ) = @_; my $src = $options->{xml_src} ||= 'xml_src'; my $xpath = $options->{xpath} ||= $name; $options->{default} = sub { return $_[0]->$src->findvalue($xpath); }; }; }

Moose Friends

Mouse

Joose

Links

Thanks