Getting the Moose wrong: Of not using native traits

Munich Perl Mongers Technical Meeting 05/2012

Daniel Bruder

If you’re doing this …

use Moose;

has foo => (
    is  => 'rw',
    isa => 'ArrayRef[Str]',
);

push %{ $self->foo }, @values;

… you’re doing something wrong

There’s a better way

There’s a better way: “native traits”!

There’s a better way: “native traits”!

use Moose;

has foo => (
    is  => 'rw',
    isa => 'ArrayRef[Str]',
    traits => [ 'Array' ],
);

…still not quite there yet…

…still not quite there yet…

to make your trait more usable

…still not quite there yet…

to make your trait more usable: install handles!

…still not quite there yet…

to make your trait more usable: install handles!

use Moose;

has foo => (
    is      => 'rw',
    isa     => 'ArrayRef[Str]',
    traits  => [ 'Array' ],
    handles => {
        foo_push => 'push'
    },
);

$self->foo_push(@values);

Mix ‘n’ match!

(define your API)

use Moose;

has foo => (
    is      => 'rw',
    isa     => 'ArrayRef[Str]',
    traits  => [ 'Array' ],
    handles => {
        foo_push  => 'push',
        foo_shift => 'shift',
    },
);

$self->foo_push(@values);
my $str = $self->foo_shift;

More Array methods (“handles”) include

Traits and handles for Arrays only?

Traits and handles for Arrays only?

Here’s a Hash!

use Moose;

has bar => (
    is      => 'rw',
    isa     => 'HashRef',
    traits  => [ 'Hash' ],
    handles => {
        bar_set     => 'push',
        bar_get     => 'shift',
        bar_defined => 'defined',
        bar_exists  => 'exists',
    },
);

# Ok, this is contrived, but it gets the point across
my $bar = $self->bar_exists($value) ? $self->bar_get($value) : '';

So Traits are very useful.

Why are they useful?

OK cool, which traits are there?

So what are traits exactly?

“But I don’t like the Syntax”

Why not use Moose::Autobox instead?

You think Moose::Autobox is much cleaner, more intuitive, and generally, “beautiful”

use Moose;
use Moose::Autobox;

has foo => ( is => 'rw', isa => 'ArrayRef[Int]' );

$self->foo->push(1, 2, 3);   # "fine"
$self->foo->push('a', 'b');  # "fine" too (but your validation is screwed)
$self->foo->shift;           # "fine" too (too bad you didn't want your API 
                             #             to expose a shift method on foo...)

Why not use Moose::Autobox instead?

Because a Moose::Autobox-type-like approach won’t:

Why that weird syntax? Why not $self->bar->push?

Traits are very well thought out, they let you apply method modifiers on them.

(Contrived) Example:

use MooseX::Declare;
class Baz {

    # Suppose we haven't created a coercion to do this for us...

    has foo => (
        is      => 'rw',
        isa     => 'ArrayRef[Str]', # suppose we only want uppercase strings here
        traits  => [ 'Array' ],
        handles => {
            foo_push  => 'push',
            foo_shift => 'shift',
        },
    );

    around foo_push(Any $new?) {
        return $self->$orig() unless $new;
        return $self->$orig(map { uc } @$new);
    }

    # Now you can:

    $self->foo_push(qw/foo bar baz quux/);
    my $uc_str = $self->foo_shift; # ~~ 'FOO'
}

More fun with traits

Example: Baz.pm

class Baz with MooseX::Getopt {
    has foo => ( 
        is          => 'rw', 
        isa         => 'Str', 
        traits      => [ 'Getopt' ],
        cmd_aliases => ['f', 'foo'],
        default     => 'foo',
    );
}

package main;
print Baz->new_with_options->foo;

__END__

$ perl Baz.pm
# foo

$ Perl Baz.pm --foo=quux
# quux

$ Perl Baz.pm -f bar
# bar

More fun with handles: delegation

Example I

class Download {
    has ua => (
        is => 'ro',
        isa => 'WWW::Mechanize',
        handles => ['get'],
    );        
}

$dl = Download->new;

$dl->get('www.mechanize.org');
# will call
$dl->ua->get('www.mechanize.org');

Example II

class Download {
    has ua => (
        is => 'ro',
        isa => 'WWW::Mechanize',
        handles => { download => 'get' }
    );        
}

$dl = Download->new;

$dl->download('www.mechanize.org');
# will call
$dl->ua->get('www.mechanize.org');

Example III

class Download {
    has ua => (
        is => 'ro',
        isa => 'WWW::Mechanize',
        handles => qr/g.*/,
    );        
}

$dl = Download->new;

$dl->get('www.mechanize.org');
# will call
$dl->ua->get('www.mechanize.org');

Example III

class Download {
    has ua => (
        is => 'ro',
        isa => 'WWW::Mechanize',
        handles => qr/g.*/,
    );        
}

$dl = Download->new;

$dl->get('www.mechanize.org');
# will call
$dl->ua->get('www.mechanize.org');

$dl->goofy('www.mechanize.org');
# will (try to) call
$dl->ua->goofy('www.mechanize.org');

Example III

class Download {
    has ua => (
        is => 'ro',
        isa => 'WWW::Mechanize',
        handles => qr/g.*/,
    );        
}

$dl = Download->new;

$dl->get('www.mechanize.org');
# will call
$dl->ua->get('www.mechanize.org');

$dl->goofy('www.mechanize.org');
# will (try to) call
$dl->ua->goofy('www.mechanize.org');
# (use with caution)

Example IV

class Download {
    has ua => (
        is => 'ro',
        isa => 'WWW::Mechanize',
        handles => 'Downloadable',  # a role name
    );        
}

Example V

The doc also says:

“Finally, you can also provide a sub reference to generate a mapping. You probably won’t need this version often (if ever). See the Moose docs for more details on exactly how this works” (never needed this)

Long story short

Long story short (a quick summary)

You never (have to) do this again

has foo => (
    is  => 'rw',
    isa => 'ArrayRef[Str]',
);

push %{ $self->foo }, @values;

(but you can) do this instead

has foo => (
    is      => 'rw',
    isa     => 'ArrayRef[Str]',
    traits  => [ 'Array' ],
    handles => {
        foo_push  => 'push',
        foo_shift => 'shift',
    },
);

and enjoy

have fun

and (optionally) have a look here:

FIN

Contact

   __  __               
  /\ \/\ \              Daniel Bruder
  \_\ \ \ \____         IT-Services,
  /'_` \ \ '__`\        Consulting &
 /\ \L\ \ \ \L\ \       Training 
 \ \___,_\ \_,__/       daniel.bruder   
  \/__,_ /\/___/        @gmail.com