Code Reuse with Moose

Hans Dieter Pearcey (hdp@cpan.org)

design patterns

Design Patterns in Dynamic Programming

Moose's role

Type Constraints

use MooseX::Types::Moose -all;
use MooseX::Types;
ArrayRef[Int]
HashRef[Object]

Subtyping

use MooseX::Types -declare => [
qw(PosInt PosEvenInt PosIntTriplet)
];

subtype PosInt,
as Int, where { $_ > 0 },
message { "Int is not greater than 0" };

subtype PosEvenInt,
as PosInt, where { $_ % 2 == 0 },
message { "Int is not even" };

subtype PosIntTriplet,
as ArrayRef[PosInt], where { @$_ == 3 },
message { "Must have exactly 3 PosInts" };

Type Constraint Use

my $tc = find_type_constraint(HashRef);
if ($tc->check($input)) { ... }
has foo => (is => 'rw', isa => HashRef);
use MooseX::Params::Validate;
sub foo {
my ($self, %arg) = validated_hash(
\@_,
bar => { isa => Int },
)
}

Libraries

package MyApp::Types;
use MooseX::Types -declare => [ qw(PosInt ...) ];
subtype PosInt, ...;

package MyApp;
use MyApp::Types qw(PosInt);

# don't let awesomeness drop to 0
has awesome => (isa => PosInt);

Type Constraints

Type Constraints

Type Coercions

sub read_data {
my ($self, $from) = @_;
open my $handle, '<', $from
or die "Can't open file $from: $!";
# ... do something with $handle
}

Type Coercions

sub read_data {
my ($self, $from) = @_;
my $handle;
if (! ref $from) {
open $handle, '<', $from
or die "Can't open file $from: $!";
} else {
die "Unhandled data source: $from"
}
# ... do something with $handle
}

Type Coercions

sub read_data {
my ($self, $from) = @_;
my $handle;
if (! ref $from) {
open $handle, '<', $from
or die "Can't open file $from: $!";
} elsif (ref $from and ref $from eq 'GLOB') {
$handle = $from;
} else {
die "Unhandled data source: $from"
}
# ... do something with $handle
}

Type Coercions

sub read_data {
my ($self, $from) = @_;
my $handle;
if (! ref $from) {
open $handle, '<', $from
or die "Can't open file $from: $!";
} elsif (ref $from and ref $from eq 'GLOB') {
$handle = $from;
} elsif (ref $from and ref $from eq 'SCALAR') {
open $handle, '<', $from
or die "Can't open scalar ref: $!";
} else {
die "Unhandled data source: $from"
}
# ... do something with $handle
}

Type Coercions

use MooseX::Types::Moose
FileHandle => { -as => 'MooseFileHandle' };
use MooseX::Types -declare => ['FileHandle'];

subtype FileHandle, as MooseFileHandle;
coerce FileHandle,
from Str,
via {
IO::File->new($_, 'r')
or die "Can't open $_: $!"
};

Put into place

package MyApp::Reader;
use Moose;
use MyApp::Types qw(FileHandle);
has from => (
is => 'ro',
isa => FileHandle,
coerce => 1,
);

# elsewhere
$f = MyApp::Reader->new(from => $fh);
$f = MyApp::Reader->new(from => '/some/file');

Adding a coercion

coerce FileHandle,
from ScalarRef,
via { IO::File->new($_, 'r') };
$f = MyApp::Reader->new(from => \'a string');

Complete definition

use MooseX::Types::Moose
FileHandle => { -as => 'MooseFileHandle' },
qw(Str ScalarRef);
use MooseX::Types -declare => ['FileHandle'];

subtype FileHandle, as MooseFileHandle;

coerce FileHandle,
from Str,
via {
IO::File->new($_, 'r')
or die "Can't open $_: $!"
};

coerce FileHandle,
from ScalarRef,
via { IO::File->new($_, 'r') };

Conclusion

Roles

roles vs. inheritance

sub driver_name {
croak "A subclass should override this!"
}
package MyApp::Driver;
requires 'driver_name';

examples

as interface

package Fey::Role::Named;
use Moose::Role;
requires 'name';

as label

package Fey::Role::Joinable;
use Moose::Role;
sub is_joinable { 1 }

as composed definition

Fey::Role::ColumnLike

as complex, standalone behavior

Fey::Role::SQL::HasWhereClause

parameterized roles

Fey::Role::HasAliasName

SELECT
(SELECT ...) AS SELECT0,
SUBSTR(...) AS FUNCTION0,
...

parameterized roles

sub driver_name {
croak "A subclass should override this!"
}
package MyApp::Driver;
parameter driver_name => (isa => Str, required => 1);
role {
my $p = shift;
method driver_name => sub { $p->driver_name };
};
package MyApp::Driver::Wagon;
with 'MyApp::Driver' => { driver_name => 'wagon' };

applied to individual instances

WWW::Mechanize::TreeBuilder

my $mech = WWW::Mechanize->new;
WWW::Mechanize::TreeBuilder->meta->apply($mech);

Role caveats

the end

thank you!