tags:

views:

747

answers:

2

I have written a set of classes and interfaces that are implemented in Moose also using roles. What I am having trouble understanding is the exact differences in both usage and implementation of Moose traits vs. roles.

The Moose documentation states:

It is important to understand that roles and traits are the same thing. A role can be used as a trait, and a trait is a role. The only thing that distinguishes the two is that a trait is packaged in a way that lets Moose resolve a short name to a class name. In other words, with a trait, the caller can refer to it by a short name like "Big", and Moose will resolve it to a class like MooseX::Embiggen::Meta::Attribute::Role::Big.

It is my understanding that traits and roles are "the same". However, when implementing a basic test of the idea using the use Moose -traits 'Foo' syntax does not seem to do what I would expect. Surely I must be missing something here.

This first example fails with "Can't locate object method 'foo'"

package MyApp::Meta::Class::Trait::HasTable;
use Moose::Role;
sub foo { warn 'foo' }

package Moose::Meta::Class::Custom::Trait::HasTable;
sub register_implementation { 'MyApp::Meta::Class::Trait::HasTable' }

package MyApp::User;
use Moose -traits => 'HasTable';
__PACKAGE__->foo();  #Can't locate object method 'foo'

Compared to this one (which does work):

package MyApp::Meta::Class::Trait::HasTable;
use Moose::Role;
sub foo { warn 'foo' }

package Moose::Meta::Class::Custom::Trait::HasTable;
sub register_implementation { 'MyApp::Meta::Class::Trait::HasTable' }

package MyApp::User;
use Moose;
with 'MyApp::Meta::Class::Trait::HasTable';
__PACKAGE__->foo();  #foo
A: 

You don't define a package 'x::Foo' with any role. Ripped straight from the documentation, we see that register_implementation returns the name of an actually defined package:

package MyApp::Meta::Class::Trait::HasTable;
use Moose::Role;

has table => (
  is  => 'rw',
  isa => 'Str',
);

package Moose::Meta::Class::Custom::Trait::HasTable;
sub register_implementation { 'MyApp::Meta::Class::Trait::HasTable' }

package MyApp::User;
use Moose -traits => 'HasTable';

__PACKAGE__->meta->table('User');

The "shortcut" is achieved by Moose looking for "Moose::Meta::Class::Trait::$trait_name" (when called in a "class context"), not just passing back a shorter name.

Axeman
Please see my revision to my question, I have tried to add more detail.
Danny
+6  A: 

This is the only difference in how Moose uses the terms "Trait" and "Role". Moose's documentation and APIs often use the term "traits" as "Roles applied to Metaclasses". In your revised answer your first example applies the Role to MyApp::User's metaclass via -traits, the second example applies it to the class.

If you change your first example to:

package MyApp::Meta::Class::Trait::HasTable;
use Moose::Role;
sub foo { warn 'foo' }

package Moose::Meta::Class::Custom::Trait::HasTable;
sub register_implementation { 'MyApp::Meta::Class::Trait::HasTable' }

package MyApp::User;
use Moose -traits => 'HasTable';
__PACKAGE__->meta->foo();

You'll see "foo at [script]. line 3." Which is exactly what it supposed to be doing.

UPDATE: Apparently I'm not exactly correct here. Traits are roles applied to instances. The -traits hook applies HasTable to the metaclass instance for MyApp::User. I have updated the relevant Moose docs.

perigrin
Thanks, that explains the behavior I was seeing.
Danny
I've updated the text in Extending/Recipe1.pod to try to reflect this difference a bit. Also note to confuse things more Role-like things are called Traits in other languages like Scala. These Traits are defined in some papers that informed the origin of Moose's Role implementation but lack the state carrying features (ie they're methods only and have no no attributes).
perigrin