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

Hi Monks,

I noticed in node 310766 that you can pass different variables through to a sub.. all good and useful stuff.

But when I tried to pass a hash through by reference it only worked one way.

I assume its because I have defined a local hash to be equal to the referenced hash.. but I thought 'by ref' meant it would link directly to the original hash not use a copy of it.

How do you pass a hash (or anything) into a sub, work on it, and have the original effected?

use strict; use Data::Dumper; my %bits; $bits{'zero'}='start element'; &loader('something',\%bits); print 'Result:'.Dumper(\%bits); sub loader{ # loads values into hash.. my $tail = shift; my %h_ref = %{+shift}; print 'In sub:'.Dumper(\%h_ref); $h_ref{'one'}='thing_'.$tail; $h_ref{'two'}='thong_'.$tail; print 'After add:'.Dumper(\%h_ref); }
Output:
In sub:$VAR1 = { 'zero' => 'start element' }; After add:$VAR1 = { 'zero' => 'start element', 'two' => 'thong_something', 'one' => 'thing_something' }; Result:$VAR1 = { 'zero' => 'start element' };
Thanks,
___ /\__\ "What is the world coming to?" \/__/ www.wolispace.com

Replies are listed 'Best First'.
Re: Passing hashes by reference
by Roger (Parson) on Dec 02, 2003 at 04:04 UTC
    I noticed that you created a copy of the hash, and modified the copy of the hash, not the original hash. If you want to modify the original hash, you need to do something like below...

    sub loader{ # loads values into hash.. my $tail = shift; my $h_ref = shift; print 'In sub:'.Dumper($h_ref); $h_ref->{'one'}='thing_'.$tail; $h_ref->{'two'}='thong_'.$tail; print 'After add:'.Dumper($h_ref); }
    Your code my %h_ref = %{+shift}; created a local copy of the hash.

      Such a quick response, thanks.

      I noticed that you used ->:

      $h_ref->{'one'}='thing_'.$tail;
      Am I right in assuming the -> is the key to the whole thing.

      Is it the only way of referencing the hash?

      And do lists and scalars need this or is it just the complicated old hash that does?

      ___ /\__\ "What is the world coming to?" \/__/ www.wolispace.com
        Corrent. '->' dereferences a reference to a hash. Note that I do not have to explicitly tell Perl that I am dereferencing a reference to a hash, Perl will work it out.

        I have constructed the following example to show how to dereference a hash in a sub and modify its contents.

        use strict; use Data::Dumper; my %hash = qw/ A 1 B 2 /; print 'Before sub:'.Dumper(\%hash); modify_hash(\%hash); print 'After sub:'.Dumper(\%hash); sub modify_hash{ my $h_ref = shift; $h_ref->{C} = '3'; $h_ref->{D} = '4'; %{$h_ref}->{E} = '5'; # this is what it's doing $$h_ref{F} = '6'; # you could do this too }
        And the output is -
        Before sub:$VAR1 = { 'A' => '1', 'B' => '2' }; After sub:$VAR1 = { 'F' => '6', 'A' => '1', 'B' => '2', 'C' => '3', 'D' => '4', 'E' => '5' };

        Yes, the -> is key. Without it, the lhs would be interpreted as $h_ref{'one'}, ie an element of the hash %h_ref.

        Though this is convenient, it isn't the only way to do it. You may use the notation $$href{'one'}, but that's just ugly. The same notation can be use with arrays (not the same thing as a list!) like this: $my_array->[1]



        Who is Kayser Söze?
        You can also use this form.
        $hash = { a=>1 }; ${$hash}{b}=2; print ${$hash}{a}; print ${$hash}{b};
        I prefer this form, though I'm sure it's the unpopular way. :)

        Play that funky music white boy..
Re: Passing hashes by reference
by Zaxo (Archbishop) on Dec 02, 2003 at 04:06 UTC

    By saying,     my %h_ref = %{+shift}; in the sub, you make a copy of the hash which all your work goes into. Instead,

    my $h_ref = shift; # ... $h_ref->{'one'}= 'thing_' . $tail; # etc
    That works on the original external hash.

    After Compline,
    Zaxo

Re: Passing hashes by reference
by DrHyde (Prior) on Dec 02, 2003 at 11:24 UTC
    Perl subroutines are indeed pass-by-reference, meaning that the elements of @_ in your sub are exactly the same elements as were passed to it by the caller. If you manipulate the contents of the elements of @_, you change them in the caller. However, if you assign the elements of @_ to another variable before changing them, you only change the new copy of those data. If you want to change the contents of a hash by calling a subroutine, you can do this:
    %a = qw(cat dog); print $a{cat}; foo(%a); print $a{cat}; sub foo { $_[1] = "elephant" }
    which prints "dog" first, then "elephant".

    However, such code is Bad because there's nasty magical action-at-a-distance. In fact, if I saw code like that I would assume it was a bug. You're better off doing this:

    %a = qw(cat dog); print $a{cat}; %a = foo(%a); # <-- note assignment print $a{cat}; sub foo { my %temphash = @_; $temphash{cat} = "elephant"; %temphash; }
    which, while longer, makes it clearer to someone maintaining your code that the foo subroutine is used to modify the contents of the hash.

    Another way would be to explicitly pass the subroutine a reference to the hash and then use the -> operator to modify what the reference refers to. When doing that, it is still common to copy the reference to the hash into a scalar in your subroutine, but when you do that, you can still dereference it and $reference->{wibble} is far easier to read than $_[0]->{wibble}.

Re: Passing hashes by reference
by Anonymous Monk on Dec 02, 2003 at 08:18 UTC
    I noticed in node 310766 that you can pass different variables through to a sub.. all good and useful stuff.
    Did you notice perlsub and perlreftut? You should give those a read :)
      Yes, I did notice those pods and I do read pods before diving in with a question..

      ..I just find the language in them assumes too much prior knowledge. People here are soooo good at explaining things in plain english.

      PerlMonks can also give specific examples where as the pods give one example and assume you can work out how it would be done with a different variable type or in a different situation (something I can't always do).

      ___ /\__\ "What is the world coming to?" \/__/ www.wolispace.com
Re: Passing hashes by reference
by Stevie-O (Friar) on Dec 02, 2003 at 22:49 UTC
    Simply because I enjoy producing scripts with arcane/obscure syntax that make other people cringe, I shall offer up the following:
    sub loader{ # loads values into hash.. my $tail = shift; local *h_ref = shift; # there's nothing quite like hacking the symbol + table :) print 'In sub:'.Dumper(\%h_ref); $h_ref{'one'}='thing_'.$tail; $h_ref{'two'}='thong_'.$tail; print 'After add:'.Dumper(\%h_ref); }
    I would like to remind you that noone sane would use this technique.
    --Stevie-O
    $"=$,,$_=q>|\p4<6 8p<M/_|<('=> .q>.<4-KI<l|2$<6%s!<qn#F<>;$, .=pack'N*',"@{[unpack'C*',$_] }"for split/</;$_=$,,y[A-Z a-z] {}cd;print lc
Re: Passing hashes by reference
by uecker (Initiate) on Dec 03, 2003 at 16:08 UTC
    Work directly on @_ instead of making a copy by the my-operator:
    use strict; use Data::Dumper; my %bits; $bits{'zero'}='start element'; &loader('something',\%bits); print 'Result:'.Dumper(\%bits); sub loader{ # loads values into hash.. my $tail = shift; print 'In sub:'.Dumper($_[0]); $_[0]->{'one'}='thing_'.$tail; $_[0]->{'two'}='thong_'.$tail; print 'After add:'.Dumper($_[0]); }
    ciao, Veit Wehner