When should I use Perl's AUTOLOAD?

2020-04-02 18:07发布

问题:

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;
}

回答1:

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.



回答2:

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.



回答3:

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.