Item 2: Avoid using a slice when you want an element

' slice when you want an element."-->

Item 2: Avoid using a slice when you want an element.

Is @a[1] an array element? Or an array slice?

It's a slice .

One of the counterintuitive things encountered by people just beginning to learn Perl is the difference between array elements and array slices. Even after you know the difference, it's not hard to type @ instead of $ .

An introductory book or course about Perl will typically begin by telling you that scalar variable names begin with $ , and array variable names begin with @ . This is, of course, an oversimplification, which is corrected in the next step of the introduction, where you learn that to access element $n of array @a , you use the syntax $a[$n] , not @a[$n] . This may seem peculiar. However, it is a consistent syntax. Scalar values , not variables , begin with $ , even when those values come from an array or hash.

Therefore, @a[$n] doesn't mean element $n of array @a . Rather, it is something different, called a slice. A slice is a shortcut way of writing a list of elements:

 @giant = qw(fee fie foe fum); 

@giant is ('fee', 'fie', 'foe', 'fum') .

 @queue = ($giant[1], $giant[2]);  @queue = @giant[1, 2]; 

@queue is ('fie', 'foe') .

Same thing, using a slice.

 @fifo = (1, 2);  @queue = @giant[@fifo]; 

Same thing again, using a list of values in an array.

A slice has all the characteristics of a list of variable names. You can even use it on the left-hand side of an assignment expression, or in other places where an lvalue is required:

 ($giant[1], $giant[2]) =    ("tweedle", "dee"); 

@giant is ('fee', 'tweedle', 'dee', 'fum') .

 @giant[1, 2] = ("tweedle", "dee"); 

Same thing, assigning to a slice.

Now, @a[1] is as much a slice as are @a[1, 2] , @a[2, 10] , @a[5, 3, 1] , @a[3..7] , and so on. @a[1] is a list , not a scalar value. It is a list of one element.

These single-element slices are something you should watch out for. They are dangerous critters if not used properly. A slice used in a scalar context returns the last value in the slice, which makes single-element slices work like scalar values, in some cases . For example:

 $jolly = @giant[3]; 

$jolly = 'fum' , but for the wrong reason.

Probably what was intended here was $jolly = $giant[3] . The single-element slice @giant[3] is still OK, sort of, since @giant[3] in a scalar context evaluates to its last (and in this case only) element, $giant[3] .

Although single-element slices work somewhat like array elements on the right side of assignments, they behave very differently on the left-hand side of assignments. Because a single-element slice is a list, an assignment to a single-element slice is a list assignment, and thus the right-hand side of the assignment is evaluated in a list context . Unintentionally evaluating an operator in a list context can produce dramatic (and unfortunate) results. A good example is the line input operator, < filehandle > :

Don't use a single-element slice as the left-hand side of an assignment.

What was intended was $ info [0] = <STDIN> .

 @info[0] = <STDIN>; 

OOPS! <STDIN> in a list context!

 ($info[0]) = <STDIN>; 

Same problem w/o slice.

This reads all the lines from standard input, assigns the first one to element of @info , and ignores the rest! Assigning <STDIN> to @info[3] eval-uates <STDIN> in a list context. In a list context, <STDIN> reads all the lines from standard input and returns them as a list.

One more difference between slices and elements is that the expression in the brackets of an element access is evaluated in a scalar context, whereas for slices it is evaluated in a list context. This leads to another example of bizarre behavior that is more difficult to explain:

Don't confuse slices and elements.

Suppose you want to add an additional line containing 'EOF' to the end of the array @text . You could write this as $text[@text] = 'EOF' . But don't write @text[@text] instead.

 chomp (@text = <STDIN>); 

Read lines into @text . So far, so good.

 @text[@text] = 'EOF'; 

Seriously wrong! See below.

The array @text inside the brackets above is interpreted in a list context. In a scalar context it returns the number of elements in @text , but in a list context it returns the contents of the array itself. The result is a slice with as many elements as there are lines.

The contents of the lines are interpreted as integer indicesif they're text they will likely all turn out to be zero, so the slice will look like @text[0, 0, 0, 0, 0, ...] . Then 'EOF' is assigned to the first element of the slice, and undef to all the rest, which means that this will probably just overwrite the first element of @text with undef , leaving everything else alone.

What a mess!

Get in the habit of looking for single-element slices like @a[0] in your programs. Single-element slices are generally not what you want (though they're handy for tricks now and then), and a single-element slice on the left-hand side of an assignment is almost certainly wrong. The -w command line option (see Item 36) will flag many suspect uses of slices.

Slicing for fun and profit

Beginning Perl programmers generally do not (intentionally) use slices, except to select elements from a result:

 ($uid, $gid) = (stat $file)[4, 5]; 

Get user and group id from result of stat $file .

 $last = (sort @list)[-1]; 

Find the element of @list that comes last in ASCII order (inefficient for long @list ).

 $field_two = (split /:/)[1]; 

Get the second element from the result of splitting $_ on : .

However, slices can be put to some pretty interesting (and weird) uses. For example:

 @list[5..9] = reverse @list[5..9]; 

Reverse elements 5 through 9 of @list .

 @list[reverse 5..9] = @list[5..9]; 

There's more than one way to do it.

They make a handy way to swap two elements:

 @a[$n, $m] = @a[$m, $n]; 

Swap $a[$m] and $a[$n] .

 @item{'old', 'new'} =    @item{'new', 'old'}; 

Swap $item{old} and $item{new} .

Slices are also used in sorting (see Item 14):

Use slices to reorder arrays.

Given two parallel arrays @uid and @ name , this example sorts @name according to the numerical contents of @uid .

 @name = @name[    sort {$uid[$a] <=> $uid[$b]} 0..$#name  ]; 

Sort indices 0..$#name according to @uid , then use the result to reorder @name .

You can use hash slices to create hashes from two lists, to overlay the contents of one hash onto another, and to "subtract" one hash from another:

Use slices to create and manipulate hashes.

 @char_num{'A'..'Z'} = 1..26; 

Create a hash from the keys ' A'..'Z' and values 1..26 .

 @old{keys %new} = values %new; 

Overlay the contents of %new on %old .

 %old = (%old, %new); 

Another (probably less efficient) way of writing the above.

 delete @name{keys %invalid}; 

"Subtract" elements in %invalid from %name .

 foreach $key (keys %invalid) {    delete $name{$key};  } 

Another more verbose way of writing the above.



Effective Perl Programming. Writing Better Programs with Perl
Effective Perl Programming: Writing Better Programs with Perl
ISBN: 0201419750
EAN: 2147483647
Year: 1996
Pages: 116

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net