Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

Re: Moo: inheritance conflicting with role composition?

6 views
Skip to first unread message

Graham Knop

unread,
Jan 8, 2015, 9:45:02 AM1/8/15
to mo...@perl.org, Diab Jerius
On 1/7/15 2:18 PM, Diab Jerius wrote:
> Hi!
>
> I've attached some example code which exhibits (to my thinking) an
> unexpected collision between inheritance and composition. I'm using
> Moo v. 1.006001.
>
> There is a base role (R0) which provides a method, track().
>
> The role is consumed by another role (R1) which is consumed by a class
> (C1), each of which modifies track().
>
> C1->new->track exhibits the correct series of modifications.
>
> C2 is a class which inherits from C1 and also modifies track().
>
> C2->new->track also exhibits the correct behavior with regard to the
> modifications.
>
> C3 is a class which inherits from C1, modifies track(), and consumes R0.
>
> C3->new->track seems to completely ignore its parent class.
>
> The code outputs the packages whose modification was performed. I'm
> getting this
>
> C1: R1 C1
> C2: R1 C1 C2
> C3: C3
>
> I expected the result for C3 to be
>
> C3: R1 C1 C3
>
> The order of C3's consumption of R0 and extension of C1 doesn't change
> the results.
>
> What confuses me is that track() calls up the inheritance chain, so
> that any version of track() which ends up in C3 should know enough to
> call C1's track, and thus see the modifications made at that level.
>
> Please let me know if I'm missing something here!
>
> Thanks,
>
> Diab
>

The problem here is that you are trying to call the superclass of a
role. next::method and its brethren don't work in roles. If the role
wants to call the superclass of whatever class it is consumed by, it
needs to use an around modifier.

Diab Jerius

unread,
Jan 9, 2015, 3:45:03 PM1/9/15
to mo...@perl.org
Thanks.

I should have caught the incorrect use of next::method; that was silly.

My more problematic assumption was that consuming a role which was
already implicitly consumed via inheritance was a no-op. Looking at
the relevant Moo/Role::Tiny code, it looks like

1. role application doesn't check if a role has already been consumed;
2. when checking for methods to install, the inheritance hierarchy
isn't consulted

I'm sure there are good reasons for that, but I find that surprising behavior.

Your suggestion about using an around modifier was spot on. Here's
what I ended up with:

package R0 {

use Moo::Role;
use Carp;
use Sub::Name 'subname';

my $can = sub { ( shift )->next::can };

around track => sub {

# at this point, execution is at the bottom of the stack
# of wrapped calls for the immediate composing class.

# use the calling package as the starting point for the search up
# the inheritance change. as this routine gets called from
# different points, that'll change.

# only run the original method when we've reached the very
# end of the inheritance chain. otherwise it will get run
# for each class (as we bottom out here) which is incorrect.

my $orig = shift;
my $package = caller;

my $next = ( subname "${package}::track" => $can )->( $_[0] );

return $next ? $next->( @_ ) : &$orig;
};

sub track { return __PACKAGE__ }

}


Thanks a lot.

Diab
0 new messages