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

Hi,

Is there a way to make Perl call a class' AUTOLOAD method before going up on @ISA? For example, in the following code:

package A; sub new { my $class=shift; my $self={}; bless $self, $class; return $self; } sub doit { print "doit in A\n"; } package B; @ISA=qw(A); sub AUTOLOAD { print "autoload in B, called as $AUTOLOAD\n"; } package main; $a=B->new; $a->doit;
I get the following output:
doit in A autoload in B, called as B::DESTROY
What I would like is to have it call AUTOLOAD on B when doit is not found, before looking for it in A, so that I get:
autoload in B, called as B::doit autoload in B, called as B::DESTROY
Can this be done?

Thanks a lot,

--ZZamboni

Replies are listed 'Best First'.
Re: How to call AUTOLOAD before @ISA?
by perlmonkey (Hermit) on May 10, 2000 at 05:18 UTC
    I am pretty sure (but not positive) the answer is: No, this cant be done.

    Calling AUTOLOAD before your parent functions sort of defeats the purpose of inheritance. If this were the case, a parent function would never get called because the AUTOLOAD would get called first. AUTOLOAD is a last ditch effort when *all* else fails, so if perl cannot find the function in the class or anywhere in chain of parents it will call the first AUTOLOAD it finds.

    I would recommend just moving the AUTOLOAD to the parent. Or not give the class a parent if you think the class needs to have an AUTOLOAD function.

    Another idea is instead of total inheritance you can do cheesy selective pseudo-inheritance, ie only grab the functions you want like:
    package B; *new = \&A::new; sub AUTOLOAD { print "autoload in B, called as $AUTOLOAD\n"; }
    So now your main will do what you want. I have just made &A::new synonomous with &B::new in the symbol table so you only have to maintain one new function. I dont this this would be too much work to just alias the symbols for each function that you want.
      But the thing is that I want to inherit all the methods from the superclass, but I only want to call them in a controlled fashion from AUTOLOAD when certain conditions are met. My idea was to do something like this:
      sub AUTOLOAD { my $self=shift; my $method=$AUTOLOAD; $method=~s/^.*:://; if (certain conditions are met) { do something } else { $self->SUPER::$method(@_); } }
      The thing is that the check and the "do something" are all the same, but this applies to several methods, that's why I wanted to use AUTOLOAD.

      I'm also pretty sure that this cannot be done (all the documentation seems to indicate so) and I have already solved my problem by dynamically defining the necessary subroutines in B's BEGIN block, but I will be glad to hear any other ideas.

      Thanks,

      --ZZamboni

        There are a couple of other ways to get around this. If you're comfortable mucking about in the symbol table, you can generate your subroutines from a template (especially the "do some checks here" code) and eval() them into being the first time they're needed. I'd do that in a BEGIN block, perhaps. Another option is, of course, delegation -- probably the best approach.

        You could also fake inheritance, by not using @ISA, and hard-coding calls to the parent. That would allow you to use AUTOLOAD to generate them. You could even write a dispatcher method, to do all the checks for you, then find the appropriate subroutine. You'd have to pass the name of a method you want, and you could use a hash of sub refs to avoid symbolic references. That's not a bad solution.

        But there's no way I'm aware of to call AUTOLOAD before checking the inheritance tree. Code provided for any of these examples if you're really curious.

        well this is an obvious solution (okay maybe only obvious to my cloudy little mind):
        sub AUTOLOAD { my $self=shift; my $method=$AUTOLOAD; my $PSEUDO_PARENT = 'A'; $method=~s/^.*:://; return if $method eq 'DESTROY'; if (certain conditions are met) { do something } else { #dynamically call the pseudo parent function my $return_value = eval("&{$PSEUDO_PARENT"."::"."$method}".'($s +elf, @_)'); #catch eval error! if( $@ ) { die $@; } #return whatever the eval'd function returned return $return_value; } }
        The problem with this is hardcoding the parent class in, instead of using SUPER. And with a combination of eval and AUTOLOAD you are sure to not the the optimal performance.
Re: How to call AUTOLOAD before @ISA?
by btrott (Parson) on May 10, 2000 at 07:11 UTC
    Instead of inheritance, why not use delegation? It sounds more like that's what you're looking for. Make your B object contain an A object--then in your AUTOLOAD function delegate certain methods to be called on the A object.

    Like this:

    package A; sub new { my $class = shift; bless {}, $class; } sub doit { print "doit in A\n"; } package B; sub new { my $class = shift; bless { a => new A }, $class; } sub AUTOLOAD { my $self = shift; ## Your debugging message. print "autoload in B, called as $AUTOLOAD\n"; return if $AUTOLOAD =~ /::DESTROY$/; ## Remove the name of your package. $AUTOLOAD =~ s/^B:://; ## Call the method on your A object. $self->{a}->$AUTOLOAD(@_); } package main; my $b = B->new; $b->doit;
    Sometimes a strategy like delegation works better than inheritance, particularly when you're trying to "catch" every method call to an object. Another option might be using a tied object, similarly to how I used it in Tie: Creating special objects.
RE (tilly) 1: How to call AUTOLOAD before @ISA?
by tilly (Archbishop) on Sep 28, 2000 at 00:20 UTC
    Contrary to documentation, it can be done for all methods that are not in the UNIVERSAL class. (Things like can and isa.)

    Have two classes, the first of which has your desired @ISA, the second of which has an AUTOLOAD. Bless your objects into the second, and have the AUTOLOAD throw method calls to the first class upon need.

    You should note that this will significantly slow method calls to this class. This can be sped up by having the base class populated with your wrapper in the AUTOLOAD. Still a performance hit but not as bad.

Re: How to call AUTOLOAD before @ISA?
by Anonymous Monk on Sep 28, 2000 at 00:01 UTC
    If the inheritance is necessary (eg, for data inheritance, etc..) why not add a snippet like the following:
    package B; foreach $autoload_handled ( 'doit', 'dothis', 'dothat' ) { \&{ $autoload_handled } = \&AUTOLOAD; }
    Sorry, I'm not sure if that exact code will work; I'm still learning about subroutine handling in classes, sub-references, etc... But the idea is there, at least. Hope that helps?