Previous Page
Next Page

5.12. Slice Factoring

Factor large key or index lists out of their slices.

As the final example in the previous guideline demonstrates, slices can quickly become unwieldy as the number of indices/keys increases.

A more readable and more scalable approach in such cases is to factor out the index/key equivalences in a separate tabular data structure:

    Readonly my %CORRESPONDING => (
# Key of         Index of

      # %active...     @frames...
'top' => -1, 'prev' => -2, 'backup' => -3, 'emergency' => -4, 'spare' => -5, 'rainy day' => -6, 'alternate' => -7, 'default' => -8, ); @frames[ values %CORRESPONDING ] = @active{ keys %CORRESPONDING };

Each key in %CORRESPONDING is one of the keys of %active, and each value in %CORRESPONDING is the corresponding index of @frames. So the righthand side of the assignment (@active{ keys %CORRESPONDING }) is a hash slice of %active that includes all the entries whose keys are listed in %CORRESPONDING. Similarly, @frames[ values %CORRESPONDING ] is an array slice of @frames that includes all the corresponding indices listed in %CORRESPONDING. That means that the assignment copies entries from %active to the corresponding elements of @frames, with the correspondence being specified by the key/value pairs in %CORRESPONDING.

Storing that key/value correspondence in a hash works because the values and keys functions always traverse the entries of a hash in the same order, so the Nth value returned by values will always be the value of the Nth key returned by keys. Because the two builtins preserve the order of the entries of %CORRESPONDING, the assignment between the two slices copies $active{'top'} into $frames[-1], $active{'prev'} into $frames[-2], $active{'backup'} into $frames[-3], etc.

This approach improves the maintainability of the code, as the %CORRESPONDING hash very clearly and prominently lists the mapping from %active keys to @frames indices. The actual assignment statement is also made considerably simpler. In addition, factoring out the correspondence between keys and indices makes the code very much easier to maintain. Adding an extra assignment now only requires listing an extra key/index pair; changing a key or index only requires updating an existing pair.

And, of course, this technique is not restricted to negative indices, nor do the indices need to be specified in any particular order. If there are a large number of fields being transferred, it can be useful to arrange the keys alphabetically, to make them easier for humans to look up. For example:

    Readonly my %CORRESPONDING => (
        age        => 1,
        comments   => 6,
        fraction   => 8,
        hair       => 9,
        height     => 2,
        name       => 0,
        occupation => 5,
        office     => 11,
        shoe_size  => 4,
        started    => 7,
        title      => 10,
        weight     => 3,

    @staff_member_details[ values %CORRESPONDING ]
        = @personnel_record{ keys %CORRESPONDING };

Simple arrays can also be useful when refactoring the keys or indices of a single slice:

# This is the order in which stat( ) returns its information:
Readonly my @STAT_FIELDS => qw( dev ino mode nlink uid gid rdev size atime mtime ctime blksize blocks ); sub status_for { my ($file) = @_;
# The hash to be returned...
my %stat_hash = ( file => $file );
# Load each stat datum into an appropriately named entry of the hash...
@stat_hash{@STAT_FIELDS} = stat $file; return \%stat_hash; }
# and later...
warn 'File was last modified at ', status_for($file)->{mtime};

This kind of table-driven programming is highly scalable and particularly easy to maintain. Numerous variations on this technique will be advocated in subsequent chapters.

    Previous Page
    Next Page