http://qs321.pair.com?node_id=231359

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

Hi

I've got a sub which calls another sub to generate and populate a hash. This parent sub then needs to use the hash generated in the child sub. Problem is, the parent sub doesn't seem to want to read the hash generated in the child sub correctly.

Here's my code:
sub foo { ... my %locations = &bar($dbh); ... my $location_text = $locations{$ref->{'location'}}; } sub bar { my $dbh = shift; my $sql_loc = qq|SELECT * FROM ndmw_location|; my $sth = $dbh->prepare($sql_loc); $sth->execute or die("..."); my %locations; while (my $ref = $sth->fetchrow_hashref()) { my $id = $ref->{'id'}; my $location = $ref->{'location'}; $locations{$id} = $location; } $sth->finish; return %locations; }
$location_text returns sometimes nothing and othertimes a seemingly random value.

I've put Data::Dumper into the parent sub and have observed that the hash comes back into the parent sub correctly so I'm at a loss as to why I can't get at that data.

If I throw all the code into one sub and not try and split it into sub-subs, it all works as expected.

Anyone got any ideas what I might be doing wrong?

Thanks!

- wil

Replies are listed 'Best First'.
Re: Passing a hash between subs
by broquaint (Abbot) on Jan 30, 2003 at 16:24 UTC
    Maybe it's just because you're copied and pasted the code but here ...
    my $location_text = $locations{$ref->{'location'}};
    ... where's the $ref hash ref coming from? And if it is there somewhere surely you want $ref->{id}?
    HTH

    _________
    broquaint

      That $ref is coming from data extracted from the database via:
      while (my $ref = $sth->fetchrow_hashref()) { ...


      - wil
        Aha! The problem there is that the $ref lives within the lexical scope of the while loop in the bar() sub. So once the while loop exits, $ref is no longer in scope. Even if it were declared about the while loop it would fall out of scope when bar() exits. What you probably intended was something like this
        sub foo { ... ## get $ref back from bar() my($ref, %locations) = &bar($dbh); ... my $location_text = $locations{$ref->{'location'}}; } sub bar { ... my $ref; while ($ref = $sth->fetchrow_hashref()) { my $id = $ref->{'id'}; my $location = $ref->{'location'}; $locations{$id} = $location; } $sth->finish; ## return $ref back to caller return $ref, %locations; }
        However now $ref only contains the hash ref from the last iteration of the while loop, or is that the desired result?
        HTH

        _________
        broquaint

Re: Passing a hash between subs
by Cabrion (Friar) on Jan 30, 2003 at 16:49 UTC
    You should . .
    use strict;
    $ref in foo() is out of scope becaue it is my'ed in bar(). When bar() returns $ref is garbage collected.

    OK folks, here his code with my comments added:

    sub foo { ... ########## Note that $ref does not exist(it's undef) ####### my %locations = &bar($dbh); ## Note that $ref did not get passed back from bar ## ... ########## $ref->{'location'} is undef, but 'use strict;' ########## here would have said: "can't use undef as hash" my $location_text = $locations{$ref->{'location'}}; } sub bar { my $dbh = shift; my $sql_loc = qq|SELECT * FROM ndmw_location|; my $sth = $dbh->prepare($sql_loc); $sth->execute or die("..."); my %locations; ########## Note that $ref IS my'ed here ########### while (my $ref = $sth->fetchrow_hashref()) { my $id = $ref->{'id'}; my $location = $ref->{'location'}; $locations{$id} = $location; } $sth->finish; return %locations; ########## Note that $ref goes away here ########### }
    And finally,

    If I throw all the code into one sub and not try and split it into sub-subs, it all works as expected.

    Yes, it does work for him. That's beacuse $ref does not go out of scope.

    Updated To make the problem/solution more clear.

Re: Passing a hash between subs
by davorg (Chancellor) on Jan 30, 2003 at 16:28 UTC

    If Data::Dumper shows the values coming back into %locations correctly, then I guess the problem is the key you look up ($ref->{location}). Have you checked that the key you're looking for is really in the hash? Are you perhaps being caught by invisible characters?

    --
    <http://www.dave.org.uk>

    "The first rule of Perl club is you do not talk about Perl club."
    -- Chip Salzenberg

Re: Passing a hash between subs
by OM_Zen (Scribe) on Jan 30, 2003 at 16:48 UTC
    Hi ,

    The $ref in the sub foo where you get it and I guess , from the way you populate your hash in the sub bar , you should be getting the location by having
    sub foo { my $location_text = $locations('id'); }


    The passing of hash can be through a reference too like

    sub bar { return \%locations; } sub foo { $locations = &bar($dbh); print "The reference returned from bar is : [$locations] +\n"; %location_Names = %$locations; }


    You can use some debugging to see the hash values in course of program like use Data::Dumper which will give you the hash's values in course of your program in tree forms itself , so that it shall be easy and helpful
Re: Passing a hash between subs
by Coruscate (Sexton) on Jan 30, 2003 at 16:58 UTC

    I know that this may not help you with the problem you've posted, but it is something that can become important. Then I'll nitpick one smaller thing.

    Your last line of the bar() sub is return %locations, so you are copying the data structure over to the foo() sub. This might take up a few extra cycles if the hash is quite large, so you might want to consider passing by reference instead. Change that last bar() line to return \%locations;, that first line of foo() to my $locations = bar($dbh);, and then dereference $locations to get at the values. My little nitpick is getting rid of the '&' symbol that is part of your call to foo(). Nasty, nasty, nasty (:


          C:\>shutdown -s
          >> Could not shut down computer:
          >> Microsoft is logged in remotely.
        

Re: Passing a hash between subs
by physi (Friar) on Jan 30, 2003 at 18:45 UTC
    In bar(), $idand $location, which fills your %location hash, are values and no references to anything.
    I think that they are the values from coloum 'id' and 'location' from your database !!!

    So if you want to get the location for i.e. id=4 you have to write  $location{4}
    But all in all you can do it a bit more easier I think:

    use DBI; my %location = map { $_->[0] => $_->[1] } @{$dbh->selectall_arrayref( +'select id, location from ndmw_location')};
    There's also a $dbh->selectall_hashref() possibility, but I can't figure out, how it works ;-)
    Maybe perlodoc DBI can help ?

    Hope this helps ?