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

This must not be too hard but I have not been able to figure it out for months. Let's say I have the perl script X.pl that uses module Y. Here is script X:
#!/usr/bin/perl use Y; use strict; my %hash_to_send; $hash_to_send{first_colour} = "red"; $hash_to_send{second_colour} = "white"; $hash_to_send{third_colour} = "blue"; return &Y::module_subroutine($hash_to_send);
Here is the module example:
#!/usr/bin/perl package Y; @ISA = qw(Exporter); @EXPORT_OK = qw(module_subroutine) sub module_subroutine { my($hash) = @_; return "The colour of the sky is ".$hash{third_colour}; }
However, it doesn't seem to get the hash succesfully. What am I doing wrong? I heard about using references to the hash instead? That's great but the reference would only be working in the script and not in the module right? Like here:
return &Y::module_subroutine(\$hash_to_send);
I'm lost! Any help is very very welcome.

Replies are listed 'Best First'.
Re: Transferring local hashes to module subroutines
by EvanK (Chaplain) on Aug 06, 2008 at 02:44 UTC
    In the call to the subroutine, you're passing a (nonexistent) scalar, not the hash you think you're passing:
    # you're doing: return &Y::module_subroutine($hash_to_send); # should be: return &Y::module_subroutine(%hash_to_send);
    As well, in the subroutine, you're assigning a scalar value, when you want a list context:
    # you're doing: my($hash) = @_; # should be: my %hash = @_;
    These kinds of things are easy to miss, which is why it's recommended to use the strict pragma...It should kick up warning messages in such cases.

     

    Addendum: Also wanted to add that if you're only ever passing a single hash to your subroutine, and it's not going to be an extremely large hash, then passing by value is workable. If you want to pass other parameters, or a massive hash (megabytes worth of data, yes I've seen this done), then you'd want to pass a reference:

    # passing it in... return &Y::module_subroutine(\%hash_to_send); # and assigning it within the subroutine... my $hash = shift; # and when using it in the sub, be sure to dereference... return "The colour of the sky is " . $hash->{third_colour};

    __________
    Systems development is like banging your head against a wall...
    It's usually very painful, but if you're persistent, you'll get through it.

Re: Transferring local hashes to module subroutines
by ikegami (Patriarch) on Aug 06, 2008 at 03:57 UTC
    Some background:
    %var - Hash $var{$key} - Hash element $var - Scalar. No relation to %var

    Excerpt from your code:

    return &Y::module_subroutine($hash_to_send); sub module_subroutine { my($hash) = @_; return "The colour of the sky is ". $hash{third_colour}; }

    Fix 1:

    return &Y::module_subroutine(%hash_to_send); <--- Use the right var sub module_subroutine { my(%hash) = @_; <--- Use the right type return "The colour of the sky is ". $hash{third_colour}; }

    The above copies the entire hash (%hash = %hash_to_send;). To avoid that cost, a reference is usually passed. Fix 2:

    return &Y::module_subroutine(\%hash_to_send); <--- Pass reference sub module_subroutine { my($hash) = @_; <--- A ref to a hash return "The colour of the sky is ". $hash->{third_colour}; <--- Must dereference }
      Thanks for the great replies. It's working well now. Is it possible though to send an extra scalar as parameter to the subroutine? I have not been able to get this working.
      sub module_subroutine { my (%hash,$scalar) = @_; return "The color of the ".$scalar." is ".$hash{color}; } my %hash_to_send; my $scalar = "water"; $hash_to_send{color} = "red"; print module_subroutine(%hash_to_send,$scalar);
      I am using strict now as suggested and it tells me there is an odd number of elements in the hash assignment at my (%hash,$scalar) = @_;. What is going on here?
        Understand that, as far as perl is concerned, passing an array or a hash is the same as passing a list of values. This is known as list context.
        # functionally speaking, this.... %hash = ('a' => 1, 'b' => 2, 'c' => 3); myfunc(%hash); # is the same as this... myfunc('a', 1, 'b', 2, 'c', 3);
        So you'll either want to put the scalar value first and shift it off:
        $scalar = 'blue'; %hash = ('a' => 1, 'b' => 2); myfunc($scalar, %hash); sub myfunc { my $scalar = shift; my %hash = @_; print $hash{'a'}; }
        Or pass arrays and hashes by reference, because when you pass a reference to an array or hash, the reference is a scalar context.
        %hash = ('a' => 1, 'b' => 2); $scalar = 'blue'; myfunc(\%hash, $scalar); sub myfunc { my($hashref,$scalar) = @_; print $hashref->{'a'}; }

        __________
        Systems development is like banging your head against a wall...
        It's usually very painful, but if you're persistent, you'll get through it.

        All functions accept only one thing as arguments: a list of scalar.

        When you do func(%hash), the hash gets flatten into a list. For example

        sub show_args { print(join(', ', @_), "\n"); } my %hash = ( a=>1, b=>2, c=>3 ); show_args(%hash); # c, 3, a, 1, b, 2

        Same goes if you also pass some scalars.

        my %hash = ( a=>1, b=>2, c=>3 ); show_args(%hash, "d", 4); # c, 3, a, 1, b, 2, d, 4

        So when you assign @_ to a hash (or an array), the hash absorbs the entire list. Perl has no way to know that only part of the assigned list should end up in the hash unless you tell it to.

        Passing a reference to the hash avoids this issue too.

        The problem is in the line my (%hash,$scalar) = @_ This is not going to work ("passing both in one go"). See Passing hash and hashref to sub for a recent discussion on the topic. As usual there are more solutions to the problem. I would advice to use a hashref.

Re: Transferring local hashes to module subroutines
by chromatic (Archbishop) on Aug 06, 2008 at 03:32 UTC

    If you had run the code you posted, strict would have given you a strong hint as to what the problem is:

    Global symbol "$hash_to_send" requires explicit package name at - line 11

    You're fortunate that it's easy to see the problem from the example code you gave, but we'll have an easier time answering your questions in the future if you post your actual code verbatim.