http://www.perlmonks.org?node_id=92430

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

I just wrote a wrappers module that will allow arbitrary wrapping of any (named) subroutine. It manipulates the symbol table to do so. The re-definition of subroutines is happening fine. The only problem I'm having is deleting them in the presence of inheritance. Here's a simple test program that shows what's happening:

#!/usr/bin/perl -w use strict; package A; sub A { print "A::A $_[0]\n" } package B; @B::ISA = 'A'; $B::A = "i'm \$B::A\n\n"; package main; A->A; B->A; # calls A::A print $B::A; *{B::A} = sub { print "B::A $_[0]\n" }; # works fine A->A; B->A; # now calls B::A print $B::A; undef &{B::A}; A->A; # Why doesn't it just call A::A here because of inheritance? B->A; # Not a CODE reference at noinherit.pl line 27. print $B::A;

Now, I'm certain that I'm just missing something elementary, but could someone point me in the right direction? I only want to delete my definition of &B::A so it'll go back to inheriting from &A::A. And I don't want to clobber $B::A in the process.

Replies are listed 'Best First'.
Re: Undefining subroutines and inheritance
by chromatic (Archbishop) on Jun 29, 2001 at 00:09 UTC
    I did something similar in Devel::TraceMethods (hey, I invented a wheel!). The only approach that worked for me was to copy all the other typeglob slots to a temporary array, undefine the destination typeglob altogether, then assign a subroutine reference and the other references. In this case, $src is a reference to the symbol table containing the subroutine to wrap, and $symbol is the name of the subroutine in that table:
    # save all other slots of the typeglob foreach my $slot (qw( SCALAR ARRAY HASH IO FORMAT )) { my $elem = *{ $src->{$symbol} }{$slot}; next unless defined $elem; push @slots, $elem; } # clear out the source glob # undef &{ *{$src->{$symbol}} } didn't work undef $src->{$symbol}; # replace the sub in the source $src->{$symbol} = sub { logCall->($symbol, @_); return $sub->(@_); }; # replace the other slot elements foreach my $elem (@slots) { $src->{$symbol} = $elem; } }
Re: Undefining subroutines and inheritance
by japhy (Canon) on Jun 29, 2001 at 00:25 UTC
    I discussed this with several people at YAPC last year -- I think I even talked to Larry about it. You can't localize a function, and undefing it leaves a broken stub. There were several approaches I could think of:
    • Create a fake class
      my $x = SubClass->new; # inherits from SuperClass $x->method; # calls SubClass::method { local @FAKE::ISA = @{ ref($x) . "::ISA" }; $x->FAKE::method; # calls SuperClass::method } $x->method; # calls SubClass::method
    • localize the glob, and restore all but the code
      my $x = SubClass->new; # inherits from SuperClass $x->method; # calls SubClass::method { # save the glob... local *old = *{ ref($x) . "::method" }; # localize the glob... local *{ ref($x) . "::method" }; # and restore everything else ${ ref($x) . "::method" } = $old; @{ ref($x) . "::method" } = @old; %{ ref($x) . "::method" } = %old; # technically incomplete $x->method; # calls SuperClass::method } $x->method; # calls SubClass::method
    • icky eval + SUPER:: hack -- the SUPER:: doesn't work if you're not physically in the package!
      my $x = SubClass->new; # inherits from SuperClass $x->method; # calls SubClass::method { my $pkg = ref $x; eval qq{ package $pkg; \$x->SUPER::method; # calls SuperClass::method }; } $x->method; # calls SubClass::method
      These are gross. That's why I want to be able to say:
      local &foo;


      japhy -- Perl and Regex Hacker
Re: Undefining subroutines and inheritance
by bbfu (Curate) on Jun 28, 2001 at 23:52 UTC

    Maybe you have to delete the symbol-table hash key?

    # UNTESTED! # Instead of undef &{B::A} delete $B::A::{CODE};

    Update: Oh well. :-(

    Update again! Ah-ha! This seems to work fine, even preserving $B::A (though I wouldn't think it would...):

    # Instead of undef &{B::A} delete $B::{A};

    Can anyone explain why that doesn't seem to destroy $B::A? It seems dangerous and prone to breakage, so you should probably use chromatic's suggestion below.

    bbfu
    Seasons don't fear The Reaper.
    Nor do the wind, the sun, and the rain.
    We can be like they are.

      Can anyone explain why that doesn't seem to destroy $B::A?

      Ah, but it seems to! Try replacing the bottom of the program with:

      A->A; # Why doesn't it just call A::A here because of inheritance? B->A; # Not a CODE reference at noinherit.pl line 27. eval "print \$B::A"; # Use of uninitialized value in print at (eval 2) + line 1.

        Hrm. I guess normal references to variables are bound at compile-time. Ah, well. Back to chromatic's method. *shrug*

        bbfu
        Seasons don't fear The Reaper.
        Nor do the wind, the sun, and the rain.
        We can be like they are.

      Nope. I just tried it and it doesn't work. Nice thought, though...
Re: Undefining subroutines and inheritance
by bikeNomad (Priest) on Jun 29, 2001 at 01:02 UTC
    OK, chromatic's suggestion worked fine:

    #!/usr/bin/perl -w use strict; # Undefine a subroutine safely, by name. # From a method described by chromatic. sub undefSub { no strict 'refs'; my $name = shift; # save the glob... local *old = *{$name}; # and restore everything else local *new; foreach my $type (qw(HASH IO FORMAT SCALAR ARRAY)) { my $elem = *old{$type}; next if !defined($elem); *new = $elem; } *{$name} = *new; } package A; sub A { print "A::A $_[0]\n" } package B; @B::ISA = 'A'; $B::A = "i'm \$B::A\n\n"; package main; A->A; B->A; # calls A::A print $B::A; *{B::A} = sub { print "B::A $_[0]\n" }; A->A; B->A; # now calls B::A eval 'print $B::A'; undefSub('B::A'); A->A; B->A; # now calls A::A eval 'print $B::A'; # still intact!
(tye)Re: Undefining subroutines and inheritance
by tye (Sage) on Jun 29, 2001 at 01:07 UTC

    There is caching of methods going on so it might help to do @B::ISA= @B::ISA; or something similar to flush the cache.

            - tye (but my friends call me "Tye")
Re: Undefining subroutines and inheritance
by dragonchild (Archbishop) on Jun 28, 2001 at 23:52 UTC
    I'm not sure what the solution is, but I discovered that if you redefine &B::A to be sub { $_[0]->SUPER::A }, you get the error "Can't locate object method "A" via package "main" at noinherit.pl line 24." This is despite the fact that @B::ISA is still ('A').

    I dunno if this helps.