|No such thing as a small change|
A working strategy to handle AUTOLOAD, can(), and (multiple) inheritance without touching UNIVERSAL?by lodin (Hermit)
|on Aug 29, 2009 at 16:58 UTC||Need Help??|
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.)
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.
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.
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
is exactly the same as
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.
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.
A naive AUTOLOAD implementation could be
which works until the parent class also uses AUTOLOAD directly or in a parent. The problem is that you cannot simply do
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.
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.
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.
I propose that the "proper" AUTOLOAD should look like
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.
I propose that the "proper" can should look like
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.
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
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.