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

Hey folks! I'm working on a module which embeds Xmms::Remote, starting with something like this:
sub new { my ($class) = @_; my ($self, $pid); $self = bless +{}, $class; $self->{xmms} = Xmms::Remote->new; # ... more stuff ... }
I found myself wanting to pass a bunch of methods directly into my embedded object, writing one-line methods like this:
sub is_paused { $_[0]->{xmms}->is_paused; } sub is_playing { $_[0]->{xmms}->is_playing; } sub pause { $_[0]->{xmms}->pause; } sub play { $_[0]->{xmms}->play; } sub stop { $_[0]->{xmms}->stop; }
I was just about to add another, when I said to myself, "$self, this is stupid. If you used AUTOLOAD correctly, you could automatically have any unrecognized method automatically called on your embedded object". So, I started to try to write such an AUTOLOAD, and I realized I was confused about AUTOLOAD and didn't know how to do it. This is how far I got:
sub AUTOLOAD { my ($self) = @_; my ($method); ($method = $AUTOLOAD) =~ s/.*://; if ($self->{xmms}->can($method)) { $self->{xmms}-> }
That's when I realized, whoa, I have the method in a variable name, that's whack! Since perl often DWIMs, I thought I'd try
$self->{xmms}->$method;
but I don't know if that works or not, because I just got another error which is even more confusing: Global symbol "$AUTOLOAD" requires explicit package name at AlterniRATE/Player.pm line 75.
AlterniRATE/Player.pm had compilation errors.

Whoa ... I thought $AUTOLOAD was supposed to be a special magic variable! I guess I just totally don't know what I'm doing, and for the moment will return to the ugly brute-force method. Can anyone enlighten me as to how I can get AUTOLOAD to do what I want here?

Gee, I guess my oversight was pretty simple (I knew I was just being stupid) but I'm glad I asked, because the responses have all been very enlightening. Thanks, guys!

Replies are listed 'Best First'.
Re: AUTOLOAD question
by blokhead (Monsignor) on Nov 29, 2003 at 18:17 UTC
    The error isn't from the variable method call, but from using package variables under strict. You'll need to declare $AUTOLOAD with either use vars or our:
    use vars '$AUTOLOAD'; sub AUTOLOAD { my ($self) = @_; my ($method); ($method = $AUTOLOAD) =~ s/.*://; if ($self->{xmms}->can($method)) { $self->{xmms}->$method; } else { # whatever } }
    However, in your case, I would avoid using AUTOLOAD and recommend the following:
    { no strict 'refs'; for my $method (qw/is_paused is_playing play pause stop/) { *$method = sub { $_[0]->{xmms}->$method }; } }
    Here, you auto-generate only the methods you want. This way misspelled method calls won't get swallowed up by AUTOLOAD. (i.e, you get the appropriate error message from Perl itself, rather than needing AUTOLOAD to die appropriately).

    blokhead

Re: AUTOLOAD question
by jeffa (Bishop) on Nov 29, 2003 at 19:05 UTC
    And if you do still decide to use AUTOLOAD, be sure and "define" a dummy DESTROY method:
    sub DESTROY {}
    So that you don't get an extra AUTOLOAD "hit" when your object dies, such as with this code:
    package Foo; our $AUTOLOAD; sub new {bless {}, shift} sub AUTOLOAD { my $self = shift; my ($method) = $AUTOLOAD =~ /([^:]+)$/; # no need for s/// print "$method was called\n"; } package main; new Foo;

    jeffa

    L-LL-L--L-LL-L--L-LL-L--
    -R--R-RR-R--R-RR-R--R-RR
    B--B--B--B--B--B--B--B--
    H---H---H---H---H---H---
    (the triplet paradiddle with high-hat)
    
      And if you do still decide to use AUTOLOAD, be sure and "define" a dummy DESTROY method:

      Using NEXT::DESTROY if you think a super-class might have their own destroy method :-)

Re: AUTOLOAD question
by The Mad Hatter (Priest) on Nov 29, 2003 at 18:09 UTC
    Declare it with our (or use vars '$AUTOLOAD'... I think). So something like...
    sub AUTOLOAD { my $self = shift; our $AUTOLOAD; # I usually do... #$AUTOLOAD =~ s/(?:\w+::)+//; # ... but here's your pattern $AUTOLOAD =~ s/.*://; if ($self->{xmms}->can($AUTOLOAD)) { $self->{xmms}->$AUTOLOAD; } else { die "$AUTOLOAD not a valid method!"; } }
    Of course, you'll probably need to modify that and add some (different|better|more appropriate) error testing for invalid method calls.
      It's also good to note that can() returns a coderef, so you can just call it:
      &{$self->{xmms}->can($AUTOLOAD) || die "no $AUTOLOAD method"}($self->{ +xmms});
      or create a stub in your module, so each method only goes through AUTOLOAD the first time it is called:
      if (my $methodref = $self->{xmms}->can($AUTOLOAD)) { { no strict 'refs'; *$AUTOLOAD = sub { unshift @_, shift()->{xmms}; goto &$methodref + }; } $methodref->($self->{xmms}); }
      (Untested, but should give you the idea.)
Re: AUTOLOAD question
by bsb (Priest) on Dec 22, 2003 at 09:14 UTC
    I think you'd be better off with closures.

    See Schwern on Closures and Accessors

    # untested for my $method (qw(is_paused is_playing play pause)) { no strict 'refs'; *{__PACKAGE__."::$method"} = sub { $_[0]->{xmms}->$method }; }