简体   繁体   中英

How can I redefine Perl class methods?

The question "How can I monkey-patch an instance method in Perl?" got me thinking. Can I dynamically redefine Perl methods? Say I have a class like this one:

package MyClass;
sub new {
  my $class = shift;
  my $val = shift;
  my $self = { val=> $val};

  bless($self, $class);
  return $self;
};

sub get_val {
  my $self = shift;
  return $self->{val}+10;
}
1;

And let's say that adding two numbers is really expensive.

I'd like to modify the class so that $val+10 is only computed the first time I call the method on that object. Subsequent calls to the method would return a cached value.

I could easily modify the method to include caching, but:

  • I have a bunch of methods like this.
  • I'd rather not dirty up this method.

What I really want to do is specify a list of methods that I know always return the same value for a given instance. I then want to take this list and pass it to a function to add caching support to those methods

Is there an effective way to accomplish this?

Follow up. The code below works, but because use strict doesn't allow references by string I'm not 100% where I want to be.

sub myfn {
  printf("computing\n");
  return 10;
}
sub cache_fn {
  my $fnref = shift;

  my $orig = $fnref;
  my $cacheval;

  return sub {
    if (defined($cacheval)) { return $cacheval; }
    $cacheval = &$orig();
    return $cacheval;
  }
}

*{myfn} = cache_fn(\&myfn);

How do I modify to just do this?:

cache_fn(&myfn);

You can overwrite methods like get_val from another package like this:

*{MyClass::get_val} = sub { return $some_cached_value };

If you have a list of method names, you could do something like this:

my @methods = qw/ foo bar get_val /;
foreach my $meth ( @methods ) {
    my $method_name = 'MyClass::' . $meth;
    no strict 'refs';
    *{$method_name} = sub { return $some_cached_value };
}

Is that what you imagine?

I write about several different things you might want to do in the "Dynamic Subroutines" chapter of Mastering Perl . Depending on what you are doing, you might want to wrap the subroutine, or redefine it, or subclass, or all sorts of other things.

Perl's a dynamic language, so there is a lot of black magic that you can do. Using it wisely is the trick.

I've never tried it with methods, but Memoize may be what you're looking for. But be sure to read the caveats .

Not useful in your case but had your class been written in Moose then you can dynamically override methods using its Class::MOP underpinnings....

{
    package MyClass;
    use Moose;

    has 'val' => ( is => 'rw' );

    sub get_val {
        my $self = shift;
        return $self->val + 10;
    }

}

my $A = MyClass->new( val => 100 );
say 'A: before: ', $A->get_val;

$A->meta->remove_method( 'get_val' );
$A->meta->add_method( 'get_val', sub { $_[0]->val } );

say 'A: after: ', $A->get_val;

my $B = MyClass->new( val => 100 );
say 'B: after: ', $B->get_val;

# gives u...
# => A: before: 110
# => A: after: 100
# => B: after: 100

How do I modify to just do this?:

cache_fn(\&myfn);

Well based on your current example you could do something like this....

sub cache_fn2 {
    my $fn_name = shift;
    no strict 'refs';
    no warnings 'redefine';

    my $cache_value = &{ $fn_name };
    *{ $fn_name } = sub { $cache_value };
}

cache_fn2( 'myfn' );

However looking at this example I can't help thinking that you could use Memoize instead?

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM