Beefy Boxes and Bandwidth Generously Provided by pair Networks
laziness, impatience, and hubris
 
PerlMonks  

Overridding subroutines called in private subroutines

by skazat (Chaplain)
on Oct 05, 2008 at 02:55 UTC ( [id://715405]=perlquestion: print w/replies, xml ) Need Help??

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

I'm currently scratching my head whilst using the MIME::Lite::HTML module. The module itself is kind of showing it's age.

I'm attempting to override the, "absUrl" method, which isn't really a method at all - but just a subroutine in the module itself. No problem, I thought - those can still be overridden, if I do this:

package MyMLH; use MIME::Lite::HTML; @ISA = "MIME::Lite::HTML"; sub absURL($,$){ return "PONIES!"; }

I can then do this,

use MyMLH; print MyMLH::absURL;

Which prints out, "PONIES!"; But, using the, public interface, my overridden absURL never takes effect. (No ponies for me)

The only way I can get it to do what I want to do is to have something like:

sub MIME::Lite::HTML::absURL($,$){

in my code - which I don't think is valid Perl code (works alright in all, though)

The only weirdness I can find in this module is that the absUrl subroutine is only called in MIME::Lite::HTML itself in private subroutines of the, "parse" method, like this:

sub parse () { my $self = shift; sub private_sub { # calls, "absUrl" in the module itself, # and not my overridden version # (I think!) return absUrl('this', 'that'); } private_sub(); }

which is bizarre.

I can successfully override both the, "parse" and, "absUrl" method/subroutines and have the thingy do what I want, but why can't just absUrl be overridden?

Is it something I'm missing about this subroutine not being in the @EXPORT or, @EXPORT_OK list? Or is it a weird thing about private subroutines in a public method, which cannot be overridden?

I was under the impression that you can't really privatize a Perl method's subroutine or method completely.

It has to be said that the subroutine, "absURL" isn't in the docs public or private methods - it's not in the docs at all. Can anyone think of a need to have private subroutines within a method?

And just to throw this out there, I know this module hasn't been updated in years and it's quirkiness keeps costing me hours. I'll throw up my hand to take over maintenance of the module (there are, perhaps better alternatives right now, but the dependency chain scares me) - how'd I go about doin' that? I'd like to simple clean up the externals of this module a bit, so people can successfully subclass the module to fix the niggling bugs that people like myself are hitting (and then, hopefully apply patches to fix up them for real)

Replies are listed 'Best First'.
Re: Overridding subroutines called in private subroutines
by ikegami (Patriarch) on Oct 05, 2008 at 05:13 UTC

    You can't so much override a function as replace it.

    use Module; # Execute module first so it doesn't replace you! BEGIN { package Module; no warnings 'override'; sub func { ... } }

    Alternate syntax:

    use Module; # Execute module first so it doesn't replace you! BEGIN { package Module; no warnings 'override'; *func = sub { ... }; }

    You can wrap the original function:

    use Module; # Execute module first so it doesn't replace you! BEGIN { package Module; no warnings 'override'; my $orig_func = \&func; sub func { ... $orig_func->(...) ... }; }

    You can even do so temporarily:

    use Module; # Execute module first so it doesn't replace you! { package Module; no warnings 'override'; my $orig_func = \&func; local *func = sub { ... $orig_func->(...) ... }; # Replaced from here. ... # Replaced until here. }

    In all cases, it's replaced for everyone. You could use caller to emulate other behaviours, perhaps.

    The only weirdness I can find in this module is that the absUrl subroutine is only called in MIME::Lite::HTML itself in private subroutines of the, "parse" method, like this:

    There's no such thing as a private subroutine. The inner sub is just as public as the outer one. Whoever wrote that code was fooling himself. And he's asking for trouble. The following warning is an understated indicator of the problem of nesting named subs:

    >perl -c -we"sub foo { my $x; sub bar { $x } }" Variable "$x" will not stay shared at -e line 1. -e syntax OK

    I don't know if I've answered your question since I really don't know what you're asking, but I hope I helped.

    Note: The BEGIN is not necessarily needed in any of the examples. Plain curlies would likely work too.

      No - that's really helpful - the idea that I can just work within the namespace of the original module in my other script/module, by doin',

      { package Module; # Yadda Yadda, }
      I'm guessing the braces localize what I'm doing, so once outside them, I can do whatever I want. Just out of curiousity, would this do the same thing:
      package Module; package main; # And now back to our main script...

        would this do the same thing:

        If you had been in main initially, yes.

        Speaking of similar things, I didn't answer an earlier question.

        sub Foo::Bar::f {}

        is very similar to

        { package Foo::Bar; sub f {} }

        But the code is compiled in a different package. This affects caller and other things:

        use strict; use warnings; sub Foo::f { our $var; print(__PACKAGE__, "\n"); print("$var\n"); } { package Bar; sub f { our $var; print(__PACKAGE__, "\n"); print("$var\n"); } } $main::var = 'main!'; $Foo::var = 'foo!'; $Bar::var = 'bar!'; Foo::f(); # main # main! Bar::f(); # Bar # bar!
        I'm guessing the braces localize what I'm doing, so once outside them, I can do whatever I want.

        Be careful. The braces ({}) don't localize everything you are doing. The create a scope for your variables, but your package and subroutine declarations remain global. I think the form you used above gives a misleading impression that the package is block scoped.

        Update 2: Thanks for the heaping helping of crow, ikegami--I needed it. I was wrong. Dead wrong. See package. I read this wrong information somewhere, failed to check it, and have promulgated bogus information as a result.

        { package Foo; # Foo stuff # goes in here. } sub FooFunc { # This function is Foo::FooFunc! #actually it's not! It is in main. I was wrong. } package main; sub MainFunc { # This function is main::MainFunc. }

        That's the reason why I like this approach better:

        package Foo; { # Foo stuff # goes in here. } package main; # back in main.

        Update: After looking at the module, I'd be inclined to use ikegami's suggestion for using a localized override.

        This will minimize the spooky action at a distance factor.

        #!/usr/bin/perl use strict; use warnings; package Foo; sub method { routine(); } sub routine { return __PACKAGE__; } package Foo::Bar; our @ISA = qw( Foo ); sub routine { return __PACKAGE__; } sub method { no warnings 'redefine'; # Temporarily override routine() in the parent class. local *Foo::routine = \&routine; # Delegate to parent method. my $self = shift; $self->SUPER::method(@_); } package main; print "Foo: ", Foo->method, "\n"; print "Foo::Bar: ", Foo::Bar->method, "\n"; print "Foo: ", Foo->method, "\n";


        TGI says moo

Re: Overridding subroutines called in private subroutines
by wol (Hermit) on Oct 06, 2008 at 14:24 UTC
    What! You'd like to volunteer to maintain this "quirky" module and then patch absURL to return "PONIES!" ?!?

    If you must, at least make it return "STALLIONS!" - much less effete.

    --
    .sig : File not found.

      Honestly?

      I'd like to at least make it so I can override "absUrl" without going through flaming hoops - we can have ponies and stallions for all I care. That would be first on my list.

      And since I can't now, perhaps even rename it, "_absUrl", so there's some sort of mnemonic for it being a private method and perhaps a bunch of other things I've learned from the PBP book.

      So, yes, I'd like to take over the maintenance of this module, since it does something very useful in my app, it's not being updated, the maintainer isn't answering my emails (anymore - years ago: yes) and it's probably exactly at my level of hacking experience.

      And maybe one of these days, I'd like to completely rewrite it, and give it a proper testing suite, etc, etc, etc.

      Who's the guy that I have to tap on the shoulder to get the blessing to do this?

      A reply falls below the community's threshold of quality. You may see it by logging in.
Re: Overridding subroutines called in private subroutines
by blazar (Canon) on Oct 21, 2008 at 10:13 UTC
    I'm attempting to override the, "absUrl" method, which isn't really a method at all - but just a subroutine in the module itself.

    (First of all, apologies for replying so late!)

    So, why are you saying it's a method at all? (If it's not called like a method, then it's not a method...) Apart that in Perl 5 methods and subroutines are exactly the same thing, but for how they are called...

    The only weirdness I can find in this module is that the absUrl subroutine is only called in MIME::Lite::HTML itself in private subroutines of the, "parse" method, like this:

    Nope: in Perl 5 there's no notion of private subs to other methods/subs. Of course there's nothing strictly wrong declaring and defining it there, but it would be just as if it where declared outside of the body of the surrounding sub. Except for having access to some lexical variables of the latter: but remember that it won't close over them! To do so, you can use an anonymous sub and assign it to a lexical, which gets as close as possible to the concept of a "private sub" IMO.

    The only way I can get it to do what I want to do is to have something like:

    sub MIME::Lite::HTML::absURL($,$){

    in my code - which I don't think is valid Perl code (works alright in all, though)

    I'm not sure if I understand what you mean. If it compiles, then it's valid Perl. However, since nobody has pointed it out thus far, the comma is not a valid character in prototypes and if you use warnings perl will duly warn you:

    C:\temp>perl -ce "sub a ($,$) {}" -e syntax OK C:\temp>perl -wce "sub a ($,$) {}" Illegal character in prototype for main::a : $,$ at -e line 1. -e syntax OK

    Without warnings, I presume that the comma is silently ignored. I was trying to use B::Concise to check whether that's actually the case, but I don't really know how it works, and appearently it ignores prototypes at all, nor does it even mention it in the docs, as a quick check revealed. All I can do is to naively run the following tests:

    C:\temp>perl -wMO=Concise,a -e "sub a ($,$) {}" Illegal character in prototype for main::a : $,$ at -e line 1. main::a: 2 <1> leavesub[1 ref] K/REFC,1 ->(end) 1 <;> nextstate(main 2 -e:1) P:{ ->2 -e syntax OK C:\temp>perl -wMO=Concise,a -e "sub a ($$) {}" main::a: 2 <1> leavesub[1 ref] K/REFC,1 ->(end) 1 <;> nextstate(main 2 -e:1) P:{ ->2 -e syntax OK C:\temp>perl -wMO=Concise,a -e "sub a {}" main::a: 2 <1> leavesub[1 ref] K/REFC,1 ->(end) 1 <;> nextstate(main 2 -e:1) P:{ ->2 -e syntax OK
    --
    If you can't understand the incipit, then please check the IPB Campaign.

Log In?
Username:
Password:

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

How do I use this?Last hourOther CB clients
Other Users?
Others examining the Monastery: (7)
As of 2024-04-19 08:37 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found