On Github kablamo / slides-intro-to-moo
use Moo;
is equivalent to
use strict; # strict vars, refs, subs use warnings; # in Moo v2+ no indirect; # can't do `my $alien = new Alien;` no multidimensional; # can't do `$hash{1,2}` no bareword::filehandles; # can't do `open FH, $filename` use base 'Moo::Object'; # provides new(), etc
also use Moo imports some methods:
extends(), with(), has(), before(), around(), and after()
It inflates your Moo class to a Moose class
my $alien = Alien->new(@args)
new() calls Alien->BUILDARGS(@args) new() instantiates object, creating $self new() calls BUILD($self) new() returns $self
is called before instantiation
package Alien; use Moo; sub BUILDARGS { my ($class, @args) = @_; # -- modify @args here -- return { @args }; }
In Moose you need to call SUPER at the end
is called after instantiation
package Alien; use Moo; sub BUILD { my ($self, $args) = @_; # -- additional validation or logging here -- }
The return value is ignored
is called when the object is destroyed
package Alien; use Moo; sub DEMOLISH { my ($self, $in_global_destruction) = @_; $self->dbh->disconnect; }
package Alien; use Moo; has eyes => (is => 'rw'); has nostrils => (is => 'ro');
my $alien = Alien->new( nostrils => 20 ); $alien->eyes(10); # succeeds $alien->nostrils(10); # dies
package Alien; use Moo; has eyes => (is => 'ro', default => sub { 5 }); has nostrils => (is => 'ro', builder => '_build_nostrils'); # Perlism: methods that start with _ are private sub _build_nostrils { 5 }
has tentacles => (is => 'lazy');
is equivalent to
has tentacles => (is => 'ro', lazy => 1, builder => '_build_tentacles');
Use MooseX::AttributeShortcuts to get support for is => 'lazy' in Moose
is unpredictable
package Alien; use Moo; use Tentacle; has tentacle_count => (is => 'ro', default => sub { 5 }); has tentacles => (is => 'ro', builder => '_build_tentacles'); sub _build_tentacles { my $self = shift; my @tentacles; push @tentacles, Tentacle->new() for (1..$self->tentacle_count); return \@tentacles; }
package Alien; use Moo; use Tentacle; has tentacles => (is => 'lazy'); has tentacle_count => (is => 'ro', default => sub { 5 }); sub _build_tentacles { my $self = shift; my @tentacles; push @tentacles, Tentacle->new() for (1..$self->tentacle_count); return \@tentacles; }
use Types::URI -all; has uri => (is => 'ro', isa => Uri); use Types::Standard -all; has is_from_mars => (is => 'ro', isa => Bool); has tentacles => (is => 'ro', isa => ArrayRef[InstanceOf["Tentacle"]]);
package MyCompany::Types; use Type::Library -base; use Type::Utils -all; BEGIN { extends "Types::Standard" }; declare "PurpleTentacles", as ArrayRef[InstanceOf["Tentacles"]], where { my $tentacles = $_; for my $tentacle (@$tentacles) { return 0 if $tentacle->color ne "purple"; } return 1; };
use MyCompany::Types -all; has tentacles => (is => 'ro', isa => PurpleTentacles);
Unlike Moose, coercions are separate from type checking
No solution that worked with both Moose and Moo until recently
package Client; use Moo; use URI; has uri => (is => 'ro', coerce => \&string_to_uri ); sub string_to_uri { my $value = shift; return $value if ref $value eq 'URI' return URI->new($value); }
my $client = Client->new( uri => 'http://example.com' ); $client->uri->host; # returns example.com
Type::Tiny coercions come packaged with types (like in Moose)
package Client; use Moo; use Types::URI -all; has uri => (is => 'ro', isa => URI, coerce => 1);
my $client = Client->new( uri => 'http://example.com' ); $client->uri->host; # returns example.com
package MyCompany::Types; use Type::Library -base; use Type::Utils -all; BEGIN { extends "Types::Standard" }; declare "Greeting", as "Str", where { $_ =~ /Take me to your leader.$/ }; coerce "Greeting", from "Str", via { $_ .= ' Take me to your leader.' };
Too many for me to cover here. See also:
package ConfigFile; use Moo; use Path::Class; has _file => ( is => 'ro', handles => [qw/spew slurp/], default => sub { Path::Class::file('.configuration') }, );
my $config_file = ConfigFile->new(); $config_file->slurp(); # read in file contents
package ConfigFile; use Moo; use Path::Class; has _file => ( is => 'ro', handles => { write => 'spew', read => 'slurp'}, default => sub { Path::Class::file('.configuration') }, );
my $config_file = ConfigFile->new(); $config_file->read(); # slurp in file contents
A code reference run after an attribute is set
has cowboy => ( is => 'ro', trigger => sub { warn "cowboy attr set"; } );
package Brontosaurus; use Moo; extends 'Dinosaur'; before eat => sub { my ($self, $food) = @_; die "bad params" if $food eq 'meat'; };
Receives same params as the original method
Return value is ignored
Good for adding extra validation
package Brontosaurus; use Moo; extends 'Dinosaur'; after eat => sub { my ($self, $food) = @_; $self->log->warning("Brontosaurus does not like to eat $food") if $food eq 'meat'; };
Receives same params as the original method
Return value is ignored
Good for logging/debugging
package Brontosaurus; extends 'Dinosaur'; around eat => sub { my ($orig, $self, $food) = @_; uc $orig->($self, $food); };
Called instead of the original method
package Searchable; use Moo::Role; # state has 'number_of_results' => (is => 'ro', default => sub { 5 }); # behavior sub search { ... }
package Thing; use Moo; with qw/Searchable/;
my $thing = Thing->new(); $thing->search();
package Searchable; use Moo::Role; requires 'search_engine'; sub search { my ($self, $query) = @_; my $json = $self->search_engine->search($query); return JSON->new->utf8->decode($json); }
package Thing; use Moo; with qw/Searchable/; sub search_engine { return Bing->new(); }
print "hi" if Thing->does('Searchable');