views:

84

answers:

3

Is it possible to replace a method of a Moose object at runtime ? By looking at the source code of Class::MOP::Method (which Moose::Meta::Method inherits from) I concluded that by doing

 $method->{body} = sub{ my stuff }

I would be able to replace at runtime a method of an object. I can get the method using

 $object->meta->find_method_by_name(<method_name>);

However, this didn't quite work out.

Is it conceivable to modify methods at run time? And, what is the way to do it with Moose?

+3  A: 

Moose or not, that does not sound like a good idea.

Instead, design your object to have an accessor for the method. For example, users of your class can use My::Frobnicator->frobnicator->() to get and invoke the frobnicator method and use My::Frobnicator->frobnicator(sub { } ) to set it.

Sinan Ünür
Sinan very good idea :) thank you
xxxxxxx
+3  A: 

Sinan's idea is a great start.

But with an little extra tweak, you can make using your method accessor just like using a normal method.

#!/usr/bin/perl
use strict;
use warnings;
use Carp;

my $f = Frob->new;

$f->frob(
    sub { 
        my $self = shift;
        print "$self was frobbed\n"; 
        print Carp::longmess('frob') 
    }
);

print "\nCall frob as normal sub\n";
$f->frobit;

print "\nGoto frob\n";
$f->goto_frob;

BEGIN { 
    package Frob;
    use Moose;

    has 'frob' => (
        is => 'rw',
        isa => 'CodeRef',
    );

    sub frobit {
        &{$_[0]->frob};
    }
    sub goto_frob {
        goto $_[0]->frob;
    }

}

The two methods in Frob are very similar.

  • frobit passes all arguments, including the invocant to the code ref.
  • goto_frob passes all arguments, including the invocant to the code ref, and replaces goto_frob's stack frame with the code refs.

Which to use depends on what you want in the stack.


Regarding munging the body storage of a Class::MOP::Method object, like so $method->{body} = sub { 'foo' }:

It's never a good idea to violate encapsulation when you are doing OOP. Especially not when you are working with complex object systems like Moose and Class::MOP. It's asking for trouble. Sometimes, there is no other way to get what you want, but even then, violating encapsulation is still a bad idea.

daotoad
+2  A: 

Using the previously mentioned MooseX::SingletonMethod you can replace an objects method.

For example:

{
    package Foo;
    use MooseX::SingletonMethod;
    sub foo { say 'bar' };
}

my $bar = Foo->new;
my $baz = Foo->new;

# replace foo method just in $baz object
$baz->add_singleton_method( foo => sub { say 'baz' } );

$bar->foo;     # => bar
$baz->foo;     # => baz

Also see this SO answer to What should I do with an object that should no longer be used in Perl?, which shows how this can be achieved using Moose roles.

/I3az/

draegtun