Introduction
to Moo

Perl objects with Moo

Eric Johnson / @kablamo_

About Moo

  • OO sugar
  • Extensible - see MooX:: on MetaCPAN
  • Community approved - used in 698 CPAN modules
  • Created by Matt Trout (MST), first release in 2010
  • (Moose is used in 2320 CPAN modules)
  • (Moose was created by Stevan Little, first release in 2006)

Why Moo

  • Matt liked Moose, but
  • Sometimes people refuse to use Moose
    1. XS dependencies
    2. Slow startup time
    3. Some people are afraid of dependencies
  • Matt wanted an object system for all the places where Moose would not go

Moo features

  1. Moo is pure Perl (no XS)
  2. Moo has a fast startup time
  3. Moo has about 9 dependencies
  4. Moo is compatible with Moose (and Mouse)
    • Compatible syntax
    • Moo objects work with Moose roles
    • Moose objects work with Moo roles
    • If you need introspection, Moo can inflate to Moose

Moo classes

  • Moo classes have
    • Constructor/destructor
    • Attributes
    • Methods
    • Method modifiers
    • Introspection
  • Moo classes do roles

use 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()

Don't use namespace::autoclean

It inflates your Moo class to a Moose class

Object construction

 

my $alien = Alien->new(@args)

 

  1. new() calls Alien->BUILDARGS(@args)
  2. new() instantiates object, creating $self
  3. new() calls BUILD($self)
  4. new() returns $self

BUILDARGS

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

BUILD

is called after instantiation


package Alien;
use Moo;

sub BUILD {
    my ($self, $args) = @_;
   
    # -- additional validation or logging here --
}
          

The return value is ignored

Best practice

  • Validation and logging are fine in BUILDARGS or BUILD
  • Don't do work in BUILDARGS or BUILD
    • It slows down object construction
    • It violates the Principle of Least Astonishment. Users expect new() to just create an object.

DEMOLISH

is called when the object is destroyed


package Alien;
use Moo;

sub DEMOLISH {
    my ($self, $in_global_destruction) = @_;
   
    $self->dbh->disconnect;
}
          

Object destruction

  1. my $alien = Alien->new(@args)
  2. Moo creates $alien->DESTROY()
  3. undef $alien
  4. Perl calls $alien->DESTROY()
  5. $alien->DESTROY() calls $alien->DEMOLISH() at every level of inheritance

Attributes

Read only vs read/write


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
          

Best practice

  • Making attributes mutable means more complexity in your code
  • Start with ro attributes
  • Convert attributes to rw when you need that

Default vs builders


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 }
          

Best practice

  • Use default for very simple defaults
  • Use builder for everything else
    • Builders can be overridden and method modified
    • Roles can require builders
  • Builders should be private

Lazy attributes


has tentacles => (is => 'lazy');
          

is equivalent to


has tentacles => (is => 'ro', lazy => 1, builder => '_build_tentacles');
          

Moo vs Moose

Use MooseX::AttributeShortcuts to get support for is => 'lazy' in Moose

Best practice

  • Lazy attributes are the best - use them as often as you can
  • Because defaults are generated during object construction which slows down construction
  • The user might not need that attribute so why waste time setting a default

Attribute initialization order

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;
}
          

Bug fixed


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;
}
          

Types

  • No solution that worked with both Moo and Moose until recently
  • Type::Tiny has quickly become the community favorite
  • Works with Moo
  • Works with Moose
  • Works with Mouse

Type::Tiny


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"]]);
          

Building a type library


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;
        };

          

How to use your type library


use MyCompany::Types -all;
has tentacles => (is => 'ro', isa => PurpleTentacles);
          

Coercions

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
          

Coercions with Type::Tiny

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
          

Adding coercions
to your type library


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.' };
          

Type::Tiny has many features

Too many for me to cover here. See also:

Delegation


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
          

Delegation


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
          

Triggers

A code reference run after an attribute is set


has cowboy => (
  is      => 'ro', 
  trigger => sub { warn "cowboy attr set"; }
);
          

Method modifiers: before


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

Method modifiers: after


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

Method modifiers: around


package Brontosaurus;
extends 'Dinosaur';

around eat => sub {
  my ($orig, $self, $food) = @_;
  uc $orig->($self, $food);
};
          

Called instead of the original method

Roles

  • Similar to Ruby mixins or Java interfaces
  • Can have attributes, methods, and do other roles
  • Roles add behavior and/or state to a class

Roles example


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();
          

Roles Terminology

  • Classes do or consume roles
  • Roles are composed into classes

requires


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();
}
          

Zen of Roles

  • Roles are shareable between unrelated classes
  • Roles are what a class does not what a class is
  • Roles add functionality, inheritance specializes

Suggestions for naming roles

  • Might end in 'able'
    • Printable
    • Searchable
  • Might start with 'Has' or 'Is' or 'Can'
    • HasEngine
    • IsFragile
    • CanBreakDance

Roles vs Inheritance

  • Brontosaurus isa Dinosaur
  • Brontosaurus does ForagingForPlants (behavior)
  • Other Dinosaurs also do the ForagingForPlants role
  • Brontosaurus does HasLongTail (state)

Role consumption is introspectable


print "hi" if Thing->does('Searchable');
          

Moo roles vs Moose roles

  • In Moose you can apply roles to objects dynamically
  • In Moo you can't

THE END

See also

Find these slides online at