Beefy Boxes and Bandwidth Generously Provided by pair Networks
XP is just a number
 
PerlMonks  

Re: is autoload bad style?

by ihb (Deacon)
on Jul 26, 2003 at 18:39 UTC ( [id://278115]=note: print w/replies, xml ) Need Help??


in reply to is autoload bad style?

Update: As adrianh and Aristotle pointed out there is a module, NEXT, to cope with this. But NEXT has some issues of its own and is slow. (See "Bugs and irritations" in the documentation.) Having to pull in another module just proves my point: unless you really need AUTOLOAD, try to stay away from it. It may very well create more problem than it tries to solve.

I've never really needed AUTOLOAD, although I've found some nifty (and "useful") uses for it. (That doesn't mean there aren't any cases where it is needed.)

I try to avoid AUTOLOAD in object oriented code. There are two subtile traps that I'll try to explain here. The problem applies to all AUTOLOADs that don't want to do anything useful for all invokations of it. As example I'll use the popular concept of autogenerating attribute methods, which is particulary nice as an example as there would be no problems if the methods was pre-generated instead. (See Re: Re: is autoload bad style? for an elaboration on this.)

Issue 1

package Foo; my %attrs = map { $_ => 1 } qw/ this that /; AUTOLOAD { my ($m) = our $AUTOLOAD =~ /.*::(.*)/s; return if $m eq 'DESTROY'; if ($attrs{$m}) { print "OK: $m"; } else { print "Failure: $m"; # Here you need to emulate perl error message. # Note that you don't want to use the Carp # module for this. } }

This is your standard attribute AUTOLOAD routine customized to illustrate my point. It works as expected:

Foo::->this; # OK: this Foo::->that; # OK: that Foo::->these; # Failure: these

Now let's inherit Foo, and use the same technique in the child.

package Foo::Bar; use base 'Foo'; my %attrs = map { $_ => 1 } qw/ hah heh /; AUTOLOAD { my ($m) = our $AUTOLOAD =~ /.*::(.*)/s; return if $m eq 'DESTROY'; if ($attrs{$m}) { print "OK: $m"; } else { print "Failure: $m"; } }

This too seems to work as expected:

Foo::Bar::->hah; # OK: hah Foo::Bar::->heh; # OK: heh Foo::Bar::->hoh; # Failure: hoh

But it doesn't quite work right when trying to use Foo's attributes:

Foo::Bar::->this; # Failure: this Foo::Bar::->that; # Failure: that

So apparently we need to patch &Foo::Bar::AUTOLOAD.

package Foo::Bar; use base 'Foo'; my %attrs = map { $_ => 1 } qw/ hah heh /; AUTOLOAD { my ($m) = our $AUTOLOAD =~ /.*::(.*)/s; return if $m eq 'DESTROY'; if ($attrs{$m}) { print "OK: $AUTOLOAD"; } else { my $self = shift; if ($self->can('SUPER::AUTOLOAD')) { return $self->${\"SUPER::$m"}(@_); } else { print "Failure: $AUTOLOAD"; } } }

Now it's suddenly not that pretty anymore, and not fully transparent. There is a caller backtrace issue. An error message about missing attribute will probably (and should, imho) point at the return line in &Foo::Bar::AUTOLOAD rather than where it really is invoked by the programmer. You could wrap the call in an eval BLOCK and propagate the error. But I wouldn't call that elegant.

We can't use goto(), because then $AUTOLOAD won't be set in the target &AUTOLOAD. You could perhaps manually find where SUPER::AUTOLOAD is, and manually set $AUTOLOAD. But then it gets even uglier.

As a parenthesis: return if $m eq 'DESTROY'; is bad in the general case if we should be strict. There's no guarentee that no super-autoload wants to deal with it.

For me, this is enough to avoid AUTOLOAD and think of it as the very last resort.

But wait, there's more.

Issue 2

This problem has to do with multiple inheritance. We start by defining two simple classes that use AUTOLOADed attribute methods, as above. As in issue 1 this issue applies to all AUTOLOAD routines that have some criteria for when it should do anything useful.

{ package A; my %attrs = map { $_ => 1 } qw/ xism /; AUTOLOAD { my ($m) = our $AUTOLOAD =~ /.*::(.*)/s; return if $m eq 'DESTROY'; if ($attrs{$m}) { print "OK: $AUTOLOAD"; } else { print "Failure: $AUTOLOAD"; } } } { package B; my %attrs = map { $_ => 1 } qw/ yism /; AUTOLOAD { my ($m) = our $AUTOLOAD =~ /.*::(.*)/s; return if $m eq 'DESTROY'; if ($attrs{$m}) { print "OK: $AUTOLOAD"; } else { print "Failure: $AUTOLOAD"; } } }

Just as before, they seem to work alright:

A::->xism; # OK: A::xism B::->yism; # OK: B::yism

Now, let's inherit both A and B.

package AB; use base 'A', 'B';

Now we expect both AB::->xism and AB::->yism to work.

AB::->xism; # OK: AB::xism AB::->yism; # Failure: AB::yism

But nope. The explanation is that there's no way for an AUTOLOAD to tell perl that "oh, no, sorry, ignore me". Perl never gets a chance to discover &B::AUTOLOAD.

I consider these two issues quite severe, and therefore I tend to frown upon AUTOLOAD.

If one takes these two issues into consideration the coolness of AUTOLOAD is reduced--at least for me. I'm not totally anti-AUTOLOAD but I think it should be used with caution.

Hope I've helped,
ihb

Replies are listed 'Best First'.
Re^2: is autoload bad style?
by adrianh (Chancellor) on Jul 26, 2003 at 18:55 UTC

    You might want to take a look at NEXT by the ever excellent TheDamian, which can be used to get over these problems.

Re^2: is autoload bad style?
by Aristotle (Chancellor) on Jul 26, 2003 at 20:24 UTC
    Perl never gets a chance to discover &B::AUTOLOAD.
    That's why TheDamian wrote NEXT..

    Makeshifts last the longest.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://278115]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others wandering the Monastery: (3)
As of 2024-04-20 01:47 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found