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

Reply via email to