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

Extensions via Moose

by John M. Dlugosz (Monsignor)
on Apr 28, 2011 at 13:15 UTC ( [id://901797]=perlquestion: print w/replies, xml ) Need Help??

John M. Dlugosz has asked for the wisdom of the Perl Monks concerning the following question:

I'm working on a class that is a test-bed of sorts, and I want to explore some good way to provide for "plug in" behavior.

In Perl6, you have a but keyword that will dynamically apply a role to an instance, without having to name a new class for it and create the instance of that type.

I saw some clues in the manual the Moose might do this even though it doesn't offer syntactic sugar for it. Is there a good way to do this directly (as opposed to messing with MOP directly)?

That is, can Moose add a role ("traits") to an instance after it has been created, as opposed to having to declare a derived class that is based on the original class and adds the role as well? I'm thinking that one "plug in" might need to add roles to several collaborating objects, as well as do more mundane things with those object's APIs.

Catalyst, for example, lets you specify some extensions as arguments to new. Along the lines I'm thinking, the BUILD routine can slurp them in.

Replies are listed 'Best First'.
Re: Extensions via Moose
by stvn (Monsignor) on Apr 28, 2011 at 21:43 UTC
    can Moose add a role ("traits") to an instance after it has been created

    Yes, this can be done in a couple different ways. First, there is MooseX::Traits which is probably the best way to do this as it is battle tested code that just does the right thing for you, a simple skim of the POD should give you an idea of its capabilities.

    Next, you can use Moose::Util which has apply_all_roles which will not only apply roles to the $applicant, but will handle any type of role parameterization correctly as well. Basically it takes, as a second argument, the same type of list that the with keyword takes.

    If you are going to do this often, you might also want to look at Moose::Util::with_traits, which will create the anon-class for you from the list of roles and return the class name so that you can then use it to create new instances, like so:

    my $class = Moose::Util::with_traits( $a_class, @bunch_of_roles ); my $instance = $class->new;

    Lastly, you can do it the "hard" way, which is to say, do it by hand yourself using the MOP. The simplest way is to use the Role meta-object like so:

    My::Role->meta->apply( $some_instance );
    This will do the right thing for instances, classes and roles. And if you have multiple roles you want to apply you can do this:
    Moose::Meta::Role->combine( @role_meta_objects )->apply( $some_instanc +e );
    But really, you want MooseX::Traits, it is simple and pretty much does exactly what you are looking for.

    -stvn
      It says, Apply the roles to the class at new time:

      That does look like a good way to do it, in letting the user specify which extensions to use when creating the object. I don't have to imitate Catalyst's syntax or pass them as arguments to the new method.

      But, I'm still composing it before construction. Consider a main class C1 which contains instances of C2 and C3 which it creates and holds on to. If all that's already been created, I can "patch" all three objects. If I derive an anonymous class and then instantiate it, the amendment for C1 has to include instructions to make it change what it creates for C2 and C3.

      Now adding a role can do everything a traditional derived class can do, right? So there is no need to provide a mechanism to say "use this derived class instead of C2" in addition to the mechanism that lists all the roles that are to be applied to C2 at creation time.

      I could have the builder that instantiates C2 consult an attribute that says what roles to add to it, and the original plug-in added to that list. But, I don't like the idea of having a field that doesn't do anything except during object creation. There should be a more transient way to pass the information around.

      Maybe the builder can call something which gathers the trait's names, and the original plug-in can supply 'before' methods to grow that list. (But if I only have one instance anyway, all the extra code is worse than having a defunct slot!)

      Any more suggestions?

        But, I'm still composing it before construction.

        With MooseX::Traits, yes you are, however doing Role->meta->apply( $instance ) will let you do it after construction, as well the Moose::Util tools as well.

        If I derive an anonymous class and then instantiate it, the amendment for C1 has to include instructions to make it change what it creates for C2 and C3.

        I am not sure what you are asking here, are you looking for a trigger to be fired when C1 is reblessed into the anon-class? So that it will then do something with the C2 and C3 instances? If that is the case, no Moose does not provide that, however it would be pretty simple to just do yourself when the role is added to the C1 instance.

        Now adding a role can do everything a traditional derived class can do, right? So there is no need to provide a mechanism to say "use this derived class instead of C2" in addition to the mechanism that lists all the roles that are to be applied to C2 at creation time.

        When a role is applied to an instance, a new anon-class is created whose superclass is the old class (C2) and with the role added in. Then the instance is reblessed into that anon-class (see Class::MOP for all the reblessed MOP API). So, I believe I am answering your first question right when I say "Yes".

        I could have the builder that instantiates C2 consult an attribute that says what roles to add to it, and the original plug-in added to that list. But, I don't like the idea of having a field that doesn't do anything except during object creation. There should be a more transient way to pass the information around.

        I think I am missing a large part of your design, there are a bunch of ways to go about this, all different depending on your needs.

        Maybe the builder can call something which gathers the trait's names, and the original plug-in can supply 'before' methods to grow that list. (But if I only have one instance anyway, all the extra code is worse than having a defunct slot!)

        Well, the question really is, will it *always* just be one instance? As long as you don't get too carried away, it might be worth the extra effort to make it extensible.

        Any more suggestions?

        Like I said, there are many ways to go about this, runtime-roles (roles applied to an instance) are a powerful feature (thanks Perl 6!) and you can (ab)use them in lots of different ways. It all comes down to what is the best fit for your design.

        -stvn

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others romping around the Monastery: (3)
As of 2024-04-18 23:12 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found