A friend of mine is using a module from a commercial software package in a project. The package, let's call it Foo::Bar, doesn't do exactly what he wants. He wanted to override two subroutines, to change the way their return values are structured. I suggested writing a new module, inherit from the existing one, and override the offending subs. I advised him along these lines:
package Foo::BarMod; use Foo::Bar; @ISA = qw(Foo::Bar); sub stuff { my $self = shift; # Do stuff, overriding Foo::Bar::stuff } # Everything else, including new(), from Foo::Bar 1;

When he tried it out, he was very happy to see that it worked exactly as he had hoped, including stuff().

A few days later, he emailed me saying that one of his scripts wouldn't work, giving him the error:

Undefined subroutine Foo::BarMod::whiz at line 17.

He was quite suprised, because whiz was no different than anything else, he said. (Foo::Bar::whiz exists). Nothing special is being done to it in Foo::BarMod. Thus, it was suprising that it should be the only one to fail: it should be inherited like any other subroutine, right?

Thinking that perhaps something screwy was going on with the object creation (inheriting a constructor is a bit risky, IMHO - call it from your own overidden one, I say) so I enquired about the object ref he was using it on (thinking, perhaps it was mis-blessed?). This is when he mentions that it is a class method, and he didn't have (nor want) an object to use it on.

I asked him more about the method whiz(). It turned out that whiz is a class method - in other words, it does not deal with any specific Foo::Bar, but provides some service of (or to) Foo::Bar 's collectively (or maybe does nothing of the sort, of course:). But, I reasoned, Foo::BarMod ISA Foo::Bar, so any subroutine not found would be looked for in the Foo::Bar namespace, right (AUTOLOADing aside)? Thus this should work:

# call class method whiz() Foo::BarMod::whiz();

Of course, that's exactly how he was getting the error!

While our elder monks probably spotted the problem right away, we mere initiates scrambled for Programming Perl, 2nd Ed., where we found (pg 292) a discussion about how Perl uses @ISA to look for missing methods.

methods, not subroutines. Hmm. So what is the difference between a method and a subroutine? Again, the Camel: two things:

Hmm. Well, it's a class method, so the first one doesn't really apply. Maybe - just maybe - Perl decides whether to consider it a "subroutine" or a "method" (and thus whether to check @ISA) by how you call it - ie, using either the arrow or the "indirect object" syntax. volia! These both work:

Foo::BarMod->whiz(); whiz Foo::BarMod;

but this

Foo:BarMod::whiz;

is just a (fully qualified) subroutine call (and thus, ISA isn't checked, and the sub isn't found.)

So, that's how Perl knows when to check ISA. Meditate upon this, and thus how the ISA mechanism allows us the full benefits of inheritance with object-oriented (ie, method) calls, while leaving alone non-OO (subroutine) calls.

Replies are listed 'Best First'.
Re: @ISA, Inheritance, and Class Methods
by Adam (Vicar) on Mar 13, 2001 at 10:41 UTC
    I must admit that I too have fallen in the @ISA trap. But I wanted to look at your solution to extending an existing module. Not that long ago I was looking at using an OO module (I honestly don't remember which one), but this module didn't have a destructor. Instead, you were required to call some kind of clean up method, lets call it Class::Finish(). Well, I'm not a big fan of making the programmer call something that Perl will happily do for you (if you call the method DESTROY) and so I thought about how to extend the class to add:
    sub DESTROY { my $self = shift; $self->Finish(); }
    And the first idea that came to me was your idea of writing a wrapper object. But I didn't like the idea too much. The next idea (and I think this was someone else's idea) was to do it like this:
    { package Class; sub DESTROY { my $self = shift; $self->Finish(); } }
    To replace an already existing method you would only need to add no strict and (5.6+) no warnings within the block. As it turns out, and the more advanced have already made this jump, you can just declare:
    sub Class::DESTROY { my $self = shift; $self->Finish(); }
    Aint it grand? :-)
      My solution would be as follows:
      *Class::Destroy = \&Class::Finish;
      which also has the benefit of being a tad faster.
Re: @ISA, Inheritance, and Class Methods
by btrott (Parson) on Mar 13, 2001 at 10:35 UTC
    You say:
    > Undefined subroutine Foo::BarMod::whiz at line 17. > ... > Well, it's a class method, so the first one doesn't really > apply.
    No it's not. That's not a class method--that's a fully-qualified subroutine 'whiz' in the class 'Foo::BarMod'. Very much not a class method. You're saying, "call the function 'whiz' in the package 'Foo::BarMod'". But such a function doesn't exist, so Perl doesn't like that.

    If you want inheritance, call it as a class method, which is calling it like this:

    Foo::BarMod->whiz
    (Or indirect object syntax, but that's generally not used much.)

    In other words: you came to the right conclusion, but you may have spared yourself quite a bit of confusion and frustration if you had your terms right. Inheritance--ie., checking in @ISA--only occurs for class methods (and object methods). Not for fully-qualified subroutines.

Re: @ISA, Inheritance, and Class Methods
by japhy (Canon) on Mar 13, 2001 at 20:43 UTC
    Nice. I wrote an "oops" hack to get around this:
    use Carp; use vars '$AUTOLOAD'; # in case strict is on AUTOLOAD { my ($pkg, $func) = $AUTOLOAD =~ /(.*)::(.*)/; my $cref = $pkg->can($func) or carp "no such method $func in $pkg"; unshift @_, $pkg; goto &$cref; }
    This turns a Foo::BarMod::whiz into a Foo::BarMod->whiz -- specifically, it turns it into Wherever::whiz(Foo::BarMod, ...).

    japhy -- Perl and Regex Hacker