Beefy Boxes and Bandwidth Generously Provided by pair Networks
There's more than one way to do things
 
PerlMonks  

Advanced subroutine and symbol table manipultation

by Tuppence (Pilgrim)
on Jul 16, 2004 at 20:47 UTC ( [id://375131]=perlquestion: print w/replies, xml ) Need Help??

Tuppence has asked for the wisdom of the Perl Monks concerning the following question:

I want to 'push' a subroutine into a class. By this I mean if a class has a 'foo' method, I want to grab a reference to the old subroutine, and replace it with a subroutine that Does Other Stuff and then calls the old one.

This is made slightly trickier in that I want to do it in a way that I can use it across multiple modules, as long as the routine I want to add is the same.

So, for instance
package Parenthise; sub parenthise { my($class, $field_name) = @_; my $fullname = join ('::', $class, $field_name); my $coderef; my $set_coderef = "\$coderef = $fullname;"; eval $set_coderef; my $replace_sub = sub { my ($self, $value) = @_; if(@_ > 1){ return $coderef->($self); }else{ return $coderef->($self, "($value)"); } }; # here's where the trouble starts my $switcheroo = sprintf(<<"HERE" , $class, $field_name, $ +field_name); package %s; undef &{ *%s{CODE} }; *%s = \$coderef; HERE eval $switcheroo; } 1;
package SomePackage; use Class::MethodMaker get_set => [qw/value/], new_with_init => 'new'; use Parenthise; Parenthise::parenthise(__PACKAGE__, 'value'); 1;
use Somepackage; my $thing = Somepackage->new(); $thing->value('bar'); $thing->value eq '(bar)';

Note that this isn't exactly what my code is, my actual code is a bit more complicated by the fact that there are multiple fields and the function is actually figuring out which fields to change automatically.

I haven't tested the code posted in this comment, as all the interesting bits are copy and paste from my code.

I am trying to get this to work under strict, which is why the undef in the switcheroo eval.

Thanks tons for any and all help in advance, I've been banging my head on this for quite a while trying various things.

20040720 Edit by ysth: break up (accidental?) extremely long code line

Replies are listed 'Best First'.
Re: Advanced subroutine and symbol table manipultation
by Zaxo (Archbishop) on Jul 16, 2004 at 20:59 UTC

    Take a look at Hook::LexWrap. It does just that, in a couple of different ways. The temperature conversion example in the docs is wrong, you need to work with the aliases in @_, not @_ itself.

    After Compline,
    Zaxo

      Thanks, I should have figured there would be a Damien module for this ;)
Re: Advanced subroutine and symbol table manipultation
by blokhead (Monsignor) on Jul 16, 2004 at 21:13 UTC
    Zaxo's answer is probably the best for just using a pre-call hook. But if you need a little more control over calling the old sub, you may need to do it yourself.

    I don't see the problem with strict. Just turn off strict refs locally and use symbolic references, it's a lot easier than messing with eval. Part of strict is knowing when you should turn it off. Messing with the symbol table is one of those times. Just make sure you don't turn off strict refs inside that closure that becomes the new sub, though.

    package Foo; sub bar { print "Original Foo::bar\n"; } package main; sub switcheroo { my ($pkg, $func) = @_; my $old_ref = do { no strict 'refs'; \&{"$pkg\::$func"}; }; my $new_ref = sub { print "New $func\n"; $old_ref->(); }; { no strict 'refs'; *{"$pkg\::$func"} = $new_ref; } } Foo::bar(); switcheroo("Foo", "bar"); Foo::bar();
    You may also want to use goto &$old_ref in case the original sub wanted to look at caller.

    blokhead

      Wow, that's great too. I added a 'use strict' to the top of it and it still works great.

      I think I was getting confused because I was under the impression that you must undef the subroutine before you can redefine it, and I was having trouble getting undef $var to not undef $var itself but the function name stored in $var.

      Thanks!
Re: Advanced subroutine and symbol table manipultation
by mvc (Scribe) on Jul 17, 2004 at 00:26 UTC

    This is a task for the Aspect module.

    after { $_[0]->return_value( '('. $_[0]->return_value. ')' ) } call 'SomePackage::value';

    Sure you could do this with Hook::LexWrap, in fact that is how Aspect does it. But this is easier. The main difference is that you can select entire groups of methods, using a pointcut language.

    Say you wanted to parenthesize not one method, but all MyPackage methods, except the constructor:

    after { $_[0]->return_value( '('. $_[0]->return_value. ')' ) } call qr/^SomePackage/ & !call qr/^SomePackage::new/;

    Unfortunately you cannot match by attributes or inheritance yet.

    And if you want to create a reusable aspect (like the Wormhole):

    # creating an aspect package Aspect::Library::Parenthesize; use Aspect; use base 'Aspect::Modular'; sub get_advice { after { $_[0]->return_value( '('. $_[0]->return_value. ')' ) } pop; } # using the new aspect package main; use Aspect; use SomePackage; aspect Parenthesize => call 'SomePackage::value' & !cflow 'HatesParentheses::run' & cflow 'LikesParentheses::run';

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others making s'mores by the fire in the courtyard of the Monastery: (7)
As of 2024-04-18 22:09 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found