[ Team LiB ] Previous Section Next Section

4.7 Autovivification

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

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

Provisions are indented with some whitespace, following a nonindented 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, you 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, you declare the variables for the resulting hash of provisions and the current person. For each line that is read, determine if it's a person or a provision. If it's a person, remember the name and create the hash element for that person. The unless exists test ensures that you won't delete someone's provision list if his list is split in two places in the data file. For example, suppose that "The Skipper" and " sextant" (note the leading whitespace) are at the end of the data file 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 you 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 you try to store that blue shirt for the Skipper? While looking at the second line of input, you'll end up with this effect:

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

At this point, $provisions{"The Skipper"} doesn't exist, but you'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 you push the blue shirt to the provisions list.

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

This is actually the same behavior you'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, you dereference the value $not_yet as if it were an array reference. But since it's initially undef, Perl acts as if you 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 it is dereferenced as if it were an array reference, Perl inserts a reference to an empty anonymous array into $top. The third element (index value 2) is then accessed, which causes Perl to grow the array to be three elements long. That element is also undef, so it is stuffed with a reference to another empty anonymous array. We then spin out along that newly created array, setting the fifth element to lee-lou.

    [ Team LiB ] Previous Section Next Section