Beefy Boxes and Bandwidth Generously Provided by pair Networks
The stupid question is the question not asked
 
PerlMonks  

passing a hash ref in a sub

by frazap (Monk)
on Oct 31, 2018 at 14:53 UTC ( [id://1224994]=perlquestion: print w/replies, xml ) Need Help??

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

It tooks me a long time to correct this, and I don't see what's wrong with this:
use strict; use warnings; use Data::Dumper; my $data = {status=>"ok",'message-type'=>"member",'message-version'=>" +1.0.0"}; my $result_hr; unfold_hash($data, $result_hr); print Dumper $result_hr; sub unfold_hash { my ( $raw_hr, $res_hr ) = @_; for my $k ( keys %$raw_hr ) { $res_hr->{$k} = $raw_hr->{$k}; } print "unfold_hash ", $res_hr ? scalar %$res_hr : 0, "\n"; }

Here, unfold_hash is rather stupid since it does nothing interesting. But the problem remains: $result_hr is undef.

To correct this I have to say

my $result_hr={}; unfold_hash($data, $result_hr); print Dumper $result_hr;
Now $result_hr holds the content of $data. How comes that the hash auto vivification is lost if the variable $result_hr is undef ?

Thanks

F.

Replies are listed 'Best First'.
Re: passing a hash ref in a sub
by davido (Cardinal) on Oct 31, 2018 at 15:13 UTC

    When you call unfold_hash($data, $result_hr), the special variable @_ contains two elements, each that are aliases to the things you passed in. The first element is an alias to the variable that holds a reference to a datastructure. The second element is an alias to a variable that is not yet defined.

    The first thing you do is then assign @_ to two variables: $raw_hr and $res_hr. At this point you have moved away from the aliasing. $raw_hr still contains a reference to the datastructure that was referred to by $data, but $res_hr just contains undef. You can think of it like this: Each variable is assigned a value. The value assigned to the first variable is a reference. The value assigned to the second variable is undef. The reference assigned to $raw_hr is the same reference that $data holds, so both of those variables independently refer to the same datastructure..

    Aliasing no longer exists in these new variables. Yet you can modify the datastructure referred to by the reference that $raw_hr, and that change propagates outward, because they refer to the same data structure. On the other hand, if you assign a new reference to $raw_hr, it would not have anything to do with the $result_hr variable because no aliasing is in effect, and $result_hr does not know of this reference.

    When you autovivify a hashref and assign it into $raw_hr there is no mechanism by which that hashref would propagate outward; there is no aliasing in effect here, and the reference was unknown outside of the subroutine. In the case where you set $result_hr = {}, you create a reference to an empty data structure, and the subroutine obtains a copy of that reference (not of the structure), which it can then manipulate such that changes to the structure propagate outward. ...this is high risk behavior, however, as it changes the data structure... anyone referring to it will have the same change, because it is the same structure.


    Dave

      Fantastic explanation! The best documentation I have been able to find about this is perlsub, but I would love to see any more details if you have them.

      Here is the OP's code working as he expected:

      perl -e ' > use strict; > use warnings; > use Data::Dumper; > > my $data = {status=>"ok","message-type"=>"member","message-version"= +>"1.0.0"}; > my $result_hr; > > unfold_hash($data, $result_hr); > print Dumper $result_hr; > > sub unfold_hash { > for my $k ( keys %{ $_[0] } ) { > $_[1]{$k} = $_[0]{$k}; > } > print "unfold_hash ", $_[1] ? scalar %{ $_[1] } : 0, "\n"; > } > ' unfold_hash 3 $VAR1 = { 'message-version' => '1.0.0', 'message-type' => 'member', 'status' => 'ok' };

      πάντων χρημάτων μέτρον έστιν άνθρωπος.

Re: passing a hash ref in a sub
by haukex (Archbishop) on Oct 31, 2018 at 15:19 UTC

    Simplifying a bit, references point to a memory location of a data structure. In your first piece of code, my $result_hr; will be undef because it isn't initialized. In your sub, you copy the elements of @_ into $raw_hr and $res_hr, so now $res_hr is just a copy of the original undef. In other words, it doesn't point anywhere! And $res_hr has no way to affect $result_hr. There is a hash being autovivified, but it is restricted to the scope of the sub.

    When you write my $result_hr={};, now what is being passed into the sub is a reference that points to an anonymous hash. The reference still gets copied into $res_hr, but the result of that is just two references pointing at the same data structure. The hash isn't being autovivified in sub, instead the sub is modifying the anonymous hash pointed to by both $result_hr and $res_hr. When the sub returns, your main code still has access to the anonymous hash via the reference stored in $result_hr.

    Note that there is a way to modify $result_hr from within the sub: use $_[1] directly, instead of $res_hr, because the elements of @_ are aliases to the original arguments. However, I wouldn't normally recommend this way of modifying arguments, because that they are in effect return values can be confusing to users of the API. It's more common to handle this via regular return values.

    (Update before posting: I see davido has explained the same thing, with different wording - TIMTOWTDI :-) )

      There is indeed more than one way to explain references, and to date I don't believe anyone has found an optimal way. ;)


      Dave

        There is indeed more than one way to explain references, and to date I don't believe anyone has found an optimal way. ;)

        Yep, in my experience it differs from person to person which kind of an explanation will make it "click" :-)

        (And just to be clear, your explanation is very good and my post was in no way intended to be a commentary on it - hence the TIMTOWTDI comment!)

Re: passing a hash ref in a sub
by tybalt89 (Monsignor) on Oct 31, 2018 at 15:11 UTC
    #!/usr/bin/perl # https://perlmonks.org/?node_id=1224994 use strict; use warnings; use Data::Dumper; my $data = {status=>"ok",'message-type'=>"member",'message-version'=>" +1.0.0"}; my $result_hr; unfold_hash($data, $result_hr); print Dumper $result_hr; sub unfold_hash { my ( $raw_hr ) = @_; for my $k ( keys %$raw_hr ) { $_[1]->{$k} = $raw_hr->{$k}; } print "unfold_hash ", $_[1] ? scalar %{$_[1]} : 0, "\n"; }

    Outputs:

    unfold_hash 3 $VAR1 = { 'message-version' => '1.0.0', 'status' => 'ok', 'message-type' => 'member' };
Re: passing a hash ref in a sub
by Lotus1 (Vicar) on Oct 31, 2018 at 15:46 UTC

    Good question. The others explained it well already but here is another hint. (You weren't actually passing a reference is why it didn't work.)

    use strict; use warnings; my $data = {status=>"ok",'message-type'=>"member",'message-version'=>" +1.0.0"}; my $result_hr; ref($result_hr) ? print "ref($result_hr)\n" : print "\$result_hr is no +t a reference.\n" ; $result_hr = {}; ref($result_hr) ? print "ref($result_hr)\n" : print "\$result_hr is no +t a reference.\n" ; __DATA__ $result_hr is not a reference. ref(HASH(0x3ddb70))
Re: passing a hash ref in a sub
by pDaleC (Sexton) on Nov 01, 2018 at 13:54 UTC
    I would suggest you make the hash the return value of the function:
    sub unfold_hash { my ( $raw_hr ) = @_; my %res; for my $k ( keys %$raw_hr ) { $res{$k} = $raw_hr->{$k}; } print "unfold_hash ", %res ? scalar %res : 0, "\n"; \%res; } $result_hr= unfold_hash($data); print Dumper $result_hr;
Re: passing a hash ref in a sub
by pwagyi (Monk) on Nov 02, 2018 at 04:15 UTC

    Is the question for the sake of understanding reference and sub-routine? If not, the code is basically just merging one hash to another.

    my $result_hr = { %$data };

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://1224994]
Approved by marto
Front-paged by choroba
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others about the Monastery: (4)
As of 2024-04-19 02:56 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found