Beefy Boxes and Bandwidth Generously Provided by pair Networks
more useful options
 
PerlMonks  

comment on

( [id://3333]=superdoc: print w/replies, xml ) Need Help??

The goal

The problems and pitfalls with using AUTOLOAD are so involved that I have always shunned using it. But then I had a scenario that motivated me to find a solution. (Mandatory disclaimer: AUTOLOAD for methods should only be used when you can't solve the problem at compile-time or some other workaround is suitable, or have another really good reason. See e.g. Re^2: "Fields" for "Objects". Update: I.e. for this problem the autoloaded methods may even vary with different instances of the same class.)

My goal was to find a way to take resposibility for my own class' autoloading yet not force other classes to follow a particular implementation in order for them to play nicely with my class. By taking responsibility I mean to cooperate with other AUTOLOADs and have my autoloaded methods show up in can. So the approach is to define AUTOLOAD and can locally, i.e. in the class. This is different from the other two CPAN solutions--Class::AutoloadCAN (PM discussion) and UNIVERSAL::canAUTOLOAD--which overloads UNIVERSAL::can.

<update>
Here is an example of what should be possible to handle without explicit knowledge of which methods Foo autoloads when I write Bar. (The methods may be determined at runtime.)
{ package Foo; sub foo { ... } # autoloads bar() # autoloads baz() } { package Bar; @ISA = Foo::; # autoloads foo() # autoloads bar() }
  • Bar->can('foo') should return \&Foo::foo and not Bar's autoloaded version.
  • Bar->bar should be autoloaded by Bar, and can should reflect that.
  • Bar->baz should be autoloaded by Foo, and can should reflect that if Foo is designed for that.
</update>

If a super class to my class has an AUTOLOAD (and possibly also overloads can) then my subclass should still respect it for those methods not overloaded by my AUTOLOAD (and can). I do not care if a super class has an AUTOLOAD but no overloaded can--I should still forward any unhandled method to the super AUTOLOAD. The point is that the super class should not have to be rewritten in order to work with the AUTULOAD that I add--just as with any methods in a subclass. The super class should be free to choose to report its methods or not.

If I have a class that overloads AUTOLOAD and can and some subclass only overloads AUTOLOAD thus hijacking some methods calls then that's the responsibility of the subclass.

I think this is the best I can do; I take responsibility for my autoloaded methods by cooperating with other autoloaded methods and giving other classes a chance to cooperate with my AUTOLOAD/can.

My proposed solution is found below, but first to the problems that needs to be solved in order for the above to be fulfilled.

The problems with AUTOLOAD

Let's say I write a class that autoloads a set of methods depending on the instance of that class. Let us say that $obj has the autoloaded method foo. I then have the following problems.

$obj->can('autoloaded_method')

If I would do a naive implementation with AUTOLOAD I would have problems with $obj->can('foo'). If I want to have can awareness I would have to overload can. That can be tricky business. Since AUTOLOAD by design is a fallback mechanism the overloaded can must return a reference to the "real" foo method if there is one. It must be that

my $code = $obj->can('foo'); $obj->$code(...)

is exactly the same as

$obj->foo(...)

There are also some technical issues, such as handling $obj->can('can') and $obj->can('SUPER::method') and even $obj->can('SUPER::can').

In theory a super class may want to hide a (super) method as well, so can should not just return whatever UNIVERSAL::can says, but give any super can the chance to have its say.

It can also be that a subclass breaks this, but I can't control what a subclass does.

DESTROY

Personally I always forget about DESTROY. Since DESTROY will be caught by AUTOLOAD during garbage collection if it isn't found in the class or any parent then it should be just silently ignored by AUTOLOAD, unless a parent wants to AUTOLOAD it. (Not that I can think of any reason why you'd autoload DESTROY, but I bet someone else can.)

Ideally $obj->DESTROY should croak if there is no DESTROY and no AUTOLOAD wants to handle it, but I have not found any perfect way to, in AUTOLOAD, detect the difference between $obj->DESTROY and the DESTROY call from the garbage collector.

Inheritance, and multiple inheritance

A naive AUTOLOAD implementation could be

AUTOLOAD { ...; if (should_autoload($method)) { ... } else { croak("No method $method ..."); } }

which works until the parent class also uses AUTOLOAD directly or in a parent. The problem is that you cannot simply do

AUTOLOAD { ...; if (should_autoload($method)) { ... } elsif (my $code = $self->can('SUPER::AUTOLOAD')) { goto &$code; } else { croak("No method $method ..."); } }

because $AUTOLOAD will not be set in the class where the next AUTOLOAD is located. So some hazzle is needed to locate the next AUTOLOAD. The same goes for multiple inheritance even if mro and thus next::method is available.

This can still be solved though, and I actually wrote a help class Class::NextAUTOLOAD that sets $AUTOLOAD properly and uses C3 method resolution (via mro or Algorithm::C3) so it works for multiple inheritance as well.

Summary

AUTOLOAD never takes precedence over methods found in the symbol table. can must report a code reference to the method that AUTOLOAD will invoke. can should let any super can have its say.

It is still possible to add a child AUTOLOAD that does not report its methods via can and thus there can be a disprepancy between $obj->can('foo')->($obj) and $obj->foo but the bare AUTOLOAD is responsible for that.

Super classes should not need to be rewritten in order to add autoloading (AUTOLOAD and can) in a subclass.

can should only report the autoloaded methods if the class wants to. It should not affect a third class so that it starts to report autoloaded methods.

The solution?

Here is the proposed implementations of AUTOLOAD that has the properties and solves the problems described above. Multiple inheritance can be handled either through mro or Algorithm::C3.

The "proper" AUTOLOAD?

I propose that the "proper" AUTOLOAD should look like

AUTOLOAD { my $self = $_[0]; # Not shift, using goto. my $method = ...; if (my $code = _autoloaded($self, $method)) { goto &$code; } elsif (my $next = next_autoload($self)) { goto &$next; } elsif ($method eq 'DESTROY') { # If people do $obj->DESTROY and there's no # DESTROY available then they'll be surprised. # Just as surprised I am that they call DESTROY # explicitly. return; } else { croak(...); } }

This implementation is pretty straight forward. If a method is autoloaded then it is invoked. If it isn't and there is another AUTOLOAD then the call is forwarded. If this is the last AUTOLOAD in line then it croaks unless DESTROY was called.

The "proper" can()?

I propose that the "proper" can should look like

sub can { my ($self, $method) = @_; # Not shift, using goto. # The call to UNIVERSAL::can() should be inside an eval EXPR # to get caller info right for when $method =~ /^SUPER::/, # if can() is exported (see Class::AUTOCAN below). my $universal = UNIVERSAL::can($self, $method); if (not defined $universal) { if (my $code = _autoloaded($self, $method)) { return $code; } } if (my $next = $self->next::can) { goto &$next } return $universal; # May be undefined. }

The method is autoloaded only if there is no such method regularly defined elsewhere in the inheritance chain. If there is no autoloaded method it queries the next can if there is one. If there is no next can then it returns the reference to the regular method, if any.

This is very similar to the AUTOLOAD above, only UNIVERSAL::can need not be checked in AUTOLOAD and DESTROY need not be handled in can. Instead of croaking if there is no regular or overloaded method it just returns undef.

Class::AUTOCAN

I actually started writing this as an RFC for a module I wrote, Class::AUTOCAN, but I realized that most of the RFC would be about the problems it intends to solve rather than the module itself. Class::AUTOCAN basically exports AUTOLOAD and can as described above but uses a subroutine reference instead of &_autoloaded. It can be used like

use Class::AUTOCAN sub { my $self = shift; my ($method) = @_; if ($method =~ /^auto_/) { return sub { my $self = shift; return "$self called $method"; }; } return; };

or

Class::AUTOCAN->install( target => $pkg, code => sub { ... }, );

where the subroutine should return the autoloaded method or nothing if the class shouldn't autoload the method.

I would like to think that Class::AUTOCAN is "AUTOLOADing done right", but I would appreciate any feedback before thinking of preparing it for CPAN. I would not want to release another module to solve autoloading issues just to realize that it is flawed or that I missed something in the bigger picture.

Any feedback and thoughts would be appreciated.

lodin


In reply to A working strategy to handle AUTOLOAD, can(), and (multiple) inheritance without touching UNIVERSAL? by lodin

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post; it's "PerlMonks-approved HTML":



  • Are you posting in the right place? Check out Where do I post X? to know for sure.
  • Posts may use any of the Perl Monks Approved HTML tags. Currently these include the following:
    <code> <a> <b> <big> <blockquote> <br /> <dd> <dl> <dt> <em> <font> <h1> <h2> <h3> <h4> <h5> <h6> <hr /> <i> <li> <nbsp> <ol> <p> <small> <strike> <strong> <sub> <sup> <table> <td> <th> <tr> <tt> <u> <ul>
  • Snippets of code should be wrapped in <code> tags not <pre> tags. In fact, <pre> tags should generally be avoided. If they must be used, extreme care should be taken to ensure that their contents do not have long lines (<70 chars), in order to prevent horizontal scrolling (and possible janitor intervention).
  • Want more info? How to link or How to display code and escape characters are good places to start.
Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others musing on the Monastery: (3)
As of 2024-03-28 13:13 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found