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

I am having a hard time referencing a Hash that I have passed to a subroutine. Can anybody help?
#!/usr/local/bin/perl -w use strict; my @array = (); sub foobar() { $array[0] = { id => 1, status => 0, loc => 'awx1' }; $array[1] = { id => 2, status => 1, loc => 'xxx1' }; } sub fubar(%) { my (%myhash) = @_; printf "Thread <%d>, status <%d>, location <%s>\n", $myhash{id}, $ +myhash{status}, $myhash{loc}; } foobar(); my $nbr = 0; foreach (@array) { fubar(\$array[$nbr]); } print "Printing the array ...\n";

Replies are listed 'Best First'.
Re: Passing Hash to Subroutine
by theorbtwo (Prior) on Dec 28, 2003 at 20:53 UTC

    OK, reading through this code, I've got several reactions.

    First off, avoid giving your subs confusing names. The problem refered to in your subject line seems to be in fubar... but you never call fubar, only foobar. It took my second reading of the code before I noticed that problem...and on my third reading, I noticed that it wasn't a problem after all, and you did say fu where you wanted fu, and foo where you wanted foo.

    Secondly, don't use prototypes in perl unless you really know what you're doing, and why you need them. Your fubar will do what it appears you're trying to do if you simply remove the prototype (that is, (%) including the parenthises).

    Er, sorry, I'm really horrible today. It won't quite work.

    \$array[$nbr] creates a reference to the array element $array[$nbr]. You don't need that. $array[$nbr] is alreay a hashref there, so you're ending up a ref to a ref to a hash. In fact, what you want the way you've written fubar, assuming you get rid of that prototype, is just a plain hash. So you should call it with %{$array[$nbr]}. The %{...} construct returns the hash referenced by the thing inside the curly braces. You can also call it with just plain $array[$nbr], assuming you add in derefrencing -> arrows, as in $myhash->{id}.

    You could, if you really feel the need, continue writing with the prototypes, in which case, perl will play strange games behind your back... but don't do that.


    Warning: Unless otherwise stated, code is untested. Do not use without understanding. Code is posted in the hopes it is useful, but without warranty. All copyrights are relinquished into the public domain unless otherwise stated. I am not an angel. I am capable of error, and err on a fairly regular basis. If I made a mistake, please let me know (such as by replying to this node).

Re: Passing Hash to Subroutine
by mrpeabody (Friar) on Dec 28, 2003 at 23:06 UTC
    Your two biggest problems are:
    1. fubar()'s argument passing is really messed up. fubar() expects a plain hash, but you pass it a ref to a ref to a hash. One side or the other needs to be fixed. (Data::Dumper could have shown you this in two seconds.)

    2. Your foreach loop does the same thing in both passes. You need to assign the array elements to a temporary variable, and then use that variable in the loop. The default temporary variable is $_, but you can make it whatever you want using "foreach $temp (LIST) BLOCK".
    Some other comments, in no particular order:

    • foobar() alters global variables; This is confusing and difficult to maintain. Better to pass in the variables you want changed or create them inside the function.

    • foobar() and fubar() are terrible, undescriptive names for subroutines. Just name them after what they do. foobar() could be init_items(), while fubar() could be print_items().

    • Same goes for @array; Unfortunately I can't tell what it holds from your code, so I would call it @items (which is barely an improvement).

    • Forget prototypes exist until Perl 6 comes out.

    • print() is better than printf() 99.9% of the time. It's easier to read and you don't have to worry about casting.

    You need to read perldoc perlreftut, but also perldoc perlsub (argument passing conventions) and perldoc perlsyn (foreach syntax).

    Here is the fixed code. Notice how you can tell what the subroutines will do -- by their names -- before you even read them.

    #!/bin/perl use strict; use warnings; my @items = init_items(); print "Printing the array ...\n"; foreach my $item (@items) { print_items($item); } ###################### sub init_items { return ( { id => 1, status => 0, loc => 'awx1', }, { id => 2, status => 1, loc => 'xxx1', }, ); } sub print_items { my $item = shift; print "Thread <$item->{id}>, status <$item->{status}>, location <$ +item->{loc}>\n"; }
Re: Passing Hash to Subroutine
by ysth (Canon) on Dec 28, 2003 at 22:18 UTC
    You have several problems here.

    You are looping through @array, but only using element 0. Perhaps you meant foreach $nbr (0..$#array) as the loop or fubar(\$_) as the body?

    $array[0] and $array[1] are already hashrefs; when you have the \$array[$nbr] you end up passing a reference to a reference to a hash: probably not what you want. If you want a reference to a hash, just pass $array[$nbr]. If you want a copy of the keys and values of the hash (which is how your fubar is written), pass %{$array[$nbr]}.

    In fubar, you assign to a hash from @_, but @_ won't have the needed list of keys and values. You have a prototype which will mess you up, even if you were passing a flattened hash to fubar like fubar(%hash) or fubar(%{some hash reference}). I have to echo theorbtwo's recommendation: don't use prototypes unless you know what they do. Learn them later, if you want, after many other things.

    In summary, be consistent with one of these approaches:

    Flattened hash (has to copy all the keys and values twice, but may sometimes be more convenient):

    sub fubar { my %hash = @_; print $hash{key1}, $hash{key2}; } ... fubar(%{$arrayofhashes[$index]});
    Reference to hash:
    sub fubar { my $hashref = shift; print $hashref->{key1}, $hashref->{key2}; } ... fubar($arrayofhashes[$index]);
    For completeness, the prototype works this way (but don't use it!):
    sub fubar(%) { my $hashref = shift; print $hashref->{key1}, $hashref->{key2}; } ... fubar(%{$arrayofhashes[$index]});
Re: Passing Hash to Subroutine
by fx (Pilgrim) on Dec 28, 2003 at 20:37 UTC

    If I pass a reference to a hash into a sub, I keep working with the reference:

    some_sub(\%some_hash); sub some_sub { my $hash_ref = $_[0]; ## Now communicate with hash using: ## $hash_ref->{'SOME_KEY'} returns the value for SOME_KEY ## or ## $hash_ref->{'OTHER_KEY'} = "foo" will set a key to a value ## etc. ## Or the slightly more exciting: ## foreach $key (keys %{$hash_ref}) { ## do_something_with $hash_ref->{$key} }