vsespb has asked for the wisdom of the Perl Monks concerning the following question:

Why is that not working?
$ perl -MData::Dumper -e 'sub t{ $_+=1 for @_}; my %h = (1 => 2, 3 => +4); t(%h); print Dumper \%h'
$VAR1 = { '1' => 3, '3' => 5 };
One could expect:
$VAR1 = { '2' => 3, '4' => 5 };

Replies are listed 'Best First'.
Re: Modifying hash keys via aliasing
by roboticus (Chancellor) on Aug 10, 2015 at 22:36 UTC

    vsespb:

    Hash keys aren't modifiable. If you want to change a key, copy the value to the new key, and delete the old one, like this:

    $hash{$new_key} = $hash{$old_key}; delete $hash{$old_key};

    ...roboticus

    When your only tool is a hammer, all problems look like your thumb.

Re: Modifying hash keys via aliasing
by ikegami (Patriarch) on Aug 10, 2015 at 22:43 UTC
      Thank you! Very detailed explanation.
Re: Modifying hash keys via aliasing (strings)
by tye (Sage) on Aug 11, 2015 at 00:56 UTC

    Hash keys are strings, not full-blown scalars. This is also why you can't have a hash key that is a reference.

    You can't alias a string like you can alias a scalar. You could make a magic scalar that modifies a string when the scalar is modified, but that still wouldn't be enough, because then the string would be changed but the wrong bucket of the hash would be used. So changing a key really requires deleteing and then re-inserting. So Perl could create a magic scalar that does all that work but that would be pretty darn wasteful for the 99.9836% of cases when people do not try to modify hash keys via aliasing the return values from %h. So Perl doesn't do that.

    (Yes, I read all of the other answers; I like this explanation better.)

    - tye        

      Thanks!
Re: Modifying hash keys via aliasing
by Anonymous Monk on Aug 10, 2015 at 22:37 UTC
    Based on the results I'd say because keys cannot be lvalues

    So yeah, that seems to be the cause

    I'm sure its documented somewhere, I leave that up to you :)

Re: Modifying hash keys via aliasing
by Laurent_R (Canon) on Aug 11, 2015 at 08:59 UTC
    Although you have already been given detailed answers, just one quick question to let you think about it: if you passed an array to your sub, you would not expect it to modify the array subscripts, would you?

    Admittedly, it is not quite exactly the same, but you don't need a sub to figure out that you simply cannot modify the keys of a hash (other than by deleting an existing entry and creating another one.

    What is happening in your sub is that the values associated with the hash keys are actually modified in @_, but this has no effect on the hash itself, whereas the values associated with the hash values are actually modified within the hash, as it can be seen with this slight modification of your one-liner:

    $ perl -MData::Dumper -we 'sub t{ $_+=1 for @_; print Dumper \@_;} my + %h = (1 => 2, 3 => 4); t(%h); print Dumper \%h' $VAR1 = [ 2, 3, 4, 5 ]; $VAR1 = { '1' => 3, '3' => 5 };
      Your (and other's too) answer is correct and clear. But why no warnings are emitted in this case? like 'Redefine hash key not allowed at..' is similar to re-declare an already declared var, no?

      For the OP question, the desired output can also be achievied reassigning the entire hash, like:
      >perl -MData::Dumper -Mstrict -e 'sub t{ map {$_+=1 } @_}; my %h = (1 +=> 2, 3 => 4); %h = t(%h); print Dumper \%h' $VAR1 = { '4' => 5, '2' => 3 };


      L*
      There are no rules, there are no thumbs..
      Reinvent the wheel, then learn The Wheel; may be one day you reinvent one of THE WHEELS.
        Yes, I agree, I also would have expected a warning (which is why I added the -w flag in the first place).

        Just as I would also have expected a warning saying something like "attempt to modify read-only value" with the following pieces of code:

        $ perl -Mstrict -wE 'say ++$_ for 1..4;' 2 3 4 5 ~ $ perl -Mstrict -wE 'say $_ for map {++$_} 1..4;' 2 3 4 5
        But there is no warning. On the other hand, you get a warning with this:
        $ perl -Mstrict -wE 'say $_ for map {++$_} (1, 2, 3, 4);' Modification of a read-only value attempted at -e line 1.
        There must be some reason, but it does not look very consistent to me.