Beefy Boxes and Bandwidth Generously Provided by pair Networks kudra
P is for Practical
 
PerlMonks  

moose role that uses a class that does the role

by mjbetts (Initiate)
on Jun 28, 2012 at 12:14 UTC ( #978902=perlquestion: print w/ replies, xml ) Need Help??
mjbetts has asked for the wisdom of the Perl Monks concerning the following question:

Hi,

I have a moose role and two classes that do it. However, the role uses one of these classes. Is this kind of circularity allowed? I'm getting an error that I don't understand - an instance does a role but can't find a method defined in that role.

I've tried to write the simplest set of code that replicates the problem:

In Hello.pm:

package Hello; use strict; use warnings; use Moose::Role; use namespace::autoclean; use Greeting; sub hello_world { print "hello world\n"; } 1;

In Greeting.pm:

package Greeting; use strict; use warnings; use Moose; use namespace::autoclean; with 'Hello'; __PACKAGE__->meta->make_immutable; 1;

In Greeting2.pm:

package Greeting2; use strict; use warnings; use Moose; use namespace::autoclean; with 'Hello'; __PACKAGE__->meta->make_immutable; 1;

And then a script test.pl that uses these:

#!/usr/bin/perl -w + + use Greeting2; use Greeting; my $greeting = Greeting->new(); print $greeting, "\t", $greeting->does('Hello') ? "does Hello\n" : "do +es not do Hello\n"; $greeting->hello_world();

When I run test.pl I get the following error:

Greeting=HASH(0x8e5da40)	does Hello
Can't locate object method "hello_world" via package "Greeting" at ./test.pl line 9.

How come Greeting does Hello but can't locate the hello_world method? If I 'use Greeting' before 'use Greeting2', or if I don't 'use Greeting' in Hello.pm, I don't get the error.

I'd be really grateful if you could point me in the right direction.

Thanks,

Matthew

Comment on moose role that uses a class that does the role
Select or Download Code
Re: moose role that uses a class that does the role
by tospo (Hermit) on Jun 28, 2012 at 13:29 UTC

    I'm surprised the universe didn't implode... :-)

    Never tried this but, yeah, I guess it's reasonable to assume that the short-circuit is the problem here. What is the use case for something like this? It should be possible to move common functionality out of Greeting and into the Hello role so that the Hello role implements all of that and Greeting just consumes it. After all, that's the ponit of roles.

      Thanks for your reply. My use case is that I have classes that are basically lists of other objects. Within a role I have a few methods that can produce new instances of these classes. Something like the code below (the real code is a nightmare, which may be part of the problem...). There is probably a way to do this along the lines that you suggest, but I'm having a hard time figuring out how...
      package Cluster; use strict; use warnings; use Moose::Role; use namespace::autoclean; use Group; sub cluster { my($self) = @_; # cluster the members of this group, producing several # sub groups whose members may be clustered further my $sub_group1 = Group->new(); my $sub_group2 = Group->new(); return [$sub_group1, $sub_group2]; } 1;
      package Group; use strict; use warnings; use Moose; use namespace::autoclean; with 'Cluster'; __PACKAGE__->meta->make_immutable; 1;

        I guess this sort of recursion won't work as a role. A role can not depend on one of the classes it is consumed by, it should be independent and only define functionality that can be consumed by various classes. Cleary, this separation is not possible here.

        So, why a role at all? Are there really other classes that are very different from Group that can also cluster? If they are similar to the Group class then I think this would better be designed using inheritance, where Group implements the "cluster" method and then you may have sub-classes of Group (or come up with a new parent that Group is a child of too) which modify the behaviour of Group.

        What if you made the role parametric in one way or another? The role could require the presence of a method (created_class_name()) which Group could provide and, in this case, returns only the string 'Group'. That would decouple the class and the role, at least in the code you've shown here.

        Another approach is to use MooseX::Role::Parameterized and pass in the class name when you compose the role.


        Improve your skills with Modern Perl: the free book.

Re: moose role that uses a class that does the role
by duelafn (Priest) on Jun 28, 2012 at 14:14 UTC

    I would guess that you really want to use requires in your role to require certain functionality of the consumer of your role (in this case Greeting) possibly combined with normal inheritance. For example:

    package Hello; use Moose::Role; use namespace::autoclean; # Insist that someone who "with"s us, has a greet method requires "greet"; sub hello_world { my $self = shift; $self->greet("hello world\n"); } 1;

    Greeting:

    package Greeting; use Moose; use namespace::autoclean; with 'Hello'; sub greet { my ($self, @stuff) = @_; print @stuff; } __PACKAGE__->meta->make_immutable; 1;

    Greeting2:

    package Greeting2; use Moose; use namespace::autoclean; with 'Hello'; sub greet { my ($self, @stuff) = @_; print map uc($_), @stuff; } __PACKAGE__->meta->make_immutable; 1;

    Greeting3 (uses the greet from Greeting):

    package Greeting3; use Moose; use namespace::autoclean; extends 'Greeting'; # already "with" Hello __PACKAGE__->meta->make_immutable; 1;

    Now your test script produces:

    Greeting=HASH(0x2b411f8) does Hello hello world Greeting2=HASH(0x1a541f8) does Hello HELLO WORLD Greeting3=HASH(0x15c01f8) does Hello hello world

    Update: Added script output. Also, I second tospo's suggestion to move all of the common functionality to the Hello role

    Good Day,
        Dean

Re: moose role that uses a class that does the role
by tobyink (Abbot) on Jun 29, 2012 at 08:51 UTC

    In Hello.pm, you can change use Greeting to require Greeting, and then all should be well.

    Another fix would be to swap the two use lines in test.pl

    The reason this problem occurs is as follows.

    1. perl loads test.pl; it encounters use Greeting2.
    2. perl loads and compiles Greeting2.pm with no problems. Then with 'Hello' gets executed.
    3. Moose::with loads Hello.pm.

    Now we've got to look at what happens during the compilation stage of Hello.pm. This is when use and BEGIN things are executed, but not other stuff. In particular, no subs have been defined within the Hello package yet!

    1. perl encounters use Greeting.
    2. perl loads and compiles Greeting.pm with no problems. Then with 'Hello' gets executed.
    3. Moose says to itself, "the Hello role has already been loaded, I don't need to load it again".
    4. Moose copies all methods defined in the Hello role into the Greeting class. But, as we established before stage 4, there are not any subs in Hello yet!

    ... and the rest of the load process is uninteresting.

    Changing use Greeting to require Greeting in Hello.pm fixes things because it forces that step 4 to happen a little later, after Hello has been fully compiled.

    See also: leaky abstraction.

    Update: as a general principle, for "cross-loading" between classes and roles within the same "clan" (i.e. collection of modules written for a particular project), I'd recommend loading them at run time instead of compile time. That means load them with require rather than use; or load them with Class::Load (which is one of Moose's dependencies, so you've already got it!); or rely on the fact that with and extends automatically load their argument. This advice only applies to classes and roles within the clan; not to pragmata, exporters, third-party classes, etc.

    perl -E'sub Monkey::do{say$_,for@_,do{($monkey=[caller(0)]->[3])=~s{::}{ }and$monkey}}"Monkey say"->Monkey::do'

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://978902]
Front-paged by Corion
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others avoiding work at the Monastery: (14)
As of 2014-04-16 20:06 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    April first is:







    Results (433 votes), past polls