Perl Best Practices [Electronic resources]

Damian Conway

نسخه متنی -صفحه : 317/ 87
نمايش فراداده

6.6. Unnecessary Subscripting

Avoid subscripting arrays or hashes within loops .

Unless you actually need to know the indices of the array elements you're processing, iterate the values of an array directly:

for my $client (@clients) { $client->tally_hours( ); $client->bill_hours( ); $client->reset_hours( ); }

Iterating the indices and then doing repeated array accesses is significantly slower, and less readable:

for my $n (0..$#clients) {
$clients[$n]->tally_hours(  );
$clients[$n]->bill_hours(  );
$clients[$n]->reset_hours(  );
}

Repeated indexing is repeated computation; duplicated effort that incurs an extra cost but provides no added benefit. Iterating indices is also prone to off-by-one errors. For example:

for my $n (1..@clients) {
$clients[$n]->tally_hours(  );
$clients[$n]->bill_hours(  );
$clients[$n]->reset_hours(  );
}

Likewise, if you're processing the entries of a hash and you need only the values of those entries, don't iterate the keys and then look up the values repeatedly:

for my $original_word (keys %translation_for) {
if ( $translation_for{$original_word} =~ m/ $PROFANITY /xms) {
$translation_for{$original_word} = '[DELETED]';
}
}

Repeated hash look-ups are even more costly than repeated array indexing. Just iterate the hash values directly:

for my $translated_word (values %translation_for) { if ( $translated_word =~ m/ $PROFANITY /xms) { $translated_word = '[DELETED]'; } }

Note that this last example works correctly because, in Perl 5.6 and later, the values function returns a list of aliases to the actual values of the hash, rather than just a list of copies (see "Hash Values" in Chapter 8). So if you change the iterator variable (for example, assigning '[DELETED]' to $translated_word), you're actually changing the corresponding original value inside the hash.

The only situation where iterating values doesn't work correctly is when you need to delete the entries from the hash:

for my $translated_word (values %translation_for) {
if ( $translated_word =~ m/ $PROFANITY /xms) {
delete $translated_word;                    # Compile-time error
}
}

Here, aliasing isn't enough, because the delete builtin needs to know the key as well, so it will only accept actual hash look-ups as arguments. The correct solution is to use a hash slice instead (see Chapter 5):

my @unacceptable_words = grep {$translation_for{$_} =~ m/ $PROFANITY /xms} keys %translation_for; delete @translation_for{@unacceptable_words};

The grep collects all those keys whose values must be removed, and stores that list in @unacceptable_words. The list of keys is then used to create a slice of the original hash (i.e., a list of hash look-ups), which

can be passed to delete.

By Any Other Name . . .

An alias may sometimes seem like magic, but it's based in a very simple idea.

In a Perl program, a normal variable consists of two distinct components: a storage location in memory, and a name (such as $foo) by which the program refers to that storage location. In other words, every variable is a box, with a "Hi-my-name-is..." sticker on it.

Aliasing is the process of putting a second (or third, or

n th) "Hi-my-name-is-also..." sticker on a single box. Perl subroutines do this all the time. For example, if you call get_passwd($user), then inside the call to get_passwd( ) the name $_[0] is temporarily attached to the container whose original name is $user. That container now has two names: one that's used inside the subroutine and one that's used outside.

Anything you do to an alias (e.g., get its value, increment it, print it, assign a new value to it) is really being done to the original variablebecause there's really only

one variable, no matter how many separate names you give it.