I am a beginner perl programmer and I've come across a piece of code which looks as follows:

my @Numbers=();push(@{$Numbers[0]},3,8,9);push(@{$Numbers[1]},5);print join(",",@{$Numbers[0]});print "\n";print join(",",@{$Numbers[1]});print "\n";

I'm having difficulty in understanding what the push onto @{$Numbers[0]} and @{$Numbers[1]} is doing. Does the push automatically create array references to [3,8,9] and [5] in the Numbers array? I have no difficulty in understanding the syntax below, which is far more common I suppose, but I haven't come across the syntax that I've pasted above, which I guess also uses auto-vivification to populate the Numbers array with references.

my @Numbers=();push(@Numbers,[3,8,9]);push(@Numbers,[5]);print join(",",@{$Numbers[0]});print "\n";print join(",",@{$Numbers[1]});print "\n";
2

Best Answer


There are two features at play, and one is indeed autovivification.


I'll go into autovivification in a second. First, let's consider the fact that @Numbers is empty when we access $Numbers[0]. This is not a problem. Perl will automatically extend the array to include this element.[1]

my @a;$a[3] = 123; # ok!print(Dumper(\@a)); # [ undef, undef, undef, 123 ]

This isn't what the documentation calls autovivification, but it's similar enough that some people will call it autovivification too.


As I mentioned, the second feature is indeed autovivification. Autovivification is the automatic creation of an anonymous variable through dereferencing. Specifically, it happens when dereferencing an undefined scalar.[2]

In this case, autovivification creates an array since @BLOCK is an array dereference. A reference to that array is created and stored in the previously-undef scalar.

In other words, autovivification makes

push @{ $ref }, LIST;

effectively equivalent to

push @{ $ref //= [] }, LIST;

or

if (!defined($ref)) {my @anon;$ref = \@anon;}push @$ref, LIST;

It was probably just a contrived example for the question, but note that the posted code is weird. We know $Numbers[0] and $Numbers[1] are undef, so we don't need push.

my @nums;@{ $nums[0] } = ( 3, 8, 9 ); # Also autovivification.@{ $nums[1] } = 5;

And since we know $Numbers[0] and $Numbers[1] are undef, we there's no point in relying on autovivification either.

my @nums;$nums[0] = [ 3, 8, 9 ];$nums[1] = [ 5 ];

Let's get rid of those hardcoded indexes.

my @nums;push @nums, [ 3, 8, 9 ];push @nums, [ 5 ];

And a final simplification.

my @nums = ([ 3, 8, 9 ],[ 5 ],);

  1. This only happens in an lvalue context, meaning when the referenced variable is expected to be modified/modifiable. say $a[@a]; won't extend the array even though it accesses an element after its last element.

  2. It also has to be in an lvalue context. my @copy = @{ $undef }; will not autovivify.

Use Data::Dumper to see what is happening.

#! /usr/bin/perluse Data::Dumper;my @Numbers=();push(@Numbers,[3,8,9]);push(@Numbers,[5]);print Dumper(@Numbers);

Output:

$VAR1 = [3,8,9];$VAR2 = [5];

If you pass an array to Dumper, the elements are interpreted as multiple arguments.

Round parenthesis are arrays and rectangular parenthesis are array references. push works only on arrays but not on array references. See the Perl manual perlref for details about referencing and de-referencing.

You can convert an array to an array reference with a backslash:

print Dumper(\@Numbers);

Now Dumper shows the reference with rectangular parenthesis.

$VAR1 = [[3,8,9],[5]];

You can also make Numbers a reference, but then you have to de-reference it before you pass it to push.

#! /usr/bin/perluse Data::Dumper;my $Numbers=[];push(@$Numbers,[3,8,9]);push(@$Numbers,[5]);print Dumper($Numbers);

Now you will understand this:

push(@{$Numbers[0]},3,8,9);
  • $Numbers[0] takes the first element of an array (which is a scalar).
  • @{$Numbers[0]} de-references the scalar into an array.
  • push appends the three scalars 3, 8 and 9 to the de-referenced array.

This results into a creation of an array reference.