views:

471

answers:

5

Hello,

I have a function that is doing some calculations and then passes some properties into another subroutine like so:

sub get_result {
    my $id = 1;     
    my %diet = ( result  => 28, 
                 verdict => 'EAT MORE FRUIT DUDE...'     
               );

    my %iq = ( result   => 193, 
               verdict => 'Professor Einstien'   
             );           
    print_result($id, %diet, %iq);
}

sub print_result {     
    my $id = shift;     
    my %d  = @_;     
    my %i  = @_;     

    print "IQ: $id\n";     
    print "DIET RESULT: $d{result}\n";     
    print "DIET VERDICT: $d{verdict}\n";     
    print "IQ RESULT: $i{result}\n";     
    print "IQ VERDICT: $i{verdict}\n";     
}

My problem is that the results printed in (DIET RESULT, DIET VERDICT) and (IQ SCORE, IQ RESULT) are both the same. As if variable %d and %i are being populated with the same variables. Any ideas why this is?

If I try shifting all three variables like so:

my $id = shift;     
my %d  = shift;     
my %i  = shift;

I get the following error:

Odd number of elements in hash assignment
+14  A: 

When you pass an array (or hash) to a subroutine, the subroutine will get a list of the values (or key values pairs). That's why you cannot pass two arrays (or two hashes), because the subroutine won't know where the first array ends and the second one starts.

To work around this problem, you should pass in references instead:

my %hash1 = ( foo => 'bar' );
my %hash2 = ( bar => 'baz' );
subroutine( \%hash1, \%hash2 );

sub subroutine {
    my ( $hashref1, $hashref2 ) = @_;
    print $hasref1->{foo}, $hashref2->{bar};
}

PS: Apart from the conceptual problem, your code also features this:

my %d  = @_;     
my %i  = @_;

If %d and %i are both assigned the same value, it shouldn't come as a surprise when they are the same afterwards.

innaM
This worked perfect. I think I was misinterpreting references between the various types. (Scalar, Array, Hash)Thanks Manni
It might be helpful for others if you accepted the answer then. (Not that I would mind the 25 reputation points in any way.)
innaM
A: 

You need to pass references to the hashes, then in the subroutine extract and de-reference them. I googled "pass two hashes into a sub" and found http://www.wdvl.com/Authoring/Languages/Perl/ProPerl/properl2-3.html as the first hit.

kbyrd
A: 

When you pass in %diet and %iq, they both get flattened into the arg array, so in your print_result, %d contains all items in %diet and %iq.

To solve, use references of the %diet and %iq:

print_result($id, \%diet, \%iq);

Then in print_result:

my $id = shift;
my %d  = %{+shift};
my %i  = %{+shift};
Chris Jester-Young
Your example is going to look for a hash named shift! Add parens after shift so the interpreter can tell you want a function call.
Matt Kane
%i isn't empty, it's a copy of %d because both are initialized from @_. See Manni's answer.
Michael Carman
This creates shallow copies of both hashes. This means that changes to the first level of the hash will not show up in the original, but changes in the second or later levels will. It looks like the hashes are simple hashes, so this should be fine for now (so long as any changes to the hashes are not expected to propagate). If you need deep copies, then take a look at the Storable module's dclone function (http://perldoc.perl.org/Storable.html).
Chas. Owens
Thanks all for helpful comments! I've incorporated Matt's and Michael's changes (though in perhaps unexpected ways :-P). Chas: Personally for my own code I'd rather work with the hash reference directly than shallow-copy even the first layer, but given the OP's original question (which passed the hashes directly into the arglist), this replicates the behaviour a little more "faithfully", for whatever _that_'s worth (in the context of this post). :-P
Chris Jester-Young
A: 
use strict;
use warnings;

sub get_result {

  ...

  print_result( $id, \%diet, \%iq );
  # or
  print_result( $id, {%diet}, {%iq} );
}


sub print_result{
  my( $id, $diet_h, $iq_h ) = @_;
  my %diet = %$diet_h;
  my %iq = %$iq_h;

  ...

}

Or:

use strict;
use warnings;

sub print_result($\%\%);

sub get_result{

  ...

  print_result($id, %diet, %iq);
}

sub print_result($\%\%){
  my( $id, $diet_h, $iq_h ) = @_;
  my %diet = %$diet_h;
  my %iq = %$iq_h;

  ...

}
Brad Gilbert
How about making the prototype answer not the first one they'd see? :)
brian d foy
+3  A: 

You might want to check out my book Intermediate Perl, about a third of which deals with references and how to work with them. This includes passing complex data structures into subroutines as well as other ways that references make your life easier. :)

brian d foy