Beefy Boxes and Bandwidth Generously Provided by pair Networks
"be consistent"

Stealing lexicals - best practice suggestions

by diotalevi (Canon)
on Apr 17, 2003 at 04:45 UTC ( #251127=perlquestion: print w/replies, xml ) Need Help??

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

I'm looking to extend japhy's current YAPE::Regex and need to alter a lexical named %pat. I thought of several different ways of doing this but I wanted to present each and get your input and opinions on the merits of each.

The following code examples show a method that works, others that don't. All advice is welcome.

#1 Alter YAPE::Regex so it is extension friendly
This would store %pat in the YAPE::Regex object which directly exposes it to modification. This is the best solution but it requires japhy's cooperation if I want to avoid forking the source. (and I'd need to modify a method but that's unrelated to this post).

#2 Use PadWalker
This doesn't work because PadWalker doesn't actually dig out lexicals taken in closures and only really exposes lexicals created in the subroutine. Darn!

use YAPE::Regex; my $p = YAPE::Regex->new(qr//); $p->YAPE::Regex::Extensible::steal_pat; package YAPE::Regex::Extensible; use base 'YAPE::Regex'; use PadWalker 'peek_sub'; use Data::Dumper; sub steal_pat { my $self = shift; $self->{'pat'} = peek_sub(\&YAPE::Regex::next)->{'%pat'}; }

#3 Grab it via Devel::LexAlias
Oops! This doesn't work either. In fact while PadWalker at least reported the existance of %pat, Devel::LexAlias doesn't even do that.

use YAPE::Regex; my $p = YAPE::Regex->new(qr//); $p->YAPE::Regex::Extensible::steal_pat; package YAPE::Regex::Extensible; use base 'YAPE::Regex'; use Devel::LexAlias 'lexalias'; sub steal_pat { my $self = shift; $self->{'pat'} = {}; lexalias(\&YAPE::Regex::next, '%pat', $self->{'pat'}); }

#4 Be really evil and muck around with B and Devel::Pointer
Now this works but its exceedingly ugly and I can't help but think that there has got to be a better way than manually trolling around a pad for pointers.

use YAPE::Regex; my $p = YAPE::Regex->new(qr//); $p->YAPE::Regex::Extensible::steal_pat; package YAPE::Regex::Extensible; use base 'YAPE::Regex'; use B qw(svref_2object); use Devel::Pointer; sub steal_pat { my $self = shift; $self->{'pat'} = {}; my $sub = \&YAPE::Regex::next; my $osub = svref_2object( $sub ); # Unpack next()'s pad list into an array of names and values. my ($names, $values) = map [ $_->ARRAY ], $osub->PADLIST->ARRAY; # Fetch the first HV associated with the name '%pat' my $h; for my $i (0 .. $#$names) { if ( $names->[$i]->can('PV') and $names->[$i]->PV eq '%pat') { $h = $values->[$i]; last; } } # $$h is the address of the hash # Use Devel::Pointer to construct a reference to it. $self->{'pat'} = unsmash_hv( $$h ); }

Replies are listed 'Best First'.
Re: Stealing lexicals - best practice suggestions
by PodMaster (Abbot) on Apr 17, 2003 at 06:03 UTC

    Contact japhy and kindly ask him to update YAPE::Regex so it is extension friendly (possibly providing a proposed patch), and then use YAPE::Regex 44; in your subclass, to make sure users havea a good enough version of YAPE::Regex installed (presumably the extension friendly YAPE::Regex).

    If japhy doesn't agree, then simply fork the code like Zaxo suggests.

    MJD says you can't just make shit up and expect the computer to know what you mean, retardo!
    I run a Win32 PPM repository for perl 5.6x+5.8x. I take requests.
    ** The Third rule of perl club is a statement of fact: pod is sexy.

Re: Stealing lexicals - best practice suggestions
by Zaxo (Archbishop) on Apr 17, 2003 at 05:43 UTC

    I'd go with #1.

    That will allow easier testing - all you need to do is add some tests of your own to the existing ones. It's also cleaner than breaking YAPE::Regex by stealthy mucking in its internals.

    If japhy doesn't like your code or your intention, then you can maintain your modified version in a seperate namespace.

    After Compline,

Re: Stealing lexicals - best practice suggestions
by Abigail-II (Bishop) on Apr 17, 2003 at 08:41 UTC
    I would first contact the author of the module and see if you can work something out. Otherwise, fork. Beside that some of the other mentioned points don't work, you'd break all rules of software engineering. Your extension code is now dependent on the implementation details of the original code, and it's not using the API. If in a next version of YAPE::Regex, %pat is renamed, or worse, due to reimplementation of the internals, there's now a different datastructure, your extension code no longer works.


Re: Stealing lexicals - best practice suggestions
by shotgunefx (Parson) on Apr 17, 2003 at 05:41 UTC
    #3 Grab it via Devel::LexAlias
    Funny you should mention that. I was looking at japhy's Lexical::Alias the last couple of days (it's based on Devel::LexAlias) and noticed the same closure issue and suggested he mention it in the POD.

    Are you just trying to get a reference to the %pat lexical?


    "To be civilized is to deny one's nature."
Re: Stealing lexicals - best practice suggestions
by diotalevi (Canon) on Apr 17, 2003 at 14:44 UTC

    One evening's sleep and I'll agree that I'm likely to break stuff this way especially since all the module's subroutines would have to be recompiled to use the data in the object instead of from the lexicals. Yick.

    I was musing this morning of breakfast that I have a bad habit of jumping off of more bridges than I have to - use of one work around doesn't justify the use of yet another. Somewhere along the way after I've convinced myself that whatever thing I had to do to make something else work (that was necessary) I somehow get the idea that "just a bit more" won't hurt. And eventually I end up with icky things like this:

    BEGIN{die "No. You should not be using this code.";} package YAPE::Regex::EvilPatch; use YAPE::Regex (); use B qw(svref_2object); use Devel::Pointer qw(unsmash_sv unsmash_hv); use B::Deparse (); sub patch { return if $_[0]->{'pat'} or %YAPE::Regex::pat; my $var; # Copy the lexicals into globals. Also throw away the # compiled versions of all of the regular expressions. $var = fetch_pad_var( \ &YAPE::Regex::_ok_class, '$valid_POSIX' ); $YAPE::Regex::valid_POSIX = ''.unsmash_sv( $$var ); $var = fetch_pad_var( \ &YAPE::Regex::_ok_class, '$ok_cc_REx' ); $YAPE::Regex::ok_cc_REx = ''.unsmash_sv( $$var ); $var = fetch_pad_var( \&YAPE::Regex::next, '%pat' ); %YAPE::Regex::pat = map "$_", %{unsmash_hv( $$var )}; my $deparse = B::Deparse->new; # Patch the ->new method so it copies %YAPE::Regex::pat my $new_m = $deparse->coderef2text( \ &YAPE::Regex::new ); $new_m =~ s/(?<=bless\({)/'PAT', {%pat},/ or die "Couldn't add PAT to new"; $new_m = "sub $new_m;"; *YAPE::Regex::new = eval $new_m; # Alter ->next so it fetches from $self->{'PAT'} my $next = $deparse->coderef2text(\&YAPE::Regex::next); $next =~ s/(?<=\$)pat(?=\{)/self->{'PAT'}/g; *YAPE::Regex::next = eval "sub $next;"; # Same thin for ->_get_quant my $quant = $deparse->coderef2text(\ &YAPE::Regex::_get_quant); $quant =~ s/(?<=\$)pat(?=\{)/self->{'PAT'}/g; *YAPE::Regex::_get_quant = eval "sub $quant"; # _ok_class uses all three lexicals. Same deal. } sub fetch_pad_var { my $cv = shift; my $var = shift; my $ocv = svref_2object( $cv ); my ($names, $values) = map [ $_->ARRAY ], $ocv->PADLIST->ARRAY; my $h; for my $i (0 .. $#$names) { if ( $names->[$i]->can('PV') and $names->[$i]->PV eq $var ) { return $values->[$i]; } } return (); }
by Anonymous Monk on Apr 17, 2003 at 11:03 UTC
    Didn't your mother tell you that stealing is bad? :-P

Log In?

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

How do I use this? | Other CB clients
Other Users?
Others having an uproarious good time at the Monastery: (5)
As of 2022-05-24 09:45 GMT
Find Nodes?
    Voting Booth?
    Do you prefer to work remotely?

    Results (82 votes). Check out past polls.