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

Hi, may I know how I can pass only specific/part of hash to another sub as reference?

sub main { my (%hash_rec) = (); $hash_rec{'id'} = "001"; &doSub1(\%hash_rec); # return result should consist of id=001 and id2=002 print "back main, id=$hash_rec{'id'}, id2=$hash_rec{'id2'}\n"; } sub doSub1 { my ($hash_ref) = @_; print "inside doSub1, id=$$hash_ref{'id'}\n"; $$hash_ref{'id2'} = "002"; # How do I call doSub2 and pass only specific hash id2 as reference +? &doSub2($hash_ref{'id2'}); } sub doSub2 { my ($id2_ref) = @_; print "inside doSub2, id2=$$id2_ref\n"; }

Inside doSub1, I would like to call another sub &doSub2($hash_ref{'id2'}); by passing only specific hash value $$hash_ref{'id2'} or part of $hash_ref as by reference. Is this possible?

Currently, I can only think of doing like codes below;

Before calling &doSub2, I assign $$hash_ref{'id2'} to another local var. Then pass that var to &doSub2 as reference. After that will replace back to $$hash_ref{'id2'}. I am thinking could there be a better or shortcut way in doing so?

my $id2_value = $$hash_ref{'id2'}; &doSub2{\$id2_value); $$hash_ref{'id2'} = $id2_value;

Replies are listed 'Best First'.
Re: Sub hash param by reference
by stevieb (Canon) on Jul 07, 2016 at 18:44 UTC

    I've cleaned up the code a bit, and then made some fixes. you're trying to dereference things that aren't refs ;) Read the inline comments...

    use warnings; use strict; my %hash_rec; $hash_rec{id} = "001"; doSub1(\%hash_rec); print "back main, id=$hash_rec{'id'}, id2=$hash_rec{'id2'}\n"; sub doSub1 { my ($hash_ref) = @_; print "inside doSub1, id=$hash_ref->{id}\n"; # you're not assigning a reference to $hash_ref->{id2}, # you're assigning a simple string $hash_ref->{id2} = "002"; # you're only sending a scalar string to doSub2()... # $hash_ref->{id2} eq "002" doSub2($hash_ref->{id2}); } sub doSub2 { # $id2_ref is NOT a reference... it's the value of # $hash_ref->{id2}, which is a scalar string only my ($id2_ref) = @_; print "inside doSub2, id2=$id2_ref\n"; # no need to deref this! }

    Output:

    inside doSub1, id=001 inside doSub2, id2=002 back main, id=001, id2=002

    -> is the canonical dereference operator that you should be using, and don't put & at the beginning of sub calls. That's legacy perl 4 code, and isn't needed/shouldn't be used unless you know why you need them. Also, there's no need to define a main() sub. All portions of your program that are not wrapped in subs (or external modules, which you're not using here) already belong to "main".

      Hi Stevie, thanks for the feedback. Your code is good, However, on doSub2 is not what I am looking for. The value pass to doSub2 need to be in reference as the value may change inside that sub.

        It's a whole lot easier and more subtle than you might expect. Perl actually passes parameters as aliases in @_ so we can:

        use warnings; use strict; my %hash_rec = (id => "001"); doSub2($hash_rec{id2}); print "main: id2 = $hash_rec{id2}\n"; sub doSub2 { $_[0] = "Hello alias"; }

        Prints:

        main: id2 = Hello alias

        We don't usually take advantage of that trick because without due care it's likely to lead to hard to maintain code.

        Premature optimization is the root of all job security
Re: Sub hash param by reference
by Marshall (Canon) on Jul 07, 2016 at 21:07 UTC
    If you are trying to pass a reference to some value in a hash to a sub, that is possible. That way the sub wouldn't know the key name. More usual would be to pass a ref to the whole hash and the sub does know the key name. Here is a demo of both possibilities. Hope that I'm not confusing the issue.
    #!/usr/bin/perl use strict; use warnings; use Data::Dump qw(pp); $!=1; my %hash; $hash{id1} = 001; $hash{id2} = 002; print "initial hash: ", pp (\%hash), "\n"; my $hash_ref = \%hash; my $ref = \$hash{id2}; # A reference to id2 $$ref = 003; #use ref to a scalar (value of id2) print "modified hash: ", pp (\%hash), "\n"; modify_anon_hash_value ($ref); print "mod by sub : ", pp (\%hash), "\n"; more_normal_way($hash_ref); print "mod more usual: ", pp ($hash_ref), "\n"; sub modify_anon_hash_value #sub doesn't know which { #key's value that it is my $ref = shift; #modifying $$ref = 5; } sub more_normal_way #usual pass the entire hash as ref { my $href = shift; $href->{id2}=8; #sub know which key to use } __END__ initial hash: { id1 => 1, id2 => 2 } modified hash: { id1 => 1, id2 => 3 } mod by sub : { id1 => 1, id2 => 5 } mod more usual: { id1 => 1, id2 => 8 }

      Thank you Marshall, my $ref = \$hash{id2} is exactly what I am trying to look for.

      Your given example is perfect. I'm just curious, when calling sub modify_anon_hash_value ($ref) is it possible to do it like modify_anon_hash_value (\$hash{id2}) ? without need to do my $ref = \$hash{id2}.

      I have test run it with

      modify_anon_hash_value (\$hash{id2});
      The result return correctly without any error. Just to confirm if this way of coding is allowed or it may be a bad way to do it.

        Yes, you will be fine without creating an intermediate scalar. Using this method (modify_anon_hash_value (\$hash{id2});), I see right away in the callers code that a modification to $hash{id2} is what is intended.

        What is sent to the sub are aliases. It is possible to "hide" this reference creation in the sub. For fun, I coded the routine "confusing" below. The caller wouldn't expect that to happen, so I wouldn't do that.

        #!/usr/bin/perl use strict; use warnings; use Data::Dump qw(pp); $!=1; my %hash; $hash{id1} = 001; $hash{id2} = 002; print "initial hash: ", pp (\%hash), "\n"; my $hash_ref = \%hash; modify_anon_hash_value (\$hash{id2}); print "mod by sub : ", pp (\%hash), "\n"; confusing ($hash{id2}); print "mod by confusing : ", pp (\%hash), "\n"; sub modify_anon_hash_value #sub doesn't know which { #key's value that it is my $ref = shift; #modifying $$ref = 5; } sub confusing #a confusing mess { my $ref = \(shift); #same as $ref = \$_[0] $$ref=9; # @_ has aliases, not a copy } __END__ initial hash: { id1 => 1, id2 => 2 } mod by sub : { id1 => 1, id2 => 5 } mod by confusing : { id1 => 1, id2 => 9 }

      Hi Marshall, sorry I have confused your previous answer with my initial questions. Your previous answer is helpful. Just my initial case is little bit different. When inside a sub, the value pass in is already a hash reference and I would like to call another sub but only in refer to specific rec of that hash. Please have a look of the codes below;

      my (%hash); $hash{'id1'} = "1"; doSub1(\%hash); # the return id2 should be changed within doSub2 when doSub1 call it. print "$hash{'id2'}\n"; doSub1 { # accept param as hash by reference my ($hrec_ref) = @_; $$hrec_ref{'id2'} = "0"; # Going to call doSub2 by passing Only $$hrec_ref{'id2'} as reference. # Because $hrec_ref is a hash reference already, How I can call it? # doSub2(\$hrec_ref{'id2'}); Return Error. Not valid. # doSub2($hrec_ref{'id2'}); Also return Error. } doSub2 { # handle only a single string param by reference my ($href) = @_; $$href = "value changed in doSub2"; }

      My problem confusing now is IF the hash is in a reference value and I need call another sub to process only specific hash rec. My current only way to deal with this is;

      #... inside doSub1 my ($s) = $$hrec_ref{'id2'}; doSub2(\$s); $$hrec_ref{'id2'} = $s;
      I'm trying to see if there are any shortcut version of coding it.

        You need to use the sub keyword when declaring/defining subroutines. Here's a working version based on your code.

        use strict; use warnings; my (%hash); $hash{'id1'} = "1"; doSub1 (\%hash); # the return id2 should be changed within doSub2 when doSub1 call it. print "$hash{'id2'}\n"; sub doSub1 { # accept param as hash by reference my ($hrec_ref) = @_; $$hrec_ref{'id2'} = "0"; # doSub2(\$hrec_ref{'id2'}); Return Error. Not valid. (works for me - +Hippo) doSub2 (\$hrec_ref->{'id2'}); } sub doSub2 { # handle only a single string param by reference my ($href) = @_; $$href = "value changed in doSub2"; }
Re: Sub hash param by reference
by perlfan (Parson) on Jul 07, 2016 at 20:21 UTC
    You can pass the whole hash by reference, then deref what you want using a hash slice if that's what you mean. Otherwise, what's the point if it's all by reference?

    (tested on 5.22.1) See perldata for more info.
    use strict; use warnings; use Data::Dumper; sub slice_by_ref { my $h = shift; my %subset = %$h{'foo', 'bar'}; print Data::Dumper::Dumper(\%subset); } my $h = {blonk => 2, foo => 3, squink => 5, bar => 8}; slice_by_ref($h);
    OUTPUT:
    $VAR1 = { 'foo' => 3, 'bar' => 8 };
Re: Sub hash param by reference
by TomDLux (Vicar) on Jul 11, 2016 at 19:58 UTC

    So long as your subset is a nested hashref or nested array, this works nicely. I've done something similar to spread processing across dedicated routines.

    my $results = process_planet( \%world ); sub process_planet { my ( $world ) = @_; my @retval; for my $country_id ( keys %$world ) { my $population = process_country( $world->{$country_id} ) +; push @retval, { $country_id => $population }; } return \@retval; } sub process_country { my ( $country ) = @_; my $national_pop; for my $city ( keys %$country ) { $national_pop += extract_city_population( $countr->{$city} + ); } return $national_pop; }

    Of course, at eavh level you have to know what you're receiving, and what you're returning.

    As Occam said: Entia non sunt multiplicanda praeter necessitatem.