http://qs1969.pair.com?node_id=373471

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

In the code example, I pass a hash by value to a sub, push_1, and push one onto the array,
@{ $hash{$key} }
But when I examine the hash after the sub call, the changes are in the hash, but why is this happening. I thought the hash should have the pre function call values.
#!/usr/bin/perl use strict; use warnings; use Data::Dumper; my %hash; @hash{ 'a'..'d' } = ([175, 450], [0, 350], [0, 50], [300, 50]); print "Initial hash\n"; print Dumper \%hash; print "\nhash after call\n"; push_1(%hash); print Dumper \%hash; sub push_1 { my %hash = @_; for (keys %hash) { push @{ $hash{$_} }, 1; } } __END__ *** Output C:\perlp>perl t.pl Initial hash $VAR1 = { 'c' => [ 0, 50 ], 'a' => [ 175, 450 ], 'b' => [ 0, 350 ], 'd' => [ 300, 50 ] }; hash after call $VAR1 = { 'c' => [ 0, 50, 1 ], 'a' => [ 175, 450, 1 ], 'b' => [ 0, 350, 1 ], 'd' => [ 300, 50, 1 ] }; C:\perlp>

Replies are listed 'Best First'.
Re: Pass by value acts like pass by reference
by tilly (Archbishop) on Jul 11, 2004 at 16:38 UTC
    Understanding this is part of understanding how references work.

    You are not changing the actual values in either the function or the original data structure. Those values are a pointer saying, The real data is over there. Instead you're manipulating the value of what the reference is pointing to. Since both are pointing there, the side-effect is that both see the change.

    To achieve the effect that you are looking for, you need to change the value of the hash by creating new references. Like this:

    sub push_1 { my %hash = @_; for (keys %hash) { $hash{$_} = [ @{ $hash{$_} }, 1 ]; } }
    UPDATE: It became clear in chatter that I should make it precise what references are involved. The references that I'm talking about are the anonymous array references that are the values of the hash.
Re: Pass by value acts like pass by reference
by sauoq (Abbot) on Jul 11, 2004 at 20:15 UTC

    When you copy the hash with my %hash = @_; you are making copies of its values, but those values are themselves references. And the copied references refer to the same things that the originals do!

    In order to get pass-by-value like behavior¹ with complex structures in Perl, you need to make a "clone" or "deep copy" of your structures. The Storable module can help with that.

    1. I say "pass-by-value like behavior" because, regardless, the actual passing to your subs is really by reference. You must make the copy yourself.

    -sauoq
    "My two cents aren't worth a dime.";
    
Re: Pass by value acts like pass by reference
by davido (Cardinal) on Jul 11, 2004 at 16:22 UTC
    This is happening because you're passing the keys/values of the hash into push_1() instead of a reference to the hash.

    When you pass a hash like this: push_1(%hash), the hash is being flattened into a list of key/value pairs, and they're being assigned into @_. You should be passing the hash like this: push_1(\%hash), and then properly dereferencing it like this within the sub:

    my $href = shift; for my $key ( keys %{ $href } ) { push @{$href->{$key}}, 1; }

    Hope this helps...

    Update: Oh shoot, this is a botched answer. In testing and tinkering with your script I see that, as expected, the hash inside the sub is a different one than the one outside the sub, and in deparsing the code I see that as you pass the hash, it's being passed as a flattened list. So I can't explain the behavior you've identified.

    Update 2: Duh, how blind could I be? Thanks tilly for spelling it out here (and reminding me in CB): The value of each hash element, in your example, is an arrayref, and that value passes unchanged into the subroutine. That means that you can dereference the value held in the lexical hash within the sub just as you would the value of the lexical hash outside the sub; they're different hashes, but their values hold references to the same anonymous arrays.


    Dave

Re: Pass by value acts like pass by reference
by Anonymous Monk on Jul 11, 2004 at 16:42 UTC
    Thank you for the reply but I think you missunderstood my question. I passed by value, a flattened list of key value pairs, but after the transformation of the temporary hash in the sub, the hash outside the sub reflects the changes. I've been thinking further though and think what may be happening is that what is passed to the sub is a array reference. And inside the sub, 1 is pushed onto this array, which is the same array contained in the hash outside the sub. So thats why the change is noted (I think). Is this correct?

      When you flatten a hash you are getting a shallow copy of the hash, not a deep copy. To get a deep copy you need to use some form of serialization mechanism to do it. Storable will probably be your best bet in terms of speed and memory consumption. Of course,it may be a lot easier to just change your algorithm to not corrupt the original arrays....


      ---
      demerphq

        First they ignore you, then they laugh at you, then they fight you, then you win.
        -- Gandhi


        Of course,it may be a lot easier to just change your algorithm to not corrupt the original arrays....

        That was what I ended up doing, thanks to to tilly's suggestion. Thank you to all who responded.

      This is exactly what is happening.
Re: Pass by value acts like pass by reference
by gmpassos (Priest) on Jul 12, 2004 at 07:19 UTC
    The question is not your hash that is changed, actually your hash is still the same, a group of pairs of keys and references to arrays. The question is the references that are in the values of your hash.

    When you write

    my $ref = [0 , 1 , 2 , 3] ;
    you are creating an anonimous reference to an array, that is the same to write:
    my @array = (0 , 1 , 2 , 3) ; my $ref = \@array ;

    So, when you paste a hash as a argument to the sub, the hash is converted to an array, where we have a sequence of keys and values. The scalars that build this array (@_) will exists only inside the sub, but what you forgot is that the scalars are holding a reference to an array, and your main hash, outside the sub, is also holding this references to the same arrays. So, you are changing the same arrays and this is the meaning of references, this is why they exists.

    The best way to understand is forgeting the hash and make a simple test with references:

    $main_ref = [1 , 2 , 3] ; $ref2 = $main_ref ; $ref2->[0] = 'a' ; print "$main_ref->[0]\n" ; # we got 'a' $ref2 = 'not a ref anymore' ;
    So, if you paste the reference you change the main array, but if you work over $ref2 and redefine it (last line) this won't change $main_ref, that still hold the reference. Now just think that the values of your hash work exaclty as this 2 scalars.

    Graciliano M. P.
    "Creativity is the expression of the liberty".