Wednesday, August 8, 2012

Interesting perl foreach behavior

My co-workers were surprised by this perl behavior and shared this problem so I thought it would be a fun post.
my %hash = (
   a => 1,
   b => 2,
);

foreach my $v (values %hash) {
    $v = 99;
}

warn Data::Dumper->Dump([\%hash], [qw(hash)]);

# This was the output:
$hash = {
  a => 99,
  b => 99
};


This code is trying to edit a hash, and normally, you would iterate over the keys and then change the looked up value somewhat like this:
foreach my $k (keys %hash) {
    $hash[$k] = 99;
}

You can also change an items value for an array as well:
my @test = qw(a b c);
foreach my $v (@test) {
  $v = 'zzz';
}
warn Data::Dumper->Dump([\@test], [qw(test)]);

# This was the output:
$test = [
  'zzz',
  'zzz',
  'zzz'
];


What is happening here is that the for-loop variable is an alias for each list item. Since its an alias, Perl does not copy the list item's value just to hand $v to you (it's efficient).

The modification of hash values happens in the example because values() itself returns a list of hash values. This below wouldn't work because it introduces an intermediate copy:
my %hash = (
  a => 1,
  b => 2,
);
my @hash_values = values %hash; # make copies of hash value aliases
foreach my $v (@hash_values) {
  $v = 99;
}

And the following example is also illegal because we are trying to modify read-only values:
foreach my $v (qw[ a b c ]) {
  $v = 'zzz';
}
So now you know the power of the foreach loop.