Jump to content

Programming with Moose/Syntax/before, after and around

From Wikibooks, open books for an open world

before, after, and around are method modifiers. They give control over the chain of events around the actual call to a method. This allows the user to do simple things, easily. Two of the keywords before, and after, do nothing more than trigger code. While around permits dynamic rewriting of the arguments.

These features are documented only in Class::MOP.

Modifiers

[edit | edit source]

before

[edit | edit source]
package Jerk;
use Moose;

sub fart {
	print '\me farts';
}

before 'fart' => sub {
	print '\me laughs';
};

package main;

my $m = Jerk->new;

$m->fart;

after

[edit | edit source]
package Manners;
use Moose;

sub fart {
	print '\me farts';
}

after 'fart' => sub {
	print 'excuse me';
};

package main;

my $m = Manners->new;

$m->fart;

around

[edit | edit source]


# return an array ref, de-referenced.
around foo => sub {
    my $sub = shift;

    my $ret = $sub->(@_);

    @{ $ret }
}

# auto-instantiate an empty array for a hash value
around foo_lookup => sub {
    my ($next, $this, $key) = @_;
    my $arrayref = $this->$next($key);

    $arrayref ||= $this->foo_hashref->{$key} = [];

    return $arrayref;
};

Infinite recursion

[edit | edit source]

It might be tempting to call the function that the modifier is attached to within the modifier's sub. This is bad; observe the following and never do it:

has 'foo' => ( isa => 'Int', is => 'ro' );

after 'foo' => sub {
  my $self = shift;
  say 'Got ' . $self->foo;
}

The result is a bad infinite loop:

  1. ->foo()
  2. The after modifier executes and calls ->foo
  3. ->foo()
  4. The after modifier executes and calls ->foo
  5. ad infinitum

Execution Order

[edit | edit source]

Argument rewriting

[edit | edit source]

What not to do

[edit | edit source]

This is an example of what most people think will work, but it doesn't: the hackish solution that it seems everyone must attempt once. Observe what you should never do!![1]:

package Foobar;
use Data::Dumper;
use Moose;

sub method {
	my ( $self, $ref ) = @_;
	
	print Dumper [ 'method', $ref ];

};

sub modifier {
	my ( $self, $ref ) = @_;

	print Dumper [ 'modifier before', $ref ];

	$ref->[0] = 'hax3d';

	print Dumper [ 'modifier after', $ref ];
	
}

before 'method' => \&modifier;

package main;

my $m = Foobar->new;

$m->method( ['foo'] );

which returns:

$VAR1 = [
          'modifier before',
          [
            'foo'
          ]
        ];

$VAR1 = [
          'modifier after',
          [
            'hax3d'
          ]
        ];

$VAR1 = [
          'method',
          [
            'hax3d'
          ]
        ];


Here you might figure this works, because your able to hack a method of modification into method. but be aware, employing this technique will yield a drastic difference when you pass something other than a reference.

package Foobar;
use Data::Dumper;
use Moose;

sub method {
	my ( $self, $scalar ) = @_;
	
	print Dumper [ 'method', $scalar ];

};

sub modifier {
	my ( $self, $scalar ) = @_;

	print Dumper [ 'modifier before', $scalar ];

	$scalar = 'bar';

	print Dumper [ 'modifier after', $scalar ];
	
}

before 'method' => \&modifier;

package main;

my $m = Foobar->new;

$m->method( 'foo' );
$VAR1 = [
          'modifier before',
          'foo'
        ];

$VAR1 = [
          'modifier after',
          'bar'
        ];

$VAR1 = [
          'method',
          'foo'
        ];

Almost surely not what you were looking for?[2] Don't worry, now for what you should do.

What to do

[edit | edit source]

The Way of the Moose™ is simple, use the more suited around. Moose makes it very direct and clear what you intend to do. Using around your modifier will get: (a) the name of the desired method, (b) a reference to self, and (c) the arguments sent to the function. You can then $self->$sub( @args ) if you wish to dispatch the function or totally rewrite or halt the call.

package Foobar;
use Data::Dumper;
use Moose;

sub method {
	my ( $self, $scalar ) = @_;
	
	print Dumper [ 'method', $scalar ];

};

sub modifier {
	my ( $sub, $self, @args ) = @_;

	print Dumper [ 'modifier', $sub, \@args ];
	$self->$sub( @args );

}

around 'method' => \&modifier;

package main;

my $m = Foobar->new;

$m->method( 'foo' );

returns the following:

$VAR1 = [
          'modifier',
          sub { "DUMMY" },
          [
            'foo'
          ]
        ];

$VAR1 = [
          'method',
          'foo'
        ];

Footnotes

[edit | edit source]
  1. ^ Unless you are writing comprehensive tutorials on both what to do, and not do.
  2. ^ For the more astute coder, the my ( $self, $scalar ) = @_; isn't the culprit here. Direct access on $_[1] will result in a runtime death. @_ is actually a constant.
  3. ^ Not to suggest Moose ever does things that regular perl would not allow... just that here is one of the many areas people wish it would..