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

Oh wise monks,

So, I've inherited some code, and I've been assigned the task of enhancing it in various ways, but I am constrained along various dimensions. In particular, I cannot change the class/package structure, nor how packages and objects are related to one another. And I'm stuck trying to understand what's happening here...

The code here is a minimal example of what I have. The method I'm trying to understand is Q::bar.

package P; sub new { my ($proto) = @_; my $self = {}; bless $self => $proto; } sub foo { print "P: foo\n"; } sub bar { print "P: bar\n"; } package Pprime; @Pprime::ISA = qw(P); sub new { my ($proto) = @_; my $self = {}; bless $self => ref($proto) || $proto; } package Q; sub new { my ($proto) = @_; my $self = {}; bless $self => $proto; } sub bar { my $self = shift; print "Q: bar\n"; $self->SUPER::bar(); } sub test { my $self = shift; $self->foo(); $self->bar(); } package Qprime; @Qprime::ISA = qw(Q Pprime); sub new { my ($proto) = @_; my $self = {}; bless $self => ref($proto) || $proto; } package main; my $obj = Qprime->new(); $obj->test();

Running this script yields the following output:

P: foo Q: bar Can't locate object method "bar" via package "Q" (perhaps you forgot t +o load "Q"?) at ./demo.pl line 40.

Now as it turns out, I can get around the problem because I'm able to rename Q::bar, at which point the calls to P::foo and P::bar act the same. But I just don't understand why the SUPER doesn't work.

I appreciate any wisdom you can shed on the issue.
--roundboy

Replies are listed 'Best First'.
Re: Weird inheritance and SUPER
by pfaut (Priest) on Jan 22, 2003 at 00:59 UTC

    You must be missing something here because Q::bar() attempts to call a superclass method's bar() but Q does not have any superclass. When you take Q::bar() out of the picture, perl goes to the next superclass of QPrime which is Pprime and follows its superclasses and finds P::bar() which behaves properly. If you declare a superclass for Q that contains a bar() method or remove the $self->SUPER::bar() from the existing Q::bar(), you should be OK.

    --- print map { my ($m)=1<<hex($_)&11?' ':''; $m.=substr('AHJPacehklnorstu',hex($_),1) } split //,'2fde0abe76c36c914586c';

      Thanks, yes, the change you suggest do eliminate the error. But:

      • I specifically cannot add a superclass, because I'm not allowed to change the class hierarchy; and
      • Calling P::bar is really the goal of the exercise, because every instance of Q ever created will actually come from a subclass that also inherits from P.

      My problem, upon reflection, was that in trying to understand this, I read the following in my trusty Camel:

      SUPER...follows @ISA just as the regular inheritance mechanism does: in left-to-right, recursive, depth-first order. (3e, p. 325)

      However, I'd failed to read notice the following:

      A SUPER method consults the @ISA only of the package into which the call to SUPER was compiled. It does not care about the class of the invocant, nor about the package of the subroutine that was called. (ibid)

      I guess I expected that SUPER would follow the invocant's class hierarchy.

      As I mentioned in my original post, I solved the problem by renaming Q::bar, but now I'm curious. Is there a way to get the behavior I was looking for, without either changing method names or the class hierarchy? I imagine this would be some magic to replace the SUPER

      Thanks.
      --roundboy

        Is there a way to get the behavior I was looking for, without either changing method names or the class hierarchy?

        No.

        The method call, Qprime::bar has been resolved already. Now another method call, Q::SUPER::bar, needs to be resolved.

        You are using the wrong tool. If you got what you say you want you would have a loop repeatedly calling Q::bar.

        Usually SUPER is used to add functionality to a method:

        sub Child::foo { SUPER::foo; # do parent functionality blah; # add child functionality }
        Changing the order of the @Qprime::ISA elements might be a possible solution.

        If the hierarchy is carved in stone, i.e. you can't even change an @ISA, then you might make explicit calls: $self->P::bar.

        Maybe Q needs to also inherit from P? You can't make assumptions about peer classes. If Q is related to P, then this should be stated in Q's @ISA. If this is not satisfactory, then maybe Q needs to delegate to P in which case the relationship is by composition but this would require some changes to your class hierarchy.

        --- print map { my ($m)=1<<hex($_)&11?' ':''; $m.=substr('AHJPacehklnorstu',hex($_),1) } split //,'2fde0abe76c36c914586c';

        So you want:

        P: foo Q: bar P: bar
        right?

        Look into the NEXT module. It does exactly what you need: it follows @ISA along all the run-time class inheritance tree

        -- 
                dakkar - Mobilis in mobile