Common problems with slices

[ Perl tips index ]
[ Subscribe to Perl tips ]

In the last tip we covered array and hash slices. These allow us to access multiple elements from an array or a hash in a single statement, providing a lot of power in a single statement.

It's also possible to use slices incorrectly. Many people make the following mistake when first learning Perl:

        print @array[3];
        # or
        @array[3] += 4;

Fortunately (as long as we're using warnings) Perl catches these errors and tells us:

        Scalar value @array[3] better written as $array[3]

This is because @array[3] is a slice of an array containing a single element. In many cases this mistake won't cause us any problems, but when problems do occur they can be very difficult to debug.

Consider the following code:

        my @animals = qw(cat dog chicken duck rabbit);
        my $pet = @animals[3];

When we use a slice in a scalar context, as we are doing above, the list evaluates to the final value. Thus @animals[3] and $animals[3] both return duck. Note that @animals[0..3] would also return duck. This is one case where our mistake is unlikely to be a problem.

As discussed in the previous tip, we can assign lists of values to slices:

        @animals[3,5,6] = qw( duckling swan mouse );

Slices on the left hand side of an expression always evaluate in a list context. What do you suppose happens in the following expression?

        @data[1] = <STDIN>;             # Almost certainly a mistake

In this case all of the lines from STDIN are read in (because we're in a list context) but only the very first is stored in $data[1]. All other lines are discarded. Had we instead written:

        $data[1] = <STDIN>;

only the next line of data would be read and it would be stored in $data[1] which is the expected behaviour.

One final place where using slices instead of array look ups can surprise you is with the indices themselves. When you look up an array element any expression between the square brackets is interpreted in a scalar context. In the following example, each of the array assignments would add f to the next (5th) array position.

        my @array = ( 'a' .. 'e' );
        my $index = 5;
        # These are all equivalent
        $array[5] = 'f';
        @array[5] = 'f';                # A mistake (generates warning)
        $array[$index] = 'f';
        @array[$index] = 'f';           # A mistake (generates warning)
        
        # The following evaluates @array as a scalar, providing the
        # number of elements in the array (5).
        $array[@array] = 'f';

However, the following code would not add f to the end of the array:

        my @array = ( 'a' .. 'e' );
        @array[@array] = 'f';           # Does not generate a warning!

In this case, the expression between the square brackets is interpreted in a list context. This would then be the equivalent of writing:

        @array['a', 'b', 'c'. 'd', 'e'] = 'f';

Since these values are not numbers, Perl will coerce them to zero giving us:

        @array[0, 0, 0, 0, 0] = 'f';
        
Thus the letter C<f> will be assigned to the zeroth position rather than to
the 5th position!

To avoid these problems and other problems like these, make sure you use warnings and also look very carefully at your array look ups. The rule is simple, use $ when looking up a scalar value, and @ when looking up a list of values.

Single element slices like @array[3] are almost always a mistake particularly on the left-hand side of an assignment.

[ Perl tips index ]
[ Subscribe to Perl tips ]


This Perl tip and associated text is copyright Perl Training Australia. You may freely distribute this text so long as it is distributed in full with this Copyright noticed attached.

If you have any questions please don't hesitate to contact us:

Email: contact@perltraining.com.au
Phone: 03 9354 6001 (Australia)
International: +61 3 9354 6001

Valid XHTML 1.0 Valid CSS