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

Dear Monks, I have a sub in a self-written module: I use the Exporter module for ease of use. I am trying to pass on a scalar and hash to that sub at the same time.
myperl.pm
use myperl; $myresult=getInfo($myscalar, %myhash)
myperl.pm
package myperl; use strict; BEGIN { require Exporter; use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); $VERSION =0.1; @ISA =qw(Exporter); @EXPORT =qw( getInfo ) %EXPORT_TAGS =(); @EXPORT_OK =qw(); } sub getInfo { my $thescalar=shift; my %thehash=shift; ...etc... }
Could someone please show me the way to the light... This (obviously) does not work

Replies are listed 'Best First'.
Re: Parsing a hash into a sub
by Arunbear (Prior) on Jul 08, 2006 at 12:12 UTC
    my $thescalar=shift; my %thehash=shift;
    shift returns a single element of @_ (i.e. the first element), whereas you (presumably) want all parameters of getInfo except the first to go into %thehash. You can do that like this:
    my $thescalar = shift; # 1st parameter goes in $thescalar my %thehash = @_; # everything else goes in %thehash
    or even
    my ($thescalar, %thehash) = @_;
      Thanks for the quick reply... I used my %thehash=%{(shift)}; which does the trick as well... Should not have given up the search thanks again for replying
Re: Parsing a hash into a sub
by Hue-Bond (Priest) on Jul 08, 2006 at 12:22 UTC

    Some observations:

    • The code doesn't compile. It's missing a semicolon.
    • Modules must return a true value, or else perl will think something's wrong with it and refuse to continue.
    • The name of your module shouldn't be all lowercase, or you risk clashing with some future Perl pragma.
    • I don't think you need a BEGIN block
    • It's better to use our instead of vars.

    For passing the hash to the sub, you can use a reference. Try this module...

    ## file: Myperl.pm package Myperl; use strict; require Exporter; our $VERSION = 0.1; our @ISA = qw(Exporter); our @EXPORT = qw(getInfo); our %EXPORT_TAGS = (); our @EXPORT_OK = qw(); sub getInfo { my $thescalar=shift; my $thehash=shift; ## pick a reference print "scalar: $thescalar\nhash:\n"; print "$_ => ", $thehash->{$_}, "\n" for keys %$thehash; } 1;

    ...with this code:

    use Myperl; my $s=4; my %h=(0..5); my $myresult=getInfo($s, \%h); ## pass a reference

    --
    David Serrano

Re: Parsing a hash into a sub
by jdtoronto (Prior) on Jul 08, 2006 at 15:01 UTC
    The Perl 5 prototypes mechanism seems to be somewhat deprecated, at least within the commnity right now. We had something of a discussion in the CB this week and Far more than you ever wanted to know about prototypes has a lengthy, and substantial discussion about prototypes.

    TheDamian in his recent tome, Perl Best Pratices explains on pp194-196 just what can go wrong with code using prototypes and why. When I first started using Perl I used prototypes, for, oh, about the first week! I had come from 'C' and expected that this was how things should be done. My problem was the one pointed out on p195. If a prototype is given thus (stolen shamelessly from shmem's reply

    #!/usr/bin/perl sub getInfo ($\%) { my $scalar = shift; my $hash = shift; print "scalar = '$scalar'\n"; print "hash = $hash\n"; print "$_ => $hash->{$_}\n" for keys %$hash; } my %hash = (foo => 'bar', baz => 'quux'); my $scalar = 'string'; getInfo($scalar,%hash); __END__
    Will give the desired result. BUT there is a problem of magic being perpetrated here, the call to the sub,
    getInfo($scalar,%hash);
    implies that the hash is going to be "squashed" into the list context and unless you are aware that a prototype was being used you would expect to see
    my ( $thescalar, %thehash ) = @_;
    in the sub. The call to the prototyped function doesn't pass the hash at all! It passes a reference, but that fact is obscured. So by looking at the subroutine call we dont really know what the subroutine is doing.

    If we want to pass a hash reference, why not do it explicitly:

    sub getInfo { my ( $thescalar, $thehashref ) = @_; .... } getInfo( $scalar, \%hash );
    at least then everybody will know that the subroutine is expecting a hash and not a hash reference by some magic. Then if you want the hash itself to be copied in:
    sub getInfo { my ( $thescalar, %thehash ) = @_; .... } getInfo( $scalar, %hash );
    and then the subroutine definition and its invocation actually look alike! But don't believe me, search the Monastery on prototypes and read TheDamian.

    jdtoronto

      jdtoronto++, absolutely right. What gets passed are references. But it is just this fact which lets us do calls like
      getInfo $scalar, @array, %hash;
      and have the arguments not collapsed into a flat list:
      #!/usr/bin/perl package Foo; use Exporter; our @ISA = qw(Exporter); our @EXPORT = qw(getInfo); sub getInfo ($\@\%) { my $scalar = shift; my $array = shift; my $hash = shift; print "scalar = '$scalar'\n"; print "array = $array\n"; print "hash = $hash\n"; print '('.join(', ',map{"'$_'"} @$array).")\n"; print "$_ => $hash->{$_}\n" for keys %$hash; push @$array, 'unexpected'; } 1; __END__ # calling programm foo.pl #!/usr/bin/perl use Foo; my $scalar = 'string'; my @array = qw(di da doh); my %hash = (foo => 'bar', baz => 'quux'); getInfo $scalar, @array, %hash; print "passed array: ".join(', ',map{"'$_'"} @array).")\n"; __END__ # output scalar = 'string' array = ARRAY(0x8190b74) hash = HASH(0x81677bc) ('di', 'da', 'doh') baz => quux foo => bar passed array: 'di', 'da', 'doh', 'unexpected')

      True, the reference constructions are implicit and thus somewhat obscure, but I see them as a feature akin to autovivification. And the prototype check at compile time is something like a use strict for function arguments. The fact that the sub is able to modify the caller's arguments is something inherent to references and would be the same if we just passed references.

      It's arguably not a good practice to do calls like getInfo $scalar, @array, %hash instead of using references in the first place on both sides of the call, because normally $scalar, @array and %hash are flattened into a single list, and it is not visible from the caller's perspective what will happen on the other end.

      OTOH, if the arguments are flattened into a list and you do

      sub getInfo { my $scalar = shift; my %hash = @_; }

      you should check scalar @_ for evenness before assigning.

      But then, them's flavours, and as everywhere, the behaviour and calling conventions of subs / methods must be documented, and you must just know what you're doing... TIMTOWTDI ;-)

      greets,
      --shmem

      update: an excellent example using function prototypes is Embedding a mini-language for XML construction into Perl.

      _($_=" "x(1<<5)."?\n".q·/)Oo.  G°\        /
                                    /\_¯/(q    /
      ----------------------------  \__(m.====·.(_("always off the crowd"))."·
      ");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}
Re: Passing a hash into a sub
by shmem (Chancellor) on Jul 08, 2006 at 12:30 UTC

    You could use prototypes (see perlsub, caveats here). Consider:

    #!/usr/bin/perl sub getInfo ($\%) { my $scalar = shift; my $hash = shift; print "scalar = '$scalar'\n"; print "hash = $hash\n"; print "$_ => $hash->{$_}\n" for keys %$hash; } my %hash = (foo => 'bar', baz => 'quux'); my $scalar = 'string'; getInfo($scalar,%hash); __END__

    output:

    scalar = 'string' hash = HASH(0x81677a4) baz => quux foo => bar

    This has the advantage of throwing an error at compile time if the wrong arguments are passed in:

    my @array = qw(foo bar baz); getInfo($scalar, @array); Type of arg 2 to main::getInfo must be hash (not private array) at foo +.pl line 16, near "@array)"

    --shmem

    you mean Passing, not Parsing :-)

    update: inserted "You could" and caveat :-)

    _($_=" "x(1<<5)."?\n".q·/)Oo.  G°\        /
                                  /\_¯/(q    /
    ----------------------------  \__(m.====·.(_("always off the crowd"))."·
    ");sub _{s./.($e="'Itrs `mnsgdq Gdbj O`qkdq")=~y/"-y/#-z/;$e.e && print}
      Thanks it works now!