views:

340

answers:

3

In "Perl Best Practices" the very first line in the section on AUTOLOAD is:

Don't use AUTOLOAD

However all the cases he describes are dealing with OO or Modules.

I have a stand alone script in which some command line switches control which versions of particular functions get defined. Now I know I could just take the conditionals and the evals and stick them naked at the top of my file before everything else, but I find it convenient and cleaner to put them in AUTOLOAD at the end of the file.

Is this bad practice / style? If you think so why, and is there a another way to do it?

As per brian's request

I'm basically using this to do conditional compilation based on command line switches.

I don't mind some constructive criticism.

sub AUTOLOAD {
    our $AUTOLOAD;

    (my $method = $AUTOLOAD) =~ s/.*:://s; # remove package name
    if ($method eq 'tcpdump' && $tcpdump) {
        eval q(
        sub tcpdump {
            my $msg = shift;
            warn gf_time()." Thread ".threads->tid().": $msg\n";
        }
        );
    } elsif ($method eq 'loginfo' && $debug) {
        eval q(
        sub loginfo {
            my $msg = shift;
            $msg =~ s/$CRLF/\n/g;
            print gf_time()." Thread ".threads->tid().": $msg\n";
        }
        );
    } elsif ($method eq 'build_get') {
        if ($pipelining) {
            eval q(
            sub build_get {
                my $url = shift;
                my $base = shift;
                $url = "http://".$url unless $url =~ /^http/;
                return "GET $url HTTP/1.1${CRLF}Host: $base$CRLF$CRLF";
            }    
            );
        } else {
            eval q( 
            sub build_get {
                my $url = shift;
                my $base = shift;
                $url = "http://".$url unless $url =~ /^http/;
                return "GET $url HTTP/1.1${CRLF}Host: $base${CRLF}Connection: close$CRLF$CRLF";
            }    
            );
        }    
    } elsif ($method eq 'grow') {
        eval q{ require Convert::Scalar qw(grow); };
        if ($@) {
            eval q( sub grow {} );
        }
        goto &$method;
    } else {
        eval "sub $method {}";
        return;
    }
    die $@ if $@;
    goto &$method;
}
+3  A: 

I think for a stand alone script this approach is OK. You can create subroutines on the fly to speed up subsequent calls, e.g.:

sub AUTOLOAD {
    (my $name = our $AUTOLOAD) =~ s/.*:://;
    no strict 'refs';  # allow symbolic references

    *$AUTOLOAD = sub { print "$name subroutine called\n" };    
    goto &$AUTOLOAD;   # jump to the new sub
}

Autoloading is tricky when producing inheritance trees.

eugene y
+6  A: 

An alternative strategy would be to write the script as an App::* module and have the commandline option choose which class to load to provide whatever functionality it is that's pluggable depending on the option. You would require that class just-in-time once you know which it is. It's a little more up-front work, but if you intend to maintain the script for a long time, I bet it would pay off. The past couple years have seen the creation of some extra-nice tools for creating scripts whose functionality really lives in modules, including App::Cmd, MooseX::Getopt, and the bastard offspring of both.

hobbs
Sounds interesting and worth checking out, although I think it might be overkill for what I'm doing. It's only about a 500 line script.
Robert S. Barnes
500 is arguably about 450 too many. :)
hobbs
+1  A: 

If your only reason for using AUTOLOAD is to relocate the block to the end, why not put it at the end in a subroutine, and then call it as soon as its dependent variables are defined?

sub tcpdump;  # declare your subs if you want to call without parens

# define the parameters

compile();

# code that uses new subs

sub compile {
    *tcpdump = $tcpdump ? sub {
        my $msg = shift;
        warn gf_time()." Thread ".threads->tid().": $msg\n";
    } : sub {};
    # ...
}
# EOF

Better still, if the globals aren't needed elsewhere, just pass the values to compile as arguments.

Eric Strom
@Eric Strom: So you feel this is better style and cleaner than the `AUTOLOAD`? It seem to be practically identical to what I'm doing with `AUTOLOAD`, except I have to call `compile` explicitly at the beginning of the program. Why is this better than implicitly calling `AUTOLOAD` the first time a function is called? Very interesting solution as I hadn't seen the `*name = sub {...}` syntax before.
Robert S. Barnes
@Robert S. Barnes => `AUTOLOAD` carries with it a bit of baggage: the complications with inheritance, speed, requiring the subs to be called as methods. Those are all necessary evils if you are doing something that truly needs `AUTOLOAD`, but if not, it could just be a source of bugs. Best to do the simplest solution that works. Separately, using the anon sub syntax rather than string eval allows perl to catch errors at compile time rather than runtime.
Eric Strom