Last month I wrote about using Moose’s override func­tion to, well, over­ride a superclass’s method. Chris Prather on the #moose IRC chan­nel sug­gest­ed soon after that the around method mod­i­fi­er (or its lit­tle sis­ters before and after) might be a bet­ter choice if you’re also call­ing the orig­i­nal method inside. He not­ed that at a min­i­mum override only works if you’re sub­class­ing, around will apply to com­posed meth­ods too.”

His point was that when you decide to com­pose roles (also know as traits) instead of or in addi­tion to more tra­di­tion­al inher­i­tance, override sim­ply doesn’t work: only a method mod­i­fi­er will do. (And as Graham Knop and Karen Etheridge lat­er remarked on IRC, override isn’t even an option if you’re using Moo as an alter­na­tive to Moose.)

Modifying a role’s method with around might look like this:

#!/usr/bin/env perl

use v5.12; # for strict and say
use warnings;

package Local::Role::Hungry;
use Moose::Role;
requires 'name';

sub wants_food {
my $self = shift;
say $self->name, ' is hungry!';
return;
}

package Local::GuineaPig;
use Moose;
has name => (is => 'ro');
with 'Local::Role::Hungry';

around wants_food => sub {
my ($orig, $self, @args) = @_;
say $self->name, ' runs to the front of the cage!';
$self->$orig(@args);
say 'Wheek!';
return;
};

package Local::Dog;
use Moose;
has name => (is => 'ro');
with 'Local::Role::Hungry';

around wants_food => sub {
my ($orig, $self, @args) = @_;
say $self->name, ' runs to the kitchen!';
$self->$orig(@args);
say 'Woof!';
return;
};

before wants_food => sub {
my $self = shift;
say $self->name, ' is jumping!';
};

package main;
my $dog = Local::Dog->new(name => 'Seamus');
my @pigs = map { Local::GuineaPig->new(name => $_) }
qw<Cocoa Ginger Pepper>;

for my $animal ($dog, @pigs) {
$animal->wants_food();
}

Running the above yields:

Seamus runs to the kitchen!
Seamus is hungry!
Woof!
Cocoa runs to the front of the cage!
Cocoa is hungry!
Wheek!
Ginger runs to the front of the cage!
Ginger is hungry!
Wheek!
Pepper runs to the front of the cage!
Pepper is hungry!
Wheek!

It’s a lit­tle more involved than over­rid­ing a sub, since method mod­i­fiers are passed both the con­sumed role’s orig­i­nal method ($orig above) and the instance ($self above) as para­me­ters. It has the same effect, though, and you can call the orig­i­nal method by say­ing $self->$orig(parameters).

If all you want to do is have some­thing hap­pen either before or after the orig­i­nal method, just use before or after:

before wants_food => sub {
my $self = shift;
say $self->name, ' is jumping!';
};

Note that there’s no return val­ue in a before or after mod­i­fi­er, as those are han­dled by the orig­i­nal method.

Modifiers aren’t lim­it­ed to con­sum­ing class­es; they can be in roles and mod­i­fy their con­sumers’ meth­ods. They also have a cou­ple of oth­er tricks:

  • You can pass an array ref­er­ence to mod­i­fy mul­ti­ple meth­ods at once.
  • You can use the con­tents of a vari­able to spec­i­fy the mod­i­fied method name, and use that same vari­able in the mod­i­fi­er itself.
  • You can use a reg­u­lar expres­sion to select meth­ods. (Beware if you’re using Moo that its Class::Method::Modifiers mod­ule doesn’t sup­port this.)

Putting these togeth­er gives you con­structs like these:

after qw<foo bar baz> => sub {
say 'Something got called';
};

for my $method_name (qw<foo bar baz>) {
before $method_name => sub {
say "Calling $method_name...";
};
}

before qr/^request_/ => sub {
my ($self, @args) = @_;
$self->is_valid(@args) or die 'Invalid arguments';
};

Moose comes with great intro­duc­to­ry man­u­als for method mod­i­fiers and roles, so be sure to check those out. There’s a lot more to them and a blog can only cov­er so much.

3 thoughts on “Taming the Moose: Method modifiers instead of overrides in object-​oriented Perl

  1. Nice arti­cle. As a minor aside, I find my ($orig, $self) = splice @_, 0, 2; is rather con­fus­ing to many new devel­op­ers. Compounding that with the around mod­i­fi­er makes it even hard­er. How about my ( $orig, $self, @args ) = @_; and lat­er, you can call $self->$orig(@args). I think it’s much eas­i­er to read.

Comments are closed.