3

I'm trying to stick to the published API to dynamically modify methods in Moo, and have not come up with a general solution.

First off, some code:

package R1 {
    use Moo::Role;
    sub r1 { say __PACKAGE__ }
}

package C1 {
    use Moo;
    sub c1 { say __PACKAGE__ }
}

use Scalar::Util qw[ blessed ];
use Moo::Role ();

my $c = C1->new;
Moo::Role->apply_roles_to_object( $c, 'R1' );

The role application will illustrate a failure in one approach.

I've tried two approaches.

The first uses Class::Method::Modifiers:

use Class::Method::Modifiers qw[ install_modifier ];
install_modifier( blessed( $c ), 
                  before => r1 =>
                  sub { say "BEFORE r1" }
                );
$c->r1;

and works fine:

% perl test.pl
BEFORE r1
R1

The code for Moo's internal _install_modifier subroutine is very similar, but also performs additional Moo specific actions, so this approach is not exactly equivalent.

The next approach I tried was to directly use the before modifier available to $c, and thus get the additional Moo special sauce:

$c->can('before')->( r1 => sub { say "BEFORE r1" } );
$c->r1;

But...

% perl test.pl
The method 'r1' is not found in the inheritance hierarchy for class C1 at [...]/lib/site_perl/5.28.0/Class/Method/Modifiers.pm line 42.
        Class::Method::Modifiers::install_modifier("C1", "before", "r1") called at /[...]/lib/site_perl/5.28.0/Moo/_Utils.pm line 44
        Moo::_Utils::_install_modifier("C1", "before", "r1", CODE(0x5590bb800360)) called at [...]/lib/site_perl/5.28.0/Moo.pm line 84
        Moo::before("r1", CODE(0x5590bb800360)) called at test.pl line 25

It seems that modifiers were generated for the original C1 class, and not updated when the R1 role was applied. The following egregious hack "fixes" that:

use Import::Into;
Moo->import::into( blessed $c );

$c->can('before')->( r1 => sub { say "BEFORE r1" } );
$c->r1;

which results in:

% perl test.pl
BEFORE r1
R1

So, is there a means of achieving my goal using only the published Moo API?

Thanks!

haukex
  • 2,973
  • 9
  • 21
Diab Jerius
  • 2,310
  • 13
  • 18
  • That sounds like the sort of thing you might use metaclasses for. – melpomene Jan 27 '19 at 20:47
  • 1
    Why not use Moose, which offers a proper api for this. Moo is like Moose without the meta stuff, if you start needing it, just use Moose. They should be reasonably compatible, so you don't have to redevelop everything. – bytepusher Jan 27 '19 at 21:55
  • @bytepusher Unfortunately, for my applications in my compute environment Moose has too high of a startup overhead. – Diab Jerius Jan 28 '19 at 00:15
  • 1
    In fact you can use the Moose metaclass directly from a Moo object by calling `->meta`, but this just means the overhead will be when you inflate the metaclass instead of during compilation. – Grinnz Jan 28 '19 at 19:25
  • @DiabJerius When do you need to make this modification? Before or after the call to `apply_roles_to_objects`? Grinnz's [answer](https://stackoverflow.com/a/54408953) works in the "before" case. – haukex Jan 28 '19 at 19:44
  • It needs to be done in code which is completely separate from the class definition. The proximity of the code in my example was simply to keep it short. – Diab Jerius Jan 29 '19 at 17:35
  • 1
    Honestly since you ruled out the metaclass approach, if you cannot solve the problem with roles, I would suggest a completely different approach, like event distribution, a form of instantaneous pub/sub. Instead of trying to wrap methods, you change the methods to emit events, and then other parts of the system subscribe to the events they want to affect. See [Role::EventEmitter and all the similar options listed in its SEE ALSO](https://metacpan.org/pod/Role::EventEmitter#SEE-ALSO). – Grinnz Jan 29 '19 at 20:10
  • Moo's internal API provides support for what I need to do, so there's enough of a MOP in Moo. I just want to make sure that I haven't missed the obvious way of doing it with the public API. – Diab Jerius Jan 29 '19 at 20:17
  • I strongly recommend against relying on Moo's "internal API". You are free to keep fighting against your tools, but it doesn't seem like a good use of time. – Grinnz Jan 29 '19 at 20:40
  • Well, that's always the balance, isn't it? Since I have constraints on what I can use I have to use the tools I have available and push them as far as I can. – Diab Jerius Jan 30 '19 at 15:46

1 Answers1

1

You can modify methods by just applying another role (it doesn't even have to be Moo::Role unless you are dealing with attributes):

use Role::Tiny;
before r1 => sub { say "BEFORE r1" };

Just make sure you apply this role after the one that composes the r1 method, or include a dummy sub r1 {} in the role (if one already exists it will be ignored).

Grinnz
  • 9,093
  • 11
  • 18
  • I'm afraid I don't see how this solves the issue. This will only work when defining the class, not afterwards. I need to modify the methods outside of class definition. – Diab Jerius Jan 29 '19 at 17:32
  • @DiabJerius I'm not sure what you mean. You can apply roles at any time. Do you mean that the manner of modifying the methods is dynamically determined, and you can't create such roles to cover all cases? – Grinnz Jan 29 '19 at 20:03
  • Yes to your question. Classes are generated dynamically and their methods are also modified dynamically. As for applying roles at any time, that's technically not quite correct -- you can't apply roles after a class has been instantiated, see https://rt.cpan.org/Ticket/Display.html?id=101631. – Diab Jerius Jan 29 '19 at 20:28
  • @DiabJerius You absolutely can apply roles after it has been instantiated, this is the entire purpose of the apply_roles_to_object method. This creates a new class and reblesses the object. – Grinnz Jan 29 '19 at 20:41
  • This is escalating a bit I think, so let me clarify. My question is about modifying classes, not objects and the RT link I pointed to is about classes, not objects. I understand that roles can be applied to objects (I used apply_roles_to_object in my example code). – Diab Jerius Jan 30 '19 at 15:34
  • @DiabJerius Yes classes should be modified only before instantiation (or generally, "during its compilation unit") and anything else can be problematic. But why does it matter whether you modify the class or a new class that you rebless the object to? – Grinnz Jan 30 '19 at 18:28