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

I'm a newbie trying out the AUTOLOAD subroutine.. I can't figure out why the say statement does not work when AUTOLOAD is invoked? Appreciate any help

{ package MyDate; sub AUTOLOAD { our $AUTOLOAD; say "Going into AUTOLOAD"; # Why doesn't this print? my $method = $AUTOLOAD; $method =~ s/.*:://; grep {$method eq $_} qw/yak day month/ or Carp::croak "Invalid + method: $method."; if ($method eq "day") { eval { sub day {(localtime)[3];} }; } elsif ($method eq "month") { eval { sub month {(localtime)[4] + 1;} }; } Carp::croak $@ if $@; goto &$method; } } ###################################################################### +################## say MyDate->day; say MyDate->month;

The output is just

25 12

Replies are listed 'Best First'.
Re: say statements within 'sub AUTOLOAD'
by Anonymous Monk on Dec 25, 2010 at 10:38 UTC
    You need to use strictures, which enable messages telling you what's wrong: use strict; use warnings;. You need to import say, eg with use feature 'say';. Also, you didn't assign the coderef to $method, so there's nothing to go to with goto. And since you're (wisely) not doing string eval, you don't need the eval block (but if you do need it for compatibility with other code, check that $method has been assigned a coderef before trying to execute it). And you need to use Carp; if you're going to put it in your code (but the way it's used, die() would be adequate).

    See the following:

    #! /usr/bin/env perl package Foo; use strict; use warnings; use feature 'say'; sub AUTOLOAD { (my $method = our $AUTOLOAD) =~ s/.*:://; return if $method eq 'DESTROY'; say "Foo::$method was called"; die "bad method $method\n" if $method ~~ [qw/troz narf/]; if ($method eq 'day') { return +(localtime)[3]; } elsif ($method eq 'month') { return +(localtime)[4] + 1; } } package main; use strict; use warnings; use feature 'say'; say( Foo->day ); say( Foo->troz ); __END__ Foo::day was called 25 Foo::troz was called bad method troz

      Hi, and many thanks (with a belated Merry Xmas) for the reply. Wish to address it in point form:

      1. Using strictures: I did use those strictures (written outside the braces for the package MyDate). My bad for not reproducing it.. So I don't think its about any error being generated because i didn't declare those strictures. Using print rather than say didn't make any difference either.

      2. Coderefs: I'm not quite sure what you mean by me not assigning a coderef? I defined the subroutines within my if statements.. so if $method was 'day' wouldn't &$method be calling &day? I did get the desired outputs from the subroutines.. so it seems to me like that wasn't a problem either.

      3. Defining subs: I was learning AUTOLOAD (from Intermediate Perl) in the context of dynamically defining new subroutines, while your code only returns the desired value. I presume that means that if I write

      say( Foo->day ); say( Foo->month ); say( Foo->day );

      I would expect the output

      Foo::day was called 26 Foo::month was called 12 26

      rather than the actual output

      Foo::day was called 26 Foo::month was called 12 Foo::day was called 26

      Actually I get the impression that AUTOLOAD isn't even being called. Changing the words in my grep statement or writing if ( $method eq "dayz") {...} doesn't produce any errors while changing the name of my subroutine does. So erm...:S why?

        ianyappy:

        One thing you have to understand about the statement  sub name { ... } is that it always executes at compile time if it is not in a string eval (i.e., is in a block  eval or is a nested subroutine definition in another subroutine). So your impression that "AUTOLOAD isn't even being called" is correct. See example below.

        >perl -wMstrict -le "{ package Foo; use warnings; use strict; our $AUTOLOAD; sub AUTOLOAD { eval { sub foo { return 'FOO'; } }; sub bar { return 'BAR'; } print qq{AUTOLOADing '$AUTOLOAD'}; no strict 'refs'; *$AUTOLOAD = sub { return uc $AUTOLOAD }; goto &$AUTOLOAD; } } ;; print 'foo? ', exists &Foo::foo; print Foo->foo; print 'bar? ', exists &Foo::bar; print Foo->bar; print 'baz? ', exists &Foo::baz; print Foo->baz; print 'baz? ', exists &Foo::baz; print Foo->baz; " foo? 1 FOO bar? 1 BAR baz? AUTOLOADing 'Foo::baz' FOO::BAZ baz? 1 FOO::BAZ

        In this example,  Foo::foo and  Foo::bar already exist at runtime, so  AUTOLOAD is never called for them. The first time  Foo::baz is called,  AUTOLOAD is called and defines the subroutine and also executes the just-defined subroutine. When  Foo::baz is called subsequently, it exists.

        Update: To pound the point about nested subroutine definitions even further into the ground, remove the  sub bar { return 'BAR'; } statement from the  AUTOLOAD function in the example above and replace the  eval statement with
            eval { sub foo { sub bar { return 'BAR'; }  return 'FOO'; } };
        or the even more pathological
            eval { sub foo { return 'FOO';  sub bar { return 'BAR'; } } };
        The results of execution are the same.

        1: When I only add strictures, as in the following code: I get the following:
        String found where operator expected at 879141.pl line 6, near "say "G +oing into AUTOLOAD"" (Do you need to predeclare say?) String found where operator expected at 879141.pl line 9, near "Carp:: +croak "Invalid method: $method."" (Do you need to predeclare Carp::croak?) syntax error at 879141.pl line 6, near "say "Going into AUTOLOAD"" syntax error at 879141.pl line 9, near "Carp::croak "Invalid method: $ +method."" BEGIN not safe after errors--compilation aborted at 879141.pl line 26.

        The first error tells me that say needs to be imported, the second that Carp isn't loaded. If nothing was printed for you, then that package didn't have warnings enabled.

        2: Your code looked like this: if (foo) { eval { sub bar {} } }. You were defining a sub inside the AUTOLOAD sub, and then executing that code by name, instead of assigning to another variable a coderef, like $code = sub { ... }; and then later calling it or using goto on it.

        3: Notice that the "was called" statement is executed unconditionally, after it's determined that DESTROY wasn't called.

        Actually I get the impression that AUTOLOAD isn't even being called.
        It looks like you're trying to juggle the differences between the code I provided and the code your provided, and then trying to map them to the actual code you're running. This isn't working.