Flylib.com

Books Software

 
 
 

Section 2.5. Keys and Indices


2.5. Keys and Indices

Separate complex keys or indices from their surrounding brackets .

When accessing elements of nested data structures (hashes of hashes of arrays of whatever), it's easy to produce a long, complex, and visually dense expression, such as:

$candidates[$i] = $incumbent{$candidates[$i]{get_region( )}};

That's especially true when one or more of the indices are themselves indexed variables . Squashing everything together without any spacing doesn't help the readability of such expressions. In particular, it can be difficult to detect whether a given pair of brackets is part of the inner or outer index.

Unless an index is a simple constant or scalar variable, it's much clearer to put spaces between the indexing expression and its surrounding brackets:


$candidates[$i] = $incumbent{ $candidates[$i]{ get_region( ) } };


Note that the determining factors here are both the complexity and the overall length of the index. Occasionally, "spacing-out" an index makes sense even if that index is just a single constant or scalar. For example, if that simple index is unusually long, it's better written as:


print $incumbent{ $largest_gerrymandered_constituency };


rather than:

print $incumbent{$largest_gerrymandered_constituency};


2.6. Operators

Use whitespace to help binary operators stand out from their operands .

Long expressions can be hard enough to comprehend without adding to their complexity by jamming their various components together:

my $displacement=$initial_velocity*$time+0.5*$acceleration*$time**2;

    my $price=$coupon_paid*$exp_rate+(($face_val+$coupon_val)*$exp_rate**2);

Give your binary operators room to breathe, even if it requires an extra line to do so:


my $displacement
        = $initial_velocity * $time  +  0.5 * $acceleration * $time**2;

    my $price
        = $coupon_paid * $exp_rate  +  ($face_val + $coupon_paid) * $exp_rate**2;


Choose the amount of whitespace according to the precedence of the operators, to help the reader's eyes pick out the natural groupings within the expression. For example, you might put additional spaces on either side of the lower-precedence + to visually reinforce the higher precedence of the two multiplicative subexpressions surrounding it. On the other hand, it's quite appropriate to sandwich the ** operator tightly between its operands, given its very high precedence and its longer, more easily identified symbol.

A single space is always sufficient whenever you're also using parentheses to emphasize (or to vary) precedence:


my $velocity
        = $initial_velocity + ($acceleration * ($time + $delta_time));

    my $future_price
        = $current_price * exp($rate - $dividend_rate_on_index) * ($delivery - $now);


Symbolic unary operators should always be kept with their operands:


my $spring_force = !$hyperextended ? -$spring_constant * $extension : 0;

    my $payoff = max(0, -$asset_price_at_maturity + $strike_price);


Named unary operators should be treated like builtins, and spaced from their operands appropriately:


my $tan_theta = sin $theta / cos $theta;

    my $forward_differential_1_year = $delivery_price * exp -$interest_rate;



2.7. Semicolons

Place a semicolon after every statement .

In Perl, semicolons are statement separators, not statement terminators, so a semicolon isn't required after the very last statement in a block. Put one in anyway, even if there's only one statement in the block:


while (my $line = <>) {
        chomp $line;

        if ( $line =~ s{\A (\s*) -- (.*)}{#}xms ) {
            push @comments, ;
        }

        print $line;
    }


The extra effort to do this is negligible, and that final semicolon confers two very important advantages. It signals to the reader that the preceding statement is finished, and (perhaps more importantly) it signals to the compiler that the statement is finished. Telling the compiler is more important than telling the reader, because the reader can often work out what you really meant , whereas the compiler reads only what you actually wrote.

Leaving out the final semicolon usually works fine when the code is first written (i.e., when you're still paying proper attention to the entire piece of code):

while (my $line = <>) {
        chomp $line;

        if ( $line =~ s{\A (\s*) -- (.*)}{#}xms ) {
            push @comments, 
        }

        print $line
    }

But, without the semicolons, there's nothing to prevent later additions to the code from causing subtle problems:

while (my $line = <>) {
        chomp $line;

        if ( $line =~ s{\A (\s*) -- (.*)}{#}xms ) {
            push @comments, 
            /shift/mix
        }

        print $line
        $src_len += length;
    }

The problem is that those two additions don't actually add new statements; they just absorb the existing ones. So the previous code actually means:

while (my $line = <>) {
        chomp $line;

        if ( $line =~ s{\A (\s*) -- (.*)}{#}xms ) {
            push @comments,  / shift() / mix( )
        }

        print $line ($src_len += length);
    }

This is a very common mistake, and an understandable one. When extending existing code, you will naturally focus on the new statements you're adding, on the assumption that the existing ones will continue to work correctly. But, without its terminating semicolon, an existing statement may be assimilated into the new one instead.

Note that this rule does not apply to the block of a map or grep if that block consists of only a single statement. In that case, it's better to omit the terminator:


my @sqrt_results
        = map { sqrt $_ } @results;


because putting a semicolon in the block makes it much more difficult to detect where the full statement ends:

my @sqrt_results
        = map { sqrt $_; } @results;

Note that this exception to the stated rule is not excessively error-prone , as having more than one statement in a map or grep is relatively unusual, and often a sign that a map or grep was not the right choice in the first place (see "Complex Mappings" in Chapter 6).