views:

167

answers:

2

I have a Role and several classes that mix-in the role. The Role class loads all of the implementing classes so that anything that imports Blah can use them without typing a lot of 'use' lines.

package Blah;
use Moose::Role;

use Blah::A;
use Blah::B;
(etc...)

requires '...';
requires 'foo';
around 'foo' => sub { ... }

A typical Blah subclass:

package Blah::A;
use Moose;
with 'Blah';

sub foo { ... }

__PACKAGE__->meta->make_immutable;

Since every subclass 'foo' method starts with the same bits of code, the role also implements these via a method modifier.

Problem is: Moose doesn't apply the method modifier to any of the Blah::* classes. This happens even if I remove the make_immutable call for the classes. I thought role application was done entirely at runtime, and so even though the Blah::* classes are loaded before Blah, the modifier should still be applied?

I'm looking for a fix, or an alternate way of doing things. At the moment Blah is essentially an abstract base class except for the method modifier, which is why I used roles to begin with - but maybe a class hierarchy would be better? Thanks in advance.

+3  A: 

Your call order is a little odd -- why are you useing Blah::A from within the role that is then applied to Blah::A?

I would suggest pulling out these use lines and moving them to where they are actually needed (in the caller(s)). Get the code working first, and after that, if you have lots of use lines cluttering things up everywhere, you could move them off into an Includes file.

But no, in answer to your assumption -- role application is not done at runtime, but at whatever time the with line is encountered. If you use a module at compile-time, then that file is compiled immediately, and the with line executed (which then forces the role to be compiled, and then it is run). You can of course apply a role at runtime as well (e.g. see apply_all_roles in Moose::Util), but that is not what is happening here.

Ether
Accepted, thanks. The use lines are annoying, but I guess I'll write a factory module for them instead of overloading the role module :)
rjh
@rjh: yes, you're just loading those target modules too soon, before the role has had a chance to finish building itself and get applied to them.
Ether
A: 

I think you simply misunderstand the difference between file-inclusion, and role composition.

Under the hood, use statements simply call require and then the inferred Package's import() statement and wrap all of this in a BEGIN {} block.

It doesn't install the functions as meta-class methods with Class::MOP (CMOP). I'm not sure what the difference is between a declared method and an imported one, or how CMOP tells the difference but this works only because of the call to add_method. I'd ask for more information from irc.perl.org/#moose, but I'm banned. Hopefully this example will tell you what you need, or give you more information to formulate a better question.

package Class;
use Moose;
use Carp qw(carp);

### You have to add the method onto the class.
Class->meta->add_method( 'carp' => \&carp );

around 'carp' => sub { warn "this won't trigger" };

package main;

my $c = Class->new;
$c->carp('foo');

If possible I'd rewrite those packages your useing into Moose friendly Roles, then just have the current role call the new roles using the with statement. Roles will handle method modifiers around other role-provided methods.

Evan Carroll
Evan I think you have entirely missed the question here. He *is* using a Role (Blah is a Moose::Role). His issues are related to the circular use statements (use-ing a Class that does the Role you're currently defining is bad M'kay?) and timing (composition as ether points out happens when the `with` is seen, and can cause issues). I can't see how your example here has anything to do with the question since he's neither using imports, nor shown an example of entirely overriding a method with an `around` modifier like you've done here. Rjh is free to come into #moose for more information.
perigrin
Going up to bat, I'm going to assume the issue is *specific* to the `use` cases because that code will work without it (remove the use's and requires) instantiate `Blah::A`, and I believe the 'around' will work. Maybe I tried to infer to much.
Evan Carroll