I use Moo in my latest toy project. When experimenting with Moo::Role, I discovered the rules of interaction of role composition and inheritance are not specified in detail, and the current behaviour surprised me a bit.

In the examples below, I'll use Role::Tiny, as that's what Moo::Role uses under the hood, and it also contains all the important documentation.

The basic rule of role composition is the following:

If a method is already defined on a class, that method will not be composed in from the role.

Let's see an example:

#! /usr/bin/perl use warnings; use strict; use feature qw{ say }; { package MyRole; use Role::Tiny; sub where { 'Role' } sub role { 'yes' } } { package MyClass; sub new { bless {}, shift } sub where { 'Class' } } { package MyComposed; use Role::Tiny::With; with 'MyRole'; sub new { bless {}, shift } sub where { 'Composed' } } my $c = 'MyComposed'->new; say $c->$_ for qw( where role );

Output:

Composed yes

The "yes" shows the role was composed into the class, but the "where" method still comes from the original class. So far, so good.

What do you think should happen, if the class doesn't implement the method, but inherits it from a parent?

{ package MyHeir; use parent -norequire => 'MyClass'; use Role::Tiny::With; with 'MyRole'; } my $h = 'MyHeir'->new; say $h->$_ for qw( where role );

For me, the output was surprising:

Role yes

The same happens when you apply the role to an instance of a class:

my $o = 'MyClass'->new; 'Role::Tiny'->apply_roles_to_object($o, 'MyRole'); say $o->$_ for qw( where role );

We started with an object of a class that implemented the where method, but the resulting object uses the role's method. Maybe because a new class is created for the object inheriting from the original one, and the role is then applied to it, as with MyHeir above?

In fact, I needed that behaviour. As it's not documented explicitely, though, I decided to program defensively, require the where method, and use the around modifier for better readability and clearer specification of the intent:

{ package MyAround; use Role::Tiny; requires 'where'; around where => sub { 'Around' }; sub role { 'yes' } } my $o2 = 'MyClass'->new; 'Role::Tiny'->apply_roles_to_object($o2, 'MyAround'); say $o2->$_ for qw( where role );

Even if the composition rules changed, my object would still get the where method from the role.

Update: I proposed a documentation fix that was later accepted.

($q=q:Sq=~/;[c](.)(.)/;chr(-||-|5+lengthSq)`"S|oS2"`map{chr |+ord }map{substrSq`S_+|`|}3E|-|`7**2-3:)=~y+S|`+$1,++print+eval$q,q,a,

Replies are listed 'Best First'.
Re: Role Composition versus Inheritance
by Arunbear (Prior) on Feb 09, 2016 at 17:45 UTC
    Moose (2.1604) behaves the same way:
    use warnings; use strict; use feature qw{ say }; { package MyRole; use Moose::Role; sub where { 'Role' } sub role { 'yes' } } { package MyClass; sub new { bless {}, shift } sub where { 'Class' } } { package MyHeir; use Moose; extends 'MyClass'; with 'MyRole'; } my $h = 'MyHeir'->new; say $h->$_ for qw( where role ); __END__ Prints: Role yes
    So the behavior is at least consistent albeit not adequately documented.