in reply to Parsing a hash into a sub

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

Replies are listed 'Best First'.
Re^2: Passing a hash into a sub
by shmem (Chancellor) on Jul 08, 2006 at 15:26 UTC
    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}