Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Perl class attribute composition?

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;
    ...
}
like image 414
Alessandro Avatar asked Dec 17 '22 17:12

Alessandro


2 Answers

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?

like image 169
jrockway Avatar answered Jan 05 '23 12:01

jrockway


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.

like image 35
rafl Avatar answered Jan 05 '23 13:01

rafl