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

package Base; sub AUTOLOAD { (my $meth = our $AUTOLOAD) =~ s/([^:]+)$/$1/; if ( $meth =~ /^is_/ ) { return; } # ???? } package Foo; our @ISA = qw( Base ); sub is_foo { 1 } package Bar; our @ISA = qw( Base ); sub is_bar { 1 } # And so forth
I anticipate having dozens of these sibling classes, so I don't want Base to have to have a sub is_(whatever) { 0 }. Each one only declares the sub is_(whatever) { 1 } that it is. I don't want to use isa() cause the classnames are much longer and it's just ugly. And, frankly, this should be do-able. But, I don't want AUTOLOAD to catch methods that aren't of the form /^is_/. I want it to fail as if it wasn't there.

My criteria for good software:
  1. Does it work?
  2. Can someone else come in, make a change, and be reasonably certain no bugs were introduced?

Replies are listed 'Best First'.
Re: Redispatching AUTOLOAD for failure
by moritz (Cardinal) on Apr 14, 2008 at 20:58 UTC
    What's wrong with just dying in AUTOLOAD?
    sub AUTOLOAD { (my $meth = our $AUTOLOAD) =~ s/([^:]+)$/$1/; if ( $meth =~ /^is_/ ) { return; } else { die "No such subroutine '$meth''"; } }
    It's not exactly the same as you want, though (but it also happens at runtime).
Re: Redispatching AUTOLOAD for failure
by kyle (Abbot) on Apr 14, 2008 at 20:54 UTC

    I'd think the usual way to handle that would be to die or croak to say the method does not exist. Is there something wrong with that?

    I think this won't do what you intend it to do:

    (my $meth = our $AUTOLOAD) =~ s/([^:]+)$/$1/;

    Since you replace exactly what you match with what you match, $meth will just equal $AUTOLOAD.

    As an aside, if you're writing an AUTOLOAD(), it might be wise to write a can() method to go with it too, if you haven't done that already.

Re: Redispatching AUTOLOAD for failure
by ikegami (Patriarch) on Apr 14, 2008 at 21:07 UTC

    Your "is" methods return different values in list context and in scalar context. I recommend changing return; to return 0;.

    I was going to recommend the following solution, but the message thrown by Carp might not refer to the right line. Using <tt class='inlinecode'>caller</tt> directly to build the message would work better.

    sub AUTOLOAD { my ($pkg, $meth) = our $AUTOLOAD =~ /(.*)::(.*)/; if ( $meth =~ /^is_/ ) { return 0; } require Carp; Carp::croak(qq{Can't locate object method "$meth" via package "$pk +g"}); }
Re: Redispatching AUTOLOAD for failure
by jhourcle (Prior) on Apr 15, 2008 at 01:44 UTC

    I'd probably avoid autoload, and just assign the subroutine. Based on what I think you're trying to do, I'd probably have an init function in Base that would setup whatever I might need. Obviously, you'd want to assign '$is_func' to handle your method names relative to the package names.

    package Base; sub init { my $class = shift; my $is_func = $class.'::is_'.lc($class); no strict 'refs'; *$is_func = sub { return 1 }; } ... package Foo; our @ISA = qw( Base ); __PACKAGE__->init(); ... package Bar; our @ISA = qw( Base ); __PACKAGE__->init();
Re: Redispatching AUTOLOAD for failure
by runrig (Abbot) on Apr 14, 2008 at 21:04 UTC
    It seems like you could write one AUTOLOAD method in your base class that calls Scalar::Util::blessed() on the object to get the class, and compares that against the method name. Which should work if the method and class names are as similar as the ones in your example, and you only have sibling classes (not parent/child, other than the base class).
Re: Redispatching AUTOLOAD for failure
by rir (Vicar) on Apr 14, 2008 at 21:01 UTC
    I think you want NEXT.pm.

    Be well,
    rir

Re: Redispatching AUTOLOAD for failure
by lodin (Hermit) on Apr 15, 2008 at 14:56 UTC

    I'd avoid AUTOLOAD. I see two realistic alternatives. The first is to not use different methods but one and the same and pass an argument. A possible implementation would be

    { package MyBase; sub is { my $self = shift; my ($type) = @_; my $method = 'is_' . $type; if (my $code = $self->can($method)) { return $self->$code; } return 0; } } { package Foo; use base MyBase::; sub is_foo { 1 } } { package Bar; use base MyBase::; sub is_bar { 1 } } { package FooAndBar; # Faker! use base MyBase::; sub is_foo { 1 } sub is_bar { 1 } } printf "%-9s %s %s\n", $_, $_->is('foo'), $_->is('bar') for Foo::, Bar::, FooAndBar::; __END__ Foo 1 0 Bar 0 1 FooAndBar 1 1
    Another alternative is that you use your base class to setup the inheritance and when doing that it also defines the is_* methods. Here's an implementation:
    package MyBase; use strict; use Symbol 'qualify_to_ref'; sub import { my $class = shift; if (@_) { if ($_[0] eq '-base') { my (undef, %p) = @_; my $type = $p{is} or die '...'; my $pkg = caller; _setup_inheritance($class => ($pkg, $type)); } else { die '...'; } } } sub _setup_inheritance { my ($class) = shift; my ($pkg, $type) = @_; $type !~ /\W/ or die '...'; my $method = 'is_' . $type; { my $gref = qualify_to_ref($method); *$gref = sub { 0 } unless defined &$gref; } { my $gref = qualify_to_ref($method => $pkg); *$gref = sub { 1 }; } push @{*{qualify_to_ref(ISA => $pkg)}}, $class; return; } 1;
    and the program:
    { package Foo; use MyBase -base, is => 'foo'; } { package Bar; use MyBase -base, is => 'bar'; } printf "%-9s %s %s\n", $_, $_->is_foo, $_->is_bar for Foo::, Bar::; __END__ Foo 1 0 Bar 0 1

    lodin