Section 5.7. Autovivification


5.7. Autovivification

Let's look again at the provisions list. Suppose we were reading the data from a file, in this format:

 The Skipper   blue_shirt   hat   jacket   preserver   sunscreen Professor   sunscreen   water_bottle   slide_rule Gilligan   red_shirt   hat   lucky_socks   water_bottle 

We indent provisions with some whitespace, following a non-indented line with the person's name. Let's construct a hash of provisions. The keys of the hash will be the person's name, and the value will be an array reference to an array containing a list of provisions.

Initially, we might gather the data using a simple loop:

 my %provisions; my $person; while (<>) {   if (/^(\S.*)/) { # a person's name (no leading whitespace)     $person = $1;     $provisions{$person} = [  ] unless exists $provisions{$person};   } elsif (/^\s+(\S.*)/) { # a provision     die 'No person yet!' unless defined $person;     push @{ $provisions{$person} }, $1;   } else {     die "I don't understand: $_";   } } 

First, we declare the variables for the resulting hash of provisions and the current person. For each line that we read, we determine if it's a person or a provision. If it's a person, we remember the name and create the hash element for that person. The unless exists test ensures that we won't delete someone's provision list if his list is split in two places in the datafile.

For example, suppose that "The Skipper" and "sextant" (note the leading whitespace) are at the end of the datafile in order to list an additional data item.

The key is the person's name, and the value is initially a reference to an empty anonymous array. If the line is a provision, push it to the end of the correct array, using the array reference.

This code works fine, but it actually says more than it needs to. Why? Because we can leave out the line that initializes the hash element's value to a reference to an empty array:

 my %provisions; my $person; while (<>) {   if (/^(\S.*)/) { # a person's name (no leading whitespace)     $person = $1;     ## $provisions{$person} = [  ] unless exists $provisions{$person};   } elsif (/^\s+(\S.*)/) { # a provision     die 'No person yet!' unless defined $person;     push @{ $provisions{$person} }, $1;   } else {     die "I don't understand: $_";   } } 

What happens when we try to store that blue shirt for the Skipper? While looking at the second line of input, we'll end up with this effect:

 push @{ $provisions{'The Skipper'} }, "blue_shirt"; 

At this point, $provisions{"The Skipper"} doesn't exist, but we're trying to use it as an array reference. To resolve the situation, Perl automatically inserts a reference to a new empty anonymous array into the variable and continues the operation. In this case, the reference to the newly created empty array is dereferenced, and we push the blue shirt to the provisions list.

This process is called autovivification. Any nonexistent variable, or a variable containing undef, which we dereference while looking for a variable location (technically called an lvalue context), is automatically stuffed with the appropriate reference to an empty item, and Perl allows the operation to proceed.

This is actually the same behavior we've probably been using in Perl all along. Perl creates new variables as needed. Before that statement, $provisions{"The Skipper"} didn't exist, so Perl created it. Then @{ $provisions{"The Skipper"} } didn't exist, so Perl created it as well.

For example, this works:

 my $not_yet;                # new undefined variable @$not_yet = (1, 2, 3); 

Here, we dereference the value $not_yet as if it were an array reference. But since it's initially undef, Perl acts as if we had said:

 my $not_yet; $not_yet = [  ]; # inserted through autovivification @$not_yet = (1, 2, 3); 

In other words, an initially empty array becomes an array of three elements.

This autovivification also works for multiple levels of assignment:

 my $top; $top->[2]->[4] = 'lee-lou'; 

Initially, $top contains undef, but because we dereference it as if it were an array reference, Perl inserts a reference to an empty anonymous array into $top. Perl then accesses the third element (index value 2), which causes Perl to grow the array to be three elements long. That element is also undef, so Perl stuffs it with a reference to another empty anonymous array. We then spin out along that newly created array, setting the fifth element to lee-lou.




Intermediate Perl
Intermediate Perl
ISBN: 0596102062
EAN: 2147483647
Year: N/A
Pages: 238

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