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

This will seem like a newbie RTFMish question at first, but it's slightly deeper than that.
#I have a hash, %myhash; #I pass it by reference: mergetwohashes(\%myhash, \%b); sub mergetwohashes { my($a)=$_[0]; my($b)=$_[1]; $a = { %{$a}, %{$b} }; } #After I call the function, %myhash is unchanged. Why.
Note: I do NOT want to modify any keys of the passed in hash. (then it would be a newbie question). I'm trying to completely replace it with a totally new hash. I want to change the target of the reference, to a new hash.

I also tried derefencing it as if it was a scalar reference, but perl complains "not a scalar reference", as you might guess.

I also tried the method call like this:
mergetwohashes(%myhash, \%b);
mergetwohashes($myhash, \%b);

Just in case the backslash operator was doing something I didn't expect, but no.

Is this something deeper- does the Perl call stack not support this because the passed in @_ array is immutable? What's my workaround here?

thank you

Replies are listed 'Best First'.
Re: Modify hash reference itself
by Beechbone (Friar) on Dec 02, 2003 at 00:29 UTC
    This won't work that way. You can only change the reference to point to another hash, but you cannot replace the hash itself. You could do this:
    $myhash = \%myhash; mergetwohashes($myhash, \%b); sub mergetwohashes { #my($a)=$_[0]; my($b)=$_[1]; $_[0] = { %{$_[0]}, %{$b} }; }
    But this will require you to only pass hash references, and work on the changed reference later. It will only change $myhsash, NOT %myhash.

    Or you could do this:

    sub mergetwohashes { my($a)=$_[0]; my($b)=$_[1]; %{$a} = ( %{$a}, %{$b} ); }
    But this way you only change the (complete) contents of the hash, the hash itself stays the same. But I'd guess this is just what you want...

    Search, Ask, Know
      Just to help clarify Beechbone's answer, $a = {%$a, %$b); changes to which hash $a refers, while %$a = {%$a, %$b); changes the hash to which $a refers. English isn't especially precise in this regard either. :) But I hope this helps.

      P.S. - You don't need braces to deref bare scalars - %$a works fine.


      Who is Kayser Söze?
      Your second example worked perfectly, thank you. You know, in my struggle I almost tried that, I had:

      %{$a} = { %{$a}, %{$b} };

      I've been writing perl for 6 years, and still I get tripped up. Thanks again Beechbone

Re: Modify hash reference itself
by davido (Cardinal) on Dec 02, 2003 at 00:36 UTC
    The problem is that you are expecting $a to become an alias for $_[0]. It's not. It's simply a scalar holding a reference; the same reference as that held in $_[0]. Later, it becomes a scalar holding a reference to an anonymous hash. Consequently, it no longer is a scalar holding a reference to the same thing that $_[0] refers to.

    On another note, $a is not necessarily a good choice for a lexical variable's name, as $a and $b also happen to be the special variables used by sort. It's not a problem in this case, but in some cases, it can lead to confusion and difficult to locate bugs.


    Dave

      $a is not necessarily a good choice for a lexical variable's name

      Actually, to be completely pendantic, $a and $b are perfectly safe so long as you use an inline sort routine in the same scope. For example, this won't work:

      xyz(1 .. 5); sub xyz { my $a = shift; sort { $a <=> $b } @_; print "$a\n"; }
      But, the following will work:
      xyz(1 .. 5); sub xyz { my $a = shift; abc(@_); print "$a\n"; } sub abc { sort { $a <=> $b } @_; }
      Since abc() provides a different lexical scope than xyz(), everything is ok and $a within xyz() is safe.

      But, your point of confusion is still good, as single-letter variables are poor.

      ------
      We are the carpenters and bricklayers of the Information Age.

      Please remember that I'm crufty and crochety. All opinions are purely mine and all code is untested, unless otherwise specified.

        Yea, I knew that $a and $b are not good choices, from writing sort code. I don't do that in my real code. But you guys are right to point that out when you see it of course.

        thanks

Re: Modify hash reference itself
by Roger (Parson) on Dec 02, 2003 at 00:42 UTC
    I see that your question has been answered. I just want to point out that the %$a = { %$a,  %$b }; part makes a copy of %$a and %$b before the assignment, which could be less efficient when the hash sizes are big. The following code works off the reference to %hash directly without making an additional copy of itself.
    use strict; use Data::Dumper; my %hash1 = qw/ A 1 B 2 /; my %hash2 = qw/ C 3 D 4 /; mergetwohashes(\%hash1, \%hash2); print Dumper(\%hash1); sub mergetwohashes { my ($a, $b) = @_; $a->{$_} = $b->{$_} foreach keys %$b; }
    And the output is as expected -
    $VAR1 = { 'A' => '1', 'B' => '2', 'C' => '3', 'D' => '4' };
      Yes thank you, and thanks to all, I'm shocked at the quantity, quality, and speed of the responses here.

      Yea, the reason I used the {%{$a}, %{$b} } example was both because it looks simpler, and because it conveys my question (actually if I had used your example I wouldn't have had this question because you're not replacing the hash ref, you're adding/changing keys within it).

      In other words, my code was just demo code, and my real sub is bigger and does more than just merge.

      But while we're on the subject of hash merging, there's also this:

      @b{keys %a} = values %a;
      (given %a and %b)

      And also I have bookmarked a thread from comp.lang.perl.misc from years ago:

      http://tinyurl.com/xaiz

      thanks again everyone

        Ah... I discussed this approach with davido the other day, I thought the benchmarking showed that this was actually slower than my simple approach. Strangely enough. ;-)

Re: Modify hash reference itself
by ysth (Canon) on Dec 02, 2003 at 00:40 UTC
    The assignment operator '=' won't do what you want. It gives a new value to the scalar $a; it can't affect what %myhash is. If %myhash is a global, you can get the effect you want by passing the glob *myhash instead of \%myhash 1, because there you have an opportunity to assign to the thing that contains %myhash; otherwise there is no way to do exactly what you want2.

    It sounds like it would be better for you to not have a hash %myhash but a hashref $myhash instead. Then you can pass it to mergetwohashes and modify $_[0] there to change what hash it points to.

    1 I think that will even work with your sub as is, given the magic nature of assignment to glob and dereferencing a glob.

    2 Actually not quite true, as you can tie %{$_[0]} in your sub and make %myhash appear to reference a different hash.

Re: Modify hash reference itself
by QM (Parson) on Dec 02, 2003 at 05:05 UTC
    Perhaps I have misread the replies, but I didn't see anyone suggest returning a hashref from the sub:
    # Two hashes: %myhash; %b; # Merge them %myhash = %{mergetwohashes(\%myhash, \%b)}; sub mergetwohashes { return { %{$_[0]}, %{$_[1]} }; }
    Though this isn't as efficient as the other solutions, it may be simple enough for what you are after.

    -QM
    --
    Quantum Mechanics: The dreams stuff is made of