ced has asked for the wisdom of the Perl Monks concerning the following question:

let say we have:
use Parent;
use Parent::Child;
use Parent::Child::Granchild;

In Parent we have an AUTOLOAD method catching setters and getters for example.
If we have a Parent::Child::Granchild::AUTOLOAD method,
it will catch the non defined methods for Parent::Child::Granchild objects. If nothing is done in the lower AUTOLOAD howto get the upper one to try before giving up.

I tried in Parent::Child::Granchild

sub AUTOLOAD {
my $self=shift;
.....
if (we should do something here){
     we do it;
     return something;

}
else {
    return $class->SUPER::AUTOLOAD();
}
This works but we loose the CONTENT of $AUTOLOAD coming in Parent::AUTOLOAD.

Im'not very satisfied with this solution.
Is there some nicer way to do this and let Perl do the search for AUTOLOADS upwards?

  • Comment on More then one AUTOLOAD in a class hierarchy.

Replies are listed 'Best First'.
Re: More then one AUTOLOAD in a class hierarchy.
by chromatic (Archbishop) on Nov 11, 2004 at 18:08 UTC

    I don't seem to have any trouble with what I think you want. Does the following code work for you?

    #!/usr/bin/perl use strict; use warnings; package Parent; sub new { bless {}, shift; } sub DESTROY {} sub AUTOLOAD { our $AUTOLOAD; my $self = shift; print "Parent called for '$AUTOLOAD' on $self\n"; } package Child; our @ISA = 'Parent'; sub AUTOLOAD { our $AUTOLOAD; my $self = shift; my ($method) = $AUTOLOAD =~ /::(\w+)$/; my $parent = "SUPER::$method"; return $self->$parent( @_ ) unless $method =~ /^kid_/; print "Child called for '$method' on $self\n"; } package Grandchild; our @ISA = 'Child'; sub AUTOLOAD { our $AUTOLOAD; my $self = shift; my ($method) = $AUTOLOAD =~ /::(\w+)$/; my $parent = "SUPER::$method"; return $self->$parent( @_ ) unless $method =~ /^grandkid_/; print "Grandchild called for '$method' on $self\n"; } package main; print "Constructors\n"; my $p = Parent->new(); my $k = Child->new(); my $gc = Grandchild->new(); print "Calling foo() on all\n"; $p->foo(); $k->foo(); $gc->foo(); print "Calling kid_foo() on child and grandchild\n"; $k->kid_foo(); $gc->kid_foo(); print "Calling grandkid_foo() on grandchild\n"; $gc->grandkid_foo();
      My error was that I call the upper AUTOLOAD with the class and not the object.
      Your solution is clean and forseable.
      That is just what I wanted.
      Thks
Re: More then one AUTOLOAD in a class hierarchy.
by stvn (Monsignor) on Nov 11, 2004 at 18:28 UTC
    This works but we loose the CONTENT of $AUTOLOAD coming in Parent::AUTOLOAD.

    Then why not just set it?

    $Parent::AUTOLOAD = $AUTOLOAD; $self->Parent::AUTOLOAD(@_);
    I do something similar to this when i make proxy classes in IOC::Proxy. The proxy class catches the AUTOLOAD and allows you to log it (or whatever you want your proxy to do) and then passes it to the actual object it is proxying.

    Of course this way requires you to know what superclass you want to dispatch too. A more generic solution would involve going through the @ISA array and calling AUTOLOAD on each superclass (if they have an AUTOLOAD that is). The code might look something like this (note: this is highly untested)

    if (we can do something) { ... } else { no strict 'refs'; foreach my $super (@{'${class}::ISA"}) { my $method; if ($method = $super->can('AUTOLOAD')) { ${"${super}::AUTOLOAD"} = $AUTOLOAD; return $class->$method(@_); } } }
    Of course this would only go down one level of the tree, unless this code was included in the AUTOLOAD on each level of your object hierarchy. A recursive solution would start getting really hairy (as if this wasn't hairy enough).

    But now I ask you, is all this really worth it to save a little bit of repetitive typing by hand-making getters and setters?

    -stvn
      I define the attributes of the object just in the overloaded new method.Thus in one place.
      Setters and getters are handled centrally by the Parent. So that is not just saving typing it is keeping it compact and avoiding lines and lines of not really value added code.
      Let the machine do the boring work and you do the thinking. Is that not the real way of being lazy? :=)
      Thanks for your thinking.
        I define the attributes of the object just in the overloaded new method.Thus in one place. Setters and getters are handled centrally by the Parent. So that is not just saving typing it is keeping it compact and avoiding lines and lines of not really value added code.

        So then all your attributes are public? And attributes which are defined by Parent::Child and Parent::Child::GrandChild are handled by code in the parent? Which means that code in Parent is directly accessing fields which don't belong to it. This is actually bad OO, and just because perl's (some would say broken) OO system lets you do it, does not mean you should do it.

        My personal approach (and I am known to be a (sometimes) overly defensive programmer so take it with a grain of salt) is that I never create getters and setters until I actually see a need for one. And IMO it is bad OO design to have your superclass deal with anything which is specific to your sub-class. Your superclass should never need to have any knowledge or deal with attributes of your sub-classes in any way (regardless of how dynamic the code which makes it possible).

        Let the machine do the boring work and you do the thinking.

        My argument is that you shouldn't need to actually do the "boring" work unless your "thinking" tells you that it is nessecary. At which point you can implement whatever it is in a reasonable amount of time and not have to do it again.

        Is that not the real way of being lazy?

        No. The real way of being lazy is to do it right the first time (more work up front), so that you don't have to do it again when you realize it's wrong.

        AUTOLOAD is an oft abused feature of perl's (akward) OO system. IMO it should be used with great caution, and only when nessecary. It's mis-use has tripped up many an perl programmer experienced and in-experienced alike. The simple fact that it negates any meaningful use of can is IMO enough to not use it.

        -stvn
Re: More then one AUTOLOAD in a class hierarchy.
by tilly (Archbishop) on Nov 12, 2004 at 00:05 UTC
    In my books an AUTOLOAD catching getters and setters is almost always better replaced with a call to a function which creates getters and setters by assigning closures to typeglobs. Like this bare-bones example:
    use strict; package Parent; sub make_accessors { my ($class, @attributes) = @_; foreach my $attribute (@attributes) { no strict 'refs'; *{"$class\::$attribute"} = sub { my $self = shift; if (@_) { $self->{$attribute} = shift; } return $self->{$attribute}; }; } } package Child; our @ISA = 'Parent'; __PACKAGE__->make_accessors(qw(this that the other));
    And now the method cascading that you asked for comes for free from Perl's OO dispatch. There is no AUTOLOAD issue to figure out how to resolve because there is no AUTOLOAD to confuse the issue.
Re: More then one AUTOLOAD in a class hierarchy.
by gaal (Parson) on Nov 11, 2004 at 20:15 UTC
    From the docs to NEXT:
    NEXT.pm adds a pseudoclass named NEXT to any program that uses it. If +a method m calls $self-NEXT::m()>, the call to m is redispatched as i +f the calling method had not originally been found. In other words, a call to $self-NEXT::m()> resumes the depth-first, le +ft-to-right search of $self's class hierarchy that resulted in the or +iginal call to m. [...] Another typical use of redispatch would be in AUTOLOAD'ed methods. If +such a method determined that it was not able to handle a particular +call, it might choose to redispatch that call, in the hope that some +other AUTOLOAD (above it, or to its left) might do better.

    Sounds like you could use this.

      This proves one more time that
      "There is more than one way to do it"
      Thks