http://qs321.pair.com?node_id=792123


in reply to Re: A working strategy to handle AUTOLOAD, can(), and (multiple) inheritance without touching UNIVERSAL?
in thread A working strategy to handle AUTOLOAD, can(), and (multiple) inheritance without touching UNIVERSAL?

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

Replies are listed 'Best First'.
Re^3: A working strategy to handle AUTOLOAD, can(), and (multiple) inheritance without touching UNIVERSAL?
by SilasTheMonk (Chaplain) on Aug 31, 2009 at 18:10 UTC

    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); }

      As the thread here on PM and the two CPAN modules (written by notable Perl people) suggests, this is not as easy as it looks. I think the best way to get it right each time is to find an approach that always acts as "expected", and then put it in a module. I trust a module to be more consistent than me when faced with a repeated problem. :-)

      Regarding your suggestions; it fails to behave like I would expect when it is inherited (and upon object destruction, but to be fair you do not have a constructor).

      { package Foo; sub can { my $self = shift; my $method = shift; return sub { print __PACKAGE__ . ": autoloaded $method\n" }; } sub AUTOLOAD { my $code = $_[0]->can(our($AUTOLOAD) =~ /.*::(.*)/s); goto &$code; } } { package Bar; our @ISA = Foo::; sub new { bless {} => shift } sub bar { print __PACKAGE__ . ": bar\n" } } my $o = Bar::->new; my $m = $o->can('bar'); $o->$m; $o->bar; __END__ Foo: autoloaded bar Bar: bar Foo: autoloaded DESTROY
      In my solution outlined in the root node I handle it by moving the logic that generates the autoloaded subroutine reference to a common routine that both AUTOLOAD and can use. Then I see if UNIVERSAL::can returns anything. If it does it means that AUTOLOAD will not be invoked for $o->bar, so the return value of UNIVERSAL::can should be used instead. (This of course assumes that UNIVERSAL::can is not overloaded to do something else.)

      The remaining logic in the root node is to handle when you use AUTOLOAD in a child class and the parent class also uses AUTOLOAD. This is even rarer, but it is still possible. As you say, it is important to trust a module to handle all edge cases so that you don't end up spending your evening debugging due to the module.

      lodin

        Actually I do have an empty DESTROY but I did not bother copying it. In my case if there is no DESTROY then the program tries to do stuff on a non-existent CGI::Application reference.

        The inheritence question is more interesting. Yes I can see my approach makes no special effort to handle inheritence. But actually I think it would work. Let me put my approach in words.

        1. Avoid AUTOLOAD except where it gives a big gain in loose coupling.
        2. Don't rely on someone else's module. The area is too complicated.
        3. This means you have to craft your own solution, but it is fairly simple.
        4. Define a DESTROY method as bad stuff happens otherwise.
        5. Define a semantically correct "can" function that either returns undef or a CODE ref.
        6. Define the AUTOLOAD function in the fairly standard way based upon the "can" function.
        7. If I were to write a derived class I would either inherit both "can" and "AUTOLOAD" and override less impactful functions; or I would override "can" in the same way and make use of SUPER::can where appropriate.
        8. I avoid multiple inheritence so I would make autoloading in a multiple inheritence scenario an absolute no.