Previous Page
Next Page

6.7. Necessary Subscripting

Never subscript more than once in a loop.

Sometimes you have no choice: you really do need to know the index of each value you're iterating over, as well as the value itself. But, even when it is necessary to iterate indices or keys, be sure to extract the value only once:


    for my $agent_num (0..$#operatives) {                        
# Iterate indices
my $agent = $operatives[$agent_num];
# Extract value once
print "Checking agent $agent_num\n";
# Use index
if ($on_disavowed_list{$agent}) {
# Use value
print "\t...$agent disavowed!\n";
# Use value again
} }

Never extract it repeatedly in the same iteration:

    for my $agent_num (0..$#operatives) {                        # Iterate indices
        print "Checking agent $agent_num\n";                     # Use index
        if ($on_disavowed_list{$operatives[$agent_num]}) {       # Extract value
            print "\t...$operatives[$agent_num] disavowed!\n";   # Extract value again
        }
    }

Apart from the fact that repeated array look-ups are repeatedly expensive, they also clutter the code, and increase the maintenance effort if either the array name or the name of the iterator variable subsequently has to be changed.

Occasionally a mere copy of the value won't do, because you need to iterate both indices and values, and still be able to modify the values. It's easy to do that toojust use the Data::Alias CPAN module[*]:

[*] Or the Lexical::Alias module if you're running under a version of Perl earlier than 5.8.1.


    use Data::Alias;

    for my $agent_num (0..$#operatives) {           
# Iterate indices
alias my $agent = $operatives[$agent_num];
# Rename value
print "Checking agent $agent_num\n";
# Use index
if ($on_disavowed_list{$agent}) {
# Use value
$agent = '[DISAVOWED]';
# Change value
} }

The technique here is the same as before: iterate indices, then look up the value once. But in this case, instead of copying the value, the loop block creates a lexically scoped alias to it.

The alias function takes a variable as its only argument, and converts that variable into an alias variable. Assigning directly to the alias variable as you declare it:


    alias my $agent                     
# Alias variable
= $operatives[$agent_num];
# Assigned expression

causes the alias variable to become another name for the assigned expression (usually another variable).

Once the aliasing is accomplished, anything that is done to the alias variable (including subsequent assignments) is actually being done to the variable to which it was aliased. Thus assigning to $agent actually assigns to $operatives[$agent_num]. The only difference is that there's no array element look-up involved, so accessing the alias is actually faster.

Exactly the same approach can be taken when it's necessary to iterate the keys and values of a hash:


    for my $name (keys %client_named) {                             
# Iterate keys
alias my $client_info = $client_named{$name};
# Rename value
print "Checking client $name\n";
# Use key
if ($client_info->inactivity( ) > $ONE_YEAR) {
# Use value
$client_info
# Change value...
= Client::Moribund->new({ from => $client_info });
# ...using value
} }

Note that you can't use Perl's built-in each function for this kind of thing:

    while (my ($name, $client_info) = each %client_named) {         # Iterate key/value
        print "Checking client $name\n";                            # Use key
        if ($client_info->inactivity(  ) > $ONE_YEAR) {                  # Use value
            $client_info                                            # Change copy (!)
                = Client::Moribund->new({ from => $client_info });  # ...using value
        }
    }

If you use each, the $client_info variable receives only a copy of each hash value, not an alias to it. So changing $client_info has no effect on the original values inside %client_named.

Extracting or renaming values is also a good practice if those values are nested inside a deeper data structure, or are the result of a subroutine call. For example, if the previous code needed to choose different responses for clients with different periods of inactivity, it would be cleaner and more efficient to extract that information only once:


    for my $name (keys %client_named) {                    
# Iterate keys
alias my $client_info = $client_named{$name};
# Rename value
my $inactive = $client_info->inactivity( );
# Extract value once
print "Checking client $name\n";
# Use key
$client_info
# Reuse value many times...   To decide new status of client...
= $inactive > $ONE_YEAR ? Client::Moribund->new({ from => $client_info }) : $inactive > $SIX_MONTHS ? Client::Silver->new({ from => $client_info }) : $inactive > $SIX_WEEKS ? Client::Gold->new({ from => $client_info }) : Client::Platinum->new({ from => $client_info }) ; }

    Previous Page
    Next Page