Beefy Boxes and Bandwidth Generously Provided by pair Networks
Pathologically Eclectic Rubbish Lister
 
PerlMonks  

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

by lodin (Hermit)
on Aug 29, 2009 at 16:58 UTC ( #792087=perlmeditation: 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

Comment on A working strategy to handle AUTOLOAD, can(), and (multiple) inheritance without touching UNIVERSAL?
Select or Download Code
Re: A working strategy to handle AUTOLOAD, can(), and (multiple) inheritance without touching UNIVERSAL?
by chromatic (Archbishop) on Aug 29, 2009 at 17:38 UTC
    If I want to have can awareness I would have to overload can.

    You can also predeclare the subs you want to AUTOLOAD.

    That can be tricky business.

    Not really. If your AUTOLOAD has some logic to determine whether it should generate a subroutine, you can factor that logic into a subroutine shared between AUTOLOAD and an overridden can.

    You don't even have to go through gyrations in can to return a reference. Perl 5 autovivifies subroutines if you take references to them in the appropriate way. Of course, you can also factor out the code which installs the reference and share it between can and AUTOLOAD.

      You can also predeclare the subs you want to AUTOLOAD.

      If I know the names of the subroutines I AUTOLOAD then I don't have a problem. The problem is when you do not know the methods during compilation.

      If your AUTOLOAD has some logic to determine whether it should generate a subroutine, you can factor that logic into a subroutine shared between AUTOLOAD and an overridden can.

      Indeed, that is the problem I'm trying to solve and the solution that I use.

      You don't even have to go through gyrations in can to return a reference. Perl 5 autovivifies subroutines if you take references to them in the appropriate way. Of course, you can also factor out the code which installs the reference and share it between can and AUTOLOAD.

      I don't quite understand this paragraph. What subroutines will be autovivified? Maybe this is just wording, but I'm not installing any references. I get the impression that you think I am trying to lazily load subroutines to e.g. save memory. That is not the problem I try to solve. The major problem is how to provide fallback methods dynamically during runtime that shows up in can. Maybe that should have been clearer. I have updated the root node to make this clearer, I hope.

      lodin

        All credit to lodin for working on a subject that I just happened to be thinking about. I have quickly found that it is quite an old subject however - for example: Why breaking can() is acceptable and Class::AutoloadCAN.

        Also I am actually none the wiser. The order is as follows:

        1. If you stop using AUTOLOAD the Perl::Critic shuts up.
        2. If you must use AUTOLOAD you generally have to overload 'can'. sub can {return 1;} seems to work for me.
        3. Actually the above is wrong since 'can' is supposed to return a reference to the subroutine. But how do I do that when the subroutine is AUTOLOAD?
        4. I see a lot of modules offering solutions but I don't feel inclined to trust them. I think I would rather write code that I understand and depends on modules that look plausible.

        Edit: This seems to work.

        # In class implementing AUTOLOAD for everything. sub can { my $self = shift; my $method = shift; return sub { my $self = shift; .......... return ....; }; } sub AUTOLOAD { my $self = shift; my @method = split /::/, $AUTOLOAD; my $param = pop @method; my $c = $self->can($param); return &$c($self); }
Re: A working strategy to handle AUTOLOAD, can(), and (multiple) inheritance without touching UNIVERSAL?
by ikegami (Pope) on Aug 31, 2009 at 19:05 UTC

    A trick that's useful in some circumstances is to declare the subs to be autoloaded.

    $ perl -le' sub AUTOLOAD { print "Loading $AUTOLOAD" } sub foo; __PACKAGE__->can("foo")->(); ' Loading main::foo

    (Oops, already covered.)

      Yeah. If I could predict or control what functions can be used I would not autoload. Generally you can so I think autoloading should be rare. Where I have found it useful is in enabling much looser coupling.

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlmeditation [id://792087]
Front-paged by tye
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others lurking in the Monastery: (10)
As of 2014-10-31 15:53 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    For retirement, I am banking on:










    Results (219 votes), past polls