On Wed, Jan 7, 2015 at 3:42 PM, Graham Knop <ha...@haarg.org> wrote: > 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.
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