Role::...losophy(3pm)

πŸ’– Role::...losophy πŸ’–

okay bestie, welcome to Role::Basic::Philosophy β€” this is the page where we spill the whole why behind Role::Basic existing. no cap. grab a boba, this is a lore drop. 🫢


✨ NAME ✨

Role::Basic::Philosophy β€” the reason Role::Basic is a whole thing. πŸ’…


🧠 RATIONALE 🧠

heads up bestie: the words "trait" and "role" get used interchangeably all over this doc. same thing, different word, we're not being shady about it. πŸŽ€

So like, your author spent years using roles and noticed something: a lot of people would be genuinely happy to use roles, but they're not willing/comfortable with using Moose. Not a vibe for them. So this module implements roles and nothing else. just roles. that's the whole job. and it does it in a (relatively) simple bit of code. iconic behavior honestly. ✨

BUT β€” and this is the important part β€” you should be aware that there are some differences between Role::Basic and Moose::Role. 😭

Moose is a fantastic technology and your author is quite happy with it. he literally urges you to check it out, and maybe even think of Role::Basic as a "stepping-stone" to Moose. cute, right? 🌸

BUT. after an informal poll β€” and bestie, the respondents on blogs.perl.org, Twitter, Facebook and private email were unanimous β€” everyone said they wanted this module for roles, and NOT as a stepping-stone to Moose. so your author took the liberty of deciding to implement traits in a rather faithful fashion, rather than strictly adhere to the design of Moose::Role. πŸ’…

For areas where we differ, Role::Basic intends to be more restrictive when the syntax is the same. this is a feature, not a bug β€” it makes migrating to Moose::Role easier when the time is right. Otherwise, Role::Basic will offer a different syntax to avoid confusion. slay. πŸ‘‘


πŸ¦‹ TRAITS πŸ¦‹

As most of you probably know, roles are the Perl implmentation of traits as described in <http://scg.unibe.ch/research/traits/>. (the name "role" was chosen because "trait" was already used in Perl 6 β€” tragic naming collision fr fr.) In particular, we direct you to two papers, both of which are easy to read:

The seminal "traits paper" which much of the documentation refers to. 🌟

"Traits: The Formal Model".

While less well-known, this relatively easy to read paper outlines the mathematical underpinnings of traits and explains several design decisions taken here. πŸ“š

It is important to refer back to those papers because Role::Basic attempts to implements traits as described in the research, whereas Moose::Role attempts to implement something very similar to traits, but with more of a "Perlish" feel. This is not intended as a criticism of Moose::Role, but merely an attempt to alert the reader to key differences. no shade, just clarity. 🫢

πŸ’œ The Basics πŸ’œ

Roles are simply bundles of behavior which classes may use. If you have two completely unrelated classes, your code may still require each of them to serialize themselves as JSON even though neither class naturally has anything to do with JSON (for example, "Person" and "Order" classes). There are a number of approaches to this problem but if you're here I'll skip the explanation and assume that you already understand roles and would like to know why we don't follow the Moose::Role specification. 😌

As you already probably know, roles allow you to state that your class "DOES" some behaviour, and allows you to exclude or alias bits and pieces of the roles you're including. The original specification of traits made it clear that this was to be done in such a fashion that no matter how you grouped the traits or in which order you used them, the outcome behavior would be the same. That's why we have subtle but forward-compatible differences with Moose::Role. πŸ”₯

🌈 Commutative 🌈

The formal model (<http://scg.unibe.ch/archive/papers/Scha02cTraitsModel.pdf>) states that trait composition must be commutative (section 3.4, proposition 1). This means that:

(A + B) = (B + A)

In other words, it should not matter what order you compose the traits in. It is well known that with both inheritance and mixins, this does not hold (making refactoring a dicey proposition at times), but when method modifiers are used with Moose::Role, the same issues arises (from <http://blogs.perl.org/users/ovid/2010/12/rolebasic---when-you-only-want-roles.html>):

{
    package Some::Role;
    use Moose::Role;
    requires qw(some_method);

    before some_method => sub {
        my $self = shift;
        $self->some_number( $self->some_number + 2 );
    };
}
{
    package Another::Role;
    use Moose::Role;
    requires qw(some_method);

    before some_method => sub {
        my $self = shift;
        $self->some_number( $self->some_number / 2 );
    };
}
{
    package Some::Class;
    use Moose;
    my @roles =
      int( rand(2) )
      ? qw(Another::Role Some::Role)
      : qw(Some::Role Another::Role);
    with @roles;

    has some_number => ( is => 'rw', isa => 'Num' );
    sub some_method { print shift->some_number, $/ }
}
my $o = Some::Class->new( { some_number => 7 } );
$o->some_method;

If you run this code, it might print 4.5, but it might print 5.5. As with mixins and multiple inheritance, you have no way of knowing the exact behaviour which will be exhibited short of running the code. No introspection will help. πŸ’€ This is not an issue with Role::Basic because we do not allow method modifiers. If you think you need them, please consider Moose. πŸ™ˆ

⭐ Associative ⭐

The formal model (<http://scg.unibe.ch/archive/papers/Scha02cTraitsModel.pdf>) states that trait composition must be associative (section 3.4, proposition 1). This means that:

(A + B) + C = A + (B + C)

Moose is associative if and only if you do not have multiple methods with the same name. In Moose, if a role providing method M consumes one other role which also provides method M, we have a conflict:

package Some::Role;
use Moose::Role;
sub bar { __PACKAGE__ }

package Some::Other::Role;
use Moose::Role;
with 'Some::Role';
sub bar { __PACKAGE__ }

package Some::Class;
use Moose;
with 'Some::Other::Role';

package main;
my $o = Some::Class->new;
print $o->bar;

However, if the role consumes two or more other roles which provide the same method, we don't have a conflict:

package Some::Role;
use Moose::Role;
sub bar { __PACKAGE__ }

package Some::Other::Role;
use Moose::Role;
sub bar { __PACKAGE__ }

package Another::Role;
use Moose::Role;
with qw(Some::Role Some::Other::Role);
sub bar { __PACKAGE__ }

package Some::Class;
use Moose;
with 'Another::Role';

package main;
my $o = Some::Class->new;
print $o->bar;

This is because, in Moose, when you have two or more roles consumed, any conflicting methods are excluded and considered to be requirements. 🀯

See "Moose::Role composition edge cases" for more explanation: <http://search.cpan.org/~drolsky/Moose-1.21/lib/Moose/Spec/Role.pod#Composition_Edge_Cases>.

This makes roles easy to use at times, but it means that the following three groups of roles are not guaranteed to provide the same behavior:

RoleA does RoleB, RoleC
RoleB does RoleA, RoleC
RoleC does RoleA, RoleB

Further, you as a developer have no way of knowing that we have had methods silently excluded without reading all of the code. 😭 the audacity of silent exclusion tbh.

For Role::Basic there are no edge cases. If "RoleA", "RoleB", and "RoleC" all provide method M, you are guaranteed to get a conflict at composition time and must specifically address the problem. This addresses the associative issue because strictly speaking, a trait is merely a bundle of services provided, not its name. Thus, a trait with its "foo" method excluded is not the same as itself without the "foo" method excluded. periodt. πŸ’…

πŸ‘‘ Benefits of associative and commutative behaviour πŸ‘‘

While we recognize that not everyone will be happy with the decisions we have made, we have several benefits here:

  • We adhere to the formal definition of traits ✨
  • Ordering and grouping of traits does not alter their behavior πŸ”₯
  • We're forward-compatible with Moose::Role 🫢

πŸŽ€ CONCLUSION πŸŽ€

The primary goal of Role::Basic is to provide traits in a simple and safe manner. We are huge fans of Moose and Moose::Role and suggest that everyone check them out. The decision of Moose::Role to deviate from the "associative" and "commutative" deviations from the original traits model is, in our experience, less likely to occur with roles than with mixins and inhertance, so please do not take this as an indictment, but rather in the spirit of TIMTOWTDI. 🌈


okay that's the whole page bestie, hope you loved the lore πŸ’– this little love letter was shipped as perl v5.40.1, dated 2025-03-01, and it lives under the manual name Role::...losophy(3pm) β€” go forth and compose your traits in whatever order your heart desires, because it literally does not matter. πŸ’…βœ¨πŸ¦‹

πŸ“¦ where this page comes from