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.

Leave a comment

About Ovid

user-pic Have Perl; Will Travel. Freelance Perl/Testing/Agile consultant. Photo by http://www.circle23.com/. Warning: that site is not safe for work. The photographer is a good friend of mine, though, and it's appropriate to credit his work.