4

Suppose I have multiple roles, each one defining a set of items:

package A;
use Moose::Role;
sub items () { qw/apple orange/ }

package B;
use Moose::Role;
with 'A';
sub items () { qw/watermelon/ }

package C;
use Moose::Role;
sub items () { qw/banana/ }

Suppose I use them in another class and I want to collect all those items:

package Foo;
use Moose;
with qw(B C);

sub do_something {
    my $self = shift;
    my @items = ???;   # How can I get apple, orange, watermelon, banana here?
    ....
}

One possible solution is to adopt MooseX::ComposedBehavior but its POD says (at the time of writing, of course) that its API "is not quite stable" and also that "the current implementation is something of a hack, and should be replaced by a more robust one". Thus I'm investigating whether this could be accomplished without relying on such a "hack".

Warning: if you are reading this in the future, please go to check the POD of MooseX::ComposedBehavior (current version: 0.003) because it might have changed in the mean time. Things change quickly. CPAN authors release new versions. What's "not quite stable" at the moment might become more stable in the future. There might even be other modules. Check yourself.

Ideally there should be something like: my @items = map $_->items, @ISA; However that won't work with Moose. Are there any nicer and more reliable solutions?


Update: I ended up with this three-line solution:

package A;
use Moose::Role;
sub items () { qw/apple orange/ }

package B;
use Moose::Role;
with 'A';
sub items () { qw/watermelon/ }

package C;
use Moose::Role;
sub items () { qw/banana/ }

package Foo;
use Moose;
with qw(B C);
sub items () {}

sub do_something {
    my $self = shift;

    my @items = map $_->execute, grep $_, 
        map $_->get_method('items'),
        $self->meta->calculate_all_roles_with_inheritance;

    ...
}

Update: As various people requested me in the #moose IRC channel I removed my previous assertion that MooseX::ComposedBehavior "is not stable" and replaced it with literal text taken from its POD.


Update: I wrote a MooseX::Collect module which allows the following syntax:

package Foo;
use Moose;
use MooseX::Collect;

collect 'items';
with qw(B C);

sub do_something {
    my $self = shift;
    my @items = $self->items;
    ...
}
Alessandro
  • 1,336
  • 8
  • 15
  • 3
    I don't think you want roles here. When you compose the "B" and "C" roles into Foo, you will have created a conflict with the "items" method and not only will Foo not compile, but the way to get it too compile is to create your own "items" method. – Stevan Little Jan 31 '11 at 14:41

2 Answers2

7

You need to use around:

package A;
use Moose::Role;
requires 'items';
around items => sub {
    my ($orig, $self, @args) = @_;
    return ($self->$orig(@args), qw/apple orange/);
};

package B;
use Moose::Role;
requires 'items';
with 'A'; # not required, do it if you want it
around items => sub {
    my ($orig, $self, @args) = @_;
    return ($self->$orig(@args), qw/watermelon/);
};

package C;
use Moose::Role;
requires 'items';
around items => sub {
    my ($orig, $self, @args) = @_;
    return ($self->$orig(@args), qw/banana/);
};

package Class;
use Moose;
with qw/B C/;
sub items {}

But in general, using classes to represent data is wrong, that's what instances of classes are for. It's hard to provide further advice since your example is so trival. What are you really trying to do?

jrockway
  • 42,082
  • 9
  • 61
  • 86
  • I thought about using `override items => sub { return super(), qw(banana) };`. My roles will describe application features, and each of them defines a `Data::Schema` validation schema that will be applied to the application config. – Alessandro Jan 31 '11 at 12:51
  • Another approach using `around` might wrap BUILDARGS instead, modifying the initial input once (provided my recollection of whether or not roles can affect BUILDARGS). – Carl Jan 31 '11 at 14:26
  • 1
    @Allesandro - you are confusing roles with classes, override/super in roles end up running in the context of the class they are composed into, not the roles themselves. – Stevan Little Jan 31 '11 at 14:44
5

After having you pointed at MooseX::ComposedBehavior before on IRC, I'm not entirely sure why you feel you shouldn't be using it. It does, after all, solve exactly the problem you're having.

Yes, it does say its interface may change slightly in the future. However, how much work will it be for you to adapt to those slight changes? In comparison, how long do you think it'll take you to come up with an alternative solution and to actually implement that? How do you think will your solution compare to MooseX::ComposedBehavior in things like correctness and robustness? At least I wouldn't trust myself in reinventing a wheel originally invented by RJBS and expecting my solution to turn out better.

Also, if you're really super worried about a module warning you about possible future changes, go work with its author and help him get it into a shape that he's happy with for declaring it as stable. Write some more tests for your specific usecase. Talk to Ricardo, he's a nice guy.

rafl
  • 11,980
  • 2
  • 55
  • 77
  • 2
    I was a bit scared by MooseX::ComposedBehavior warning in its POD that "current implementation is something of a hack" and that its API "is not quite stable, and may yet change", so I wanted to double check whether my need could be satisfied without recurring to a "hack". Other than that, I know well Ricardo's modules and I don't discuss their quality :-) – Alessandro Jan 31 '11 at 15:28
  • @Alessandro: the universe's API is not quite stable and may yet change, so I give wording like that a bit of a grain of salt :) – Ether Jan 31 '11 at 16:01
  • 2
    It also says that changes are likely to be in the guts and that you may run into problems if you stray from the documented API. I don't see how either of those would affect your use case. – hdp Jan 31 '11 at 16:22