Introductionto Moo – Perl objects with Moo



Introductionto Moo – Perl objects with Moo

0 1


slides-2014-ddg-intro-to-moo

Introduction to Moo presentation

On Github kablamo / slides-2014-ddg-intro-to-moo

NOTE: This presentation is now out of date. Please see http://kablamo.org/slides-intro-to-moofor the most up to date version.

Introductionto 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
XS dependencies Slow startup time Some people are afraid of dependencies Matt wanted an object system for all the places where Moose would not go

Moo features

Moo is pure Perl (no XS) Moo has a fast startup time Moo has about 9 dependencies 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 FATAL => 'all';
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)

 

new() calls Alien->BUILDARGS(@args) new() instantiates object, creating $self new() calls BUILD($self) 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

my $alien = Alien->new(@args) Moo creates $alien->DESTROY() undef $alien Perl calls $alien->DESTROY() $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

  • Types are a bit up in the air atm. Several choices:
    • Roll your own types using Scalar::Util
    • MooX::Types::MooseLike
    • Specio
    • Type::Tiny - not marked stable, but has 50 upvotes

Scalar::Util

use Scalar::Util qw/looks_like_number/;

has antenna => (
  is  => 'ro', 
  isa => sub { ref(shift) eq 'Antenna'},
);

has antenna_count => (
  is  => 'ro', 
  isa => sub { looks_like_number(shift) },
);
          

MooX::Types::MooseLike

use MooX::Types::MooseLike qw/:all/;
has eyes => (is => 'ro', isa => Int);
          

Type::Tiny

use Types::URI -all;
has uri => (is => 'ro', isa => Uri);

use Types::Standard -all;
has is_from_mars => (is => 'ro', isa => Bool);
          

Building types with Type::Tiny

use Scalar::Util qw(looks_like_number);
use Type::Tiny;
 
my $NUM = "Type::Tiny"->new(
   name       => "Number",
   constraint => sub { looks_like_number($_) },
   message    => sub { "$_ ain't a number" },
); 

has eyes => (is => 'ro', isa => $NUM);
          

Coercions

Coercions are also a bit up in the air atm

Unlike Moose, coercions are separate from type checking

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
          

Don't use types and coercions if you want to preserve the ability to switch from Moo to Moose

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