Method modifiers, roles, and state
Note this bug report for Mouse. Because of formatting issues with code, I'll repost the code here.
First, the code:
#!/usr/bin/perl
use strict;
use warnings;
use Test::Most qw{no_plan};
#-----------------------------------------------------------------
BEGIN {
package My::Test;
use Moose;
has stack => (
is => 'rw',
isa => 'ArrayRef',
lazy => 1,
default => sub { [] },
clearer => 'clear_stack',
);
sub add {
my $self = shift;
push @{ $self->stack }, 3;
}
package My::Test::Around;
use Moose::Role;
around add => sub {
my $next = shift;
my $self = shift;
push @{ $self->stack }, 2;
$self->$next();
};
package My::Test::Before;
use Moose::Role;
before add => sub { push @{ shift->stack }, 1 };
package My::Test::Whole;
use Moose;
extends qw{My::Test};
with qw{My::Test::Before My::Test::Around};
}
#-----------------------------------------------------------------
{
my $T = My::Test::Whole->new;
$T->add;
eq_or_diff $T->stack, [ 1, 2, 3 ]
}
{
my $T = My::Test->new;
$T->add;
eq_or_diff( $T->stack, [3] );
$T->clear_stack;
My::Test::Before->meta->apply($T);
$T->add;
eq_or_diff( $T->stack, [ 1, 3 ] );
$T->clear_stack;
My::Test::Around->meta->apply($T);
$T->add;
eq_or_diff( $T->stack, [ 1, 2, 3 ] );
}
And the output:
ok 1
ok 2
ok 3
not ok 4
# Failed test at moose.t line 62.
# +----+-----+----+----------+
# | Elt|Got | Elt|Expected |
# +----+-----+----+----------+
# * 0|2 * | |
# | 1|1 | 0|1 |
# | | * 1|2 *
# | 2|3 | 2|3 |
# +----+-----+----+----------+
1..4
# Looks like you failed 1 test of 4.
Dubious, test returned 1 (wstat 256, 0x100)
Failed 1/4 subtests
Test Summary Report
-------------------
moose.t (Wstat: 256 Tests: 4 Failed: 1)
Failed test: 4
Non-zero exit status: 1
Files=1, Tests=4, 0 wallclock secs ( 0.02 usr 0.00 sys + 0.23 cusr 0.00 csys = 0.25 CPU)
Result: FAIL
The sharp-eyed will have noticed that I did s/Mouse/Moose/g on the code. That's because this bug deals with how method modifiers work, not with Mouse.
The problem is simple to resolve. In the original traits papers, traits (roles) were envisioned to be pure methods and to have no access to state. This turned out to be unrealistic and later papers attempted to address this issue.
In the presence of method modifiers, though, the issues with accessing state are magnified. I would argue that as a matter of good coding style, a method modifier (especially for runtime applied roles!) should never be allowed to modify state. It's as bad as modifying global values and just kind of hoping things will work.
One of the greatest benefits of roles is that, when used properly, the order of consumption should not matter. Thus, roles become declarative in nature, not procedural. Method modifiers which alter state eliminate this benefit.