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.
  1. my %hash = (
  2. a => 1,
  3. b => 2,
  4. );
  5.  
  6. foreach my $v (values %hash) {
  7. $v = 99;
  8. }
  9.  
  10. warn Data::Dumper->Dump([\%hash], [qw(hash)]);
  11.  
  12. # This was the output:
  13. $hash = {
  14. a => 99,
  15. b => 99
  16. };
  17.  

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:
  1. foreach my $k (keys %hash) {
  2. $hash[$k] = 99;
  3. }

You can also change an items value for an array as well:
  1. my @test = qw(a b c);
  2. foreach my $v (@test) {
  3. $v = 'zzz';
  4. }
  5. warn Data::Dumper->Dump([\@test], [qw(test)]);
  6.  
  7. # This was the output:
  8. $test = [
  9. 'zzz',
  10. 'zzz',
  11. 'zzz'
  12. ];
  13.  

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:
  1. my %hash = (
  2. a => 1,
  3. b => 2,
  4. );
  5. my @hash_values = values %hash; # make copies of hash value aliases
  6. foreach my $v (@hash_values) {
  7. $v = 99;
  8. }

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