views:

183

answers:

6

I'm trying to monkey patch a Perl class: I want to change the behavior of an existing method.

This node on perlmonks shows how to add a function to an existing class. I found that this pattern can also be used to provide a new implementation for an existing function.

However, I'd like to know how to call the original function.

I'm looking for something like this:

use ExistingClass;

# TODO: Somehow rename existingFunction() to oldExistingFunction().

sub ExistingClass::existingFunction {
    my $self = shift;

    # New behavior goes here.
    $self->oldExistingFunction(@_); # Call old behavior.
    # More new behavior here.
}
+8  A: 

Typeglob assignment

*ExistingClass::oldExistingFunction = *ExistingClass::existingFunction;

Quick and dirty. This aliases all existingFunction symbols to oldExistingFunction. This includes the sub you're interested in, but also any scalars, arrays, hashes, handles that might happen to have the same name.

  • Advantages: no thinking, it just works. "quick"
  • Disadvantages: "dirty"

Coderef assignment

*ExistingClass::oldExistingFunction = \&ExistingClass::existingFunction;
# or something using *ExistingClass::symbol{CODE}

That one only aliases the sub. It's still done in the package stash, so the oldExistingFunction symbol is globally visible, which might or might not be what you want. Probably not.

  • Advantages: that aliasing doesn't 'leak' to other variable types.
  • Disadvantages: more thinking, more typing. A lot more thinking if going for the *...{CODE} syntax (I personnally don't use it every day)

Lexical coderef

my $oldFunction = \&ExistingClass::existingFunction;

Using my keeps a reference to the old function that is only visible to the currrent block/file. There is no way for external code to get hold of it without your help anymore. Mind the calling convention:

$self->$oldFunction(@args);
$oldFunction->($self, @args);
  • Advantages: no visibility issues anymore
  • Disadvantages: harder to get right

Moose

See jrockway's answer. It's got to be The Right Way, since there's no mucking around with globs and/or references anymore, but I don't know it enough to explain it.

JB
Also note it aliases variables with the same name (scalars, arrays...) as well. I figured it wouldn't be a problem in your case, but it can be made more strict if needed.
JB
Also note that if you didn't want to expose the old function to users of your class, you can store the whole thing in a scalar.
Robert P
To make it more strict, you'd do *... = \ instead of = *...;
ysth
... or *class::method{CODE}
Brad Gilbert
I just integrated what I could from your comments. If someone wants to detail (edit or comment) the *...{CODE} one a bit, please do! I don't have enough time to dig the subject. (plus, I don't like magic words :)
JB
If all you want from Moose is overriding a method, you're using too much technology. Moose is fine if you are using it for everything else, but a dumb choice if you are only handpicking from its features features.
brian d foy
+2  A: 

Memoize is a good example of this.

ysth
A: 

As an alterative, what's wrong with:

package NewClass;
use base qw/ExistingClass/;

sub existingFunction {
# ....
}

sub oldExistingFunction {
    my $self = shift;
    return $self->SUPER::existingFunction(@_);
}
Penfold
This won't work if some code that you don't maintain calls ExistingClass->existingFunction(); It would still call the original method and not the modified one. You could monkey patch that too, but it might end up being a mess of monkey patches.
gpojd
+2  A: 

Just copy it to a lexical variable and call it.

my $existing_function_ref = \&ExistingClass::existingFunction;
*ExistingClass::existingFunction = sub { 
    my $self = shift;
    $self->go_and_do_some_stuff();
    my @returns = $existing_function_ref->( $self, @_ );
    $self->do_some_stuff_with_returns( @returns );
    return wantarray ? @returns : shift @returns;
};

If you would feel better about it with OO-syntax, you could create a UNIVERSAL::apply method (or at any base class you chose).

sub UNIVERSAL::apply { 
    my ( $self, $block ) = splice( @_, 0, 2 );
    unshift @_, $self;
    goto &$block;
}

That way you can call it like this:

my @returns = $self->apply( $existing_function_ref, @_ );
Axeman
+8  A: 

You should use Moose or Class::Method::Modifiers.

In that case, you can just say:

around 'some_method' => sub {
    my ($orig, $self, @args) = @_;
    # ... before original ...
    $self->$orig(@_);
    # ... after original ...
};
jrockway
+3  A: 

Besides the other answers, look at modules such as:

I also talk about this in the "Dynamic Languages" chapter in Mastering Perl.

brian d foy