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

Here is my scenario. I have a package that imports methods into another package. Those methods being imported need to be able to call the SUPER class of the package they're being imported into. For example...

I have a simple derived class:
package DerivedClass; use strict; use base qw(BaseClass); use ImportClass qw(blah); sub new { return bless {}; } 1;
Which inherites from a base class:
package BaseClass; sub blah { return "blah"; } 1;
And uses another module to generate a method:
package ImportClass; use strict; use NEXT; sub import { my $package = shift; my $methodname = shift; my $dest = caller; my $method = sub { return shift->SUPER::blah(); }; my $symbol = $dest . '::' . $methodname; { no strict 'refs'; *$symbol = $method; } } 1;
And then I want to call that generated method:
#!/usr/bin/perl use DerivedClass; my $object = DerivedClass->new(); print $object->blah() . "\n";

Obviously this is a simplified version of something actually useful I'm trying to accomplish.

If I run this, I'll get the error:

Can't locate object method "blah" via package "ImportClass" at ImportC +lass.pm line 10.
As SUPER is trying to look at the package in which it's defined. I thought the NEXT module would fix this, but if I change that SUPER to NEXT I get:
Can't call NEXT::blah from ImportClass::__ANON__ at ImportClass.pm lin +e 10

I thought the point of NEXT was to do runtime resolution of SUPER dispatch, but it doesn't seem to like this because it is looking at the method where it was declared, not at where it was imported to. Is there any way to massage NEXT into doing what I want? Is there another way of achieving this?

Thanks for any help anyone can give.

Replies are listed 'Best First'.
Re: SUPER or NEXT from imported methods
by tilly (Archbishop) on Feb 20, 2009 at 22:59 UTC
    The simplest solution is that instead of exporting the function, use eval to compile a copy in the right package. Now SUPER will know what to do. If you do this then for sanity sake look at the bottom of perlsyn to find out how to create a comment that will make errors in your generated code state clearly where the code was compiled from. Trust me, when you are debugging a crash this will be much, much more useful than "eval 53".

    If you don't want to go there, then you'll need to write a function that walks the symbol tree yourself. And you'll need to know inside the function what package it is installed in. This could be done with an import method that looks like:

    sub import { my $package = shift; my $methodname = shift; my $dest = caller; my $method = sub { return super($package, "blah", @_; }; my $symbol = $dest . '::' . $methodname; { no strict 'refs'; *$symbol = $method; } }
    where super is your function that looks at @ISA, and goes looking to find where ${"$package\::$method"}{CODE} is defined, then calls it.

    Honestly this is so much work that I really would advise the eval solution here.

      That function that walks the symbol tree is what NEXT is supposed to be. I don't know why it doesn't like this scenario. What exactly do you mean, use eval to compile it into the package? Something like this?
      sub import { my $package = shift; my $methodname = shift; my $dest = caller; eval <<END package $package; sub $methodname { return shift->SUPER::blah(); }; 1; END }
      That seems to work. I was hoping my imported symbol could make use of closure variables, but I guess I could just interpolate them into the eval.
        NEXT doesn't like this because it uses caller to find the calling package, and caller sees the package the function was compiled in, not the one that was called.

        You have the right idea with the eval approach, but I was suggesting something more like this:

        sub import { my $package = shift; my $methodname = shift; my $dest = caller; my $compiled_in = __PACKAGE__; # The initial comment improves error messages. eval qq{# line 1 "Loaded by $compiled_in\::import" package $package; sub $methodname { return shift->SUPER::blah(); } }; die $@ if $@; }
        Also note that your imported symbol can make use of closure variables without interpolating them. If you put \$foo in your code, that will be compiled as $foo, and that will pick up any lexical $foo that is in scope when the eval happens.
Re: SUPER or NEXT from imported methods
by chromatic (Archbishop) on Feb 21, 2009 at 05:34 UTC

    The SUPER module handles this case.

      Thanks for maintaining this module on CPAN and for noting its usefulness here.

      I think the module's documentation (SUPER) could be made a lot clearer. For example, your reply appears to contradict the module documentation (original emphasis included):

      Note: you must have the appropriate package declaration in place for this to work. That is, you must have compiled the method in which you use this function in the package from which you want to use it. Them's the breaks with Perl 5.

      But perhaps that caveat doesn't apply to one or more other features of the module? That kinda makes sense since this caveat appears under the documentation for the super function (the super method appears in sample code but otherwise doesn't appear to be documented?). But that interpretation is rather at odds with "Them's the breaks with Perl 5." So I'm not completely sure how I'm supposed to interpret that.

      As another example, the phrase "Call it directly." is much less clear than including actual code that demonstrates the proper way to call something.

      I'd also more clearly mark sample code that is not demonstrating how to use the module. For example, include a comment in such sample code:

      # Without this module you'd write: $self->SUPER::method(@_);

      since the sentence(s) above the code block are so much less prominent to the eye.

      I'm also unsure how SUPER() becomes a method of my class. There is no documentation of how one invokes the module itself (I can tell that 'use' is required but it would be nice to state that and even give better hints about what arguments can be passed in when the module is used). I'm guessing that I'm not supposed to inherit from the module.

      I assume that "all parents" means "the names of all (immediate?) parent classes" but that just leaves me wondering why $class is passed into it (rather than it just using ref($invocant) -- or is that supposed to be the current class that should be skipped, ie. what __PACKAGE__ would be if you didn't need this module?). There are several hints that "immediate" applies (indeed, "parent" is not "ancestor"). And I'd prefer to see the order used to search the hierarchy spelled out.

      I suspect most of this would become clear to me if I just gave in and read the source code, but it would probably be better for this to not be a requirement for understanding the module.

      Thanks, again.

      - tye        

        From skimming the module documentation it looks like you should be able to do this:
        $some_package->super("some_method")->($obj, @arguments);
        which would fill in all of the "walk the symbol table" bits in the second option that I gave you for how to solve this.

        I wouldn't have noticed that if I hadn't read carefully with the expectation that chromatic was almost certainly right that it could indeed solve your problem.

Re: SUPER or NEXT from imported methods
by Bloodnok (Vicar) on Feb 21, 2009 at 12:32 UTC
    I encountered something similar what seems like lifetimes ago - I was also using (pun intended) a combination of use base qw/foo/; (which is, we're told, equiv. to require foo; our @ISA = qw/foo/;) & require bar; my @ISA = qw/bar/;.

    IIRC, most of the problems I encountered were resolved by changing use base qw/foo/; to use foo; our @ISA = qw/foo/; and also changing require... to use....

    I reasoned, probably wildly inaccurately, that the change allowed perl compile-time access to the used class packages, hence it [perl] could resolve the method names earlier.

    Just my 10 penn'orth

    A user level that continues to overstate my experience :-))