weird behavior when passing hashes to subroutine

P

Patrick Hartman

Hi all, I was working on a program today and made a sub routine that I
was passing a few scalars and also a hash to as arguments. This is
what I initially tried:

my %foo = qw(pepsi cola doctor pepper seven up);
my $bar = 'soda is yummy!';

&drink(%foo,$bar);

sub drink {
my (%sodas,$opinion) = @_;

# do whatever here
print "yummy!";
}

The result was not what I was expecting. The $bar value that I was
expecting to be the $opinion scalar ended up as a extra key in the
%sodas hash as undef, and $opinion was empty. I played around a little
and found that if I reverse the order so the scalar is passed before
the hash, it worked like I was hoping:

my %foo = qw(pepsi cola doctor pepper seven up);
my $bar = 'soda is yummy!';

&drink($bar,%foo);

sub drink {
my ($opinion,%sodas) = @_;

# do whatever here
print "yummy!";
}

Is there a rule as to what order to pass types to a subroutine? I'm a
little confused on this one. Thanks,

Patrick
 
S

sln

[snip]
Is there a rule as to what order to pass types to a subroutine? I'm a
little confused on this one. Thanks,

Patrick

There is no rules in Perl, just syntax. If it doesen't work like
you expect, Perl has no way of knowing, and thinks that's what you intend.
Use strict/warnings to help you out.

Basically, I think this is what you did:

my %sodas = (
# The never ending list
'pepsi',
'cola',
'doctor',
'pepper',
'seven',
'up',
'soda is yummy!',
# Add to the never ending list
);

And, you should have gotten an "Odd number of elements ..."
type of message.

If your looking for a "I before E, except after C"
rhyme, I can't think of any.

Also, you probably don't need ampersand before the function
call.

-sln
 
J

John Bokma

Patrick Hartman said:
Hi all, I was working on a program today and made a sub routine that I
was passing a few scalars and also a hash to as arguments. This is
what I initially tried:

my %foo = qw(pepsi cola doctor pepper seven up);
my $bar = 'soda is yummy!';

&drink(%foo,$bar);

sub drink {
my (%sodas,$opinion) = @_;

# do whatever here
print "yummy!";
}

The result was not what I was expecting. The $bar value that I was
expecting to be the $opinion scalar ended up as a extra key in the
%sodas hash as undef, and $opinion was empty. I played around a little
and found that if I reverse the order so the scalar is passed before
the hash, it worked like I was hoping:

my %foo = qw(pepsi cola doctor pepper seven up);
my $bar = 'soda is yummy!';

&drink($bar,%foo);

sub drink {
my ($opinion,%sodas) = @_;

# do whatever here
print "yummy!";
}

Is there a rule as to what order to pass types to a subroutine? I'm a
little confused on this one. Thanks,

You have to be aware that the hash eats up @_ (it's the same if you use
an array, i.e. my ( @foo, $bar ) = @_; will result in all of @_ ending
up in @foo).

You could do:

sub drink {
my $opinion = pop @_;
my %sodas = @_;
:
:
}

pop removes the last item from @_ (in this case) and assigns it to
$opionion.

You can also decide to pass a reference to your hash instead, see

perldoc perlreftut
 
S

sln

[snip]
Is there a rule as to what order to pass types to a subroutine? I'm a
little confused on this one. Thanks,

Patrick

There is no rules in Perl, just syntax. If it doesen't work like
you expect, Perl has no way of knowing, and thinks that's what you intend.
Use strict/warnings to help you out.

Basically, I think this is what you did:

my %sodas = (
# The never ending list
'pepsi',
'cola',
'doctor',
'pepper',
'seven',
'up',
'soda is yummy!',
# Add to the never ending list
);

Well, it would have been more like

drink(qw(pepsi cola doctor pepper seven up), 'soda is yummy').

That's seven arguments. Now when you do

my (%sodas, $opinion) = @_ ;

the %sodas hash is going to suck up all the items in the argument list,
and leave nothing for $opinion, so 1) $opinion is going to be undef, and
2) the last argument is treated as a key, and since there are no more
elements, that key gets a value of undef.

Reversing the order of arguments ($opinion, %sodas) works because
$opinion gets the first element of the list, and %sodas sucks down the
rest of 'em in key/value order.
And, you should have gotten an "Odd number of elements ..."
type of message.

That's a compile-time error, since the compiler has to know how many
elements are in the initializer in order to know if it's an odd number.
The compiler won't see a problem with %hash = @_ since it has no clue
how long @_ is going to be.

Don

Thanks, next time I won't do that (or do it a different way).

-sln
 
J

Jürgen Exner

Patrick Hartman said:
Hi all, I was working on a program today and made a sub routine that I
was passing a few scalars and also a hash to as arguments. This is
what I initially tried:

my %foo = qw(pepsi cola doctor pepper seven up);
my $bar = 'soda is yummy!';

&drink(%foo,$bar);

sub drink {
my (%sodas,$opinion) = @_;

# do whatever here
print "yummy!";
}

The result was not what I was expecting. The $bar value that I was
expecting to be the $opinion scalar ended up as a extra key in the
%sodas hash as undef, and $opinion was empty.

From "perldoc perlsub':
The Perl model for function call and return values is simple: all
functions are passed as parameters one single flat list of scalars,

Therefore at the point where you are doing
my (%sodas,$opinion) = @_;
there is no recollection that those values in @_ where once organized
into two distinct variables. And of ourse %s is grabbing as many as it
can.
sub drink {
my ($opinion,%sodas) = @_;

In this case $opinion will grab the first value (it can't grab any
others bedause its a scalar) and thus leaving the rest of the list for
%sodas.
Is there a rule as to what order to pass types to a subroutine? I'm a
little confused on this one.

That question is not really targeting the root issue. But as a rule of
thumb if you want to extract arguments like that then they have to have
a known lenght, i.e. be scalars or hashes or arrays of a known, fixed
length.
Then on top of that you can also get away with one single hash or array
of random length at the very end of the argument list. This is the case
in your second example.

If you have more than one of those list-like arguments with an arbitrary
number of elements then you have to pass references instead or resort to
awkward workarounds like e.g. passing the number of elements as an
additional argument.

jue
 
S

sreservoir

On Thu, 18 Feb 2010 16:43:38 -0800 (PST), Patrick Hartman

[snip]
Is there a rule as to what order to pass types to a subroutine? I'm a
little confused on this one. Thanks,

Patrick

There is no rules in Perl, just syntax. If it doesen't work like
you expect, Perl has no way of knowing, and thinks that's what you
intend.
Use strict/warnings to help you out.

Basically, I think this is what you did:

my %sodas = (
# The never ending list
'pepsi',
'cola',
'doctor',
'pepper',
'seven',
'up',
'soda is yummy!',
# Add to the never ending list
);

Well, it would have been more like

drink(qw(pepsi cola doctor pepper seven up), 'soda is yummy').

That's seven arguments. Now when you do

my (%sodas, $opinion) = @_ ;

the %sodas hash is going to suck up all the items in the argument list,
and leave nothing for $opinion, so 1) $opinion is going to be undef, and
2) the last argument is treated as a key, and since there are no more
elements, that key gets a value of undef.

Reversing the order of arguments ($opinion, %sodas) works because
$opinion gets the first element of the list, and %sodas sucks down the
rest of 'em in key/value order.
And, you should have gotten an "Odd number of elements ..."
type of message.

That's a compile-time error, since the compiler has to know how many
elements are in the initializer in order to know if it's an odd number.
The compiler won't see a problem with %hash = @_ since it has no clue
how long @_ is going to be.

no. it is not.

perl -We 'sub a { %_ = @_ } a(0)' -> warning
perl -e 'sub a { %_ = @_ } a(0)' -> nothing
 
U

Uri Guttman

PH> Hi all, I was working on a program today and made a sub routine that I
PH> was passing a few scalars and also a hash to as arguments. This is
PH> what I initially tried:

PH> my %foo = qw(pepsi cola doctor pepper seven up);
PH> my $bar = 'soda is yummy!';

PH> &drink(%foo,$bar);

read perldoc perlsub. that isn't doing what you think it does.

PH> sub drink {
PH> my (%sodas,$opinion) = @_;

actually that is the bad line. the hash will slurp in all of @_ (and
should give you an odd count warning IF you used warnings which you
should do). nothing will ever be assigned to $opinion.

PH> The result was not what I was expecting. The $bar value that I was
PH> expecting to be the $opinion scalar ended up as a extra key in the
PH> %sodas hash as undef, and $opinion was empty. I played around a little
PH> and found that if I reverse the order so the scalar is passed before
PH> the hash, it worked like I was hoping:

PH> my %foo = qw(pepsi cola doctor pepper seven up);
PH> my $bar = 'soda is yummy!';

PH> &drink($bar,%foo);

PH> sub drink {
PH> my ($opinion,%sodas) = @_;

PH> # do whatever here
PH> print "yummy!";
PH> }

PH> Is there a rule as to what order to pass types to a subroutine? I'm a
PH> little confused on this one. Thanks,

no rule about order. just you the first array or hash in the left side
of any assignment gets all the rest of the values from the right
side. so if you must have one, it should be the last thing on the left
side. or use references to pass multiple aggregates around.

uri
 
P

Patrick Hartman

You have to be aware that the hash eats up @_ (it's the same if you use
an array, i.e. my ( @foo, $bar ) = @_; will result in all of @_ ending
up in @foo).


Thanks John, that explanation made the most sense. I was unaware that
using a hash or array gobbled up all the @_ contents, this behavior
makes sense now I know that.

Thanks everyone else for the input and suggestions. Hash references
are still foreign to me (I have seen them but don't really understand
them). I am still working through the end of the Llama book, but I
have the Alpaca book waiting on my desk for when I am done. I know it
covers references so hoping that will give me some clarity.

I am still in the beginning of the learning process, so I appreciate
all the help on here.

Patrick
 
J

Justin C

Hi all, I was working on a program today and made a sub routine that I
was passing a few scalars and also a hash to as arguments. This is
what I initially tried:

my %foo = qw(pepsi cola doctor pepper seven up);
my $bar = 'soda is yummy!';

&drink(%foo,$bar);

see 'Pass by Reference' in perdoc perlsub

The scalar isn't a problem, it's the hash so I'd do:

&drink(\%foo, $bar); # passes a reference to the hash, not the hash

(someone will probably come along in a minute and tell you that you
shouldn't be using the & calling your subs unless you know why you need
to. I still don't understand this so I keep out of those discussions and
never use the & anyway).
sub drink { my ($foo, $bar) = @_;
my (%sodas,$opinion) = @_;

# do whatever here
dereference the hashref here (it's all in perlsub)
print "yummy!";
}

The result was not what I was expecting. The $bar value that I was
expecting to be the $opinion scalar ended up as a extra key in the
%sodas hash as undef, and $opinion was empty. I played around a little
and found that if I reverse the order so the scalar is passed before
the hash, it worked like I was hoping:

my %foo = qw(pepsi cola doctor pepper seven up);
my $bar = 'soda is yummy!';

&drink($bar,%foo);

sub drink {
my ($opinion,%sodas) = @_;

# do whatever here
print "yummy!";
}

Is there a rule as to what order to pass types to a subroutine? I'm a
little confused on this one. Thanks,

Patrick


Justin.
 
J

Jens Thoms Toerring

see 'Pass by Reference' in perdoc perlsub
The scalar isn't a problem, it's the hash so I'd do:
&drink(\%foo, $bar); # passes a reference to the hash, not the hash

But there's one important difference that the OP should keep in
mind: The original call

drink(%foo,$bar);

passes copies of the hash keys and values to the subroutine, thus
any changes done to them are local to that subroutine and don't
change anything about the original hash. But when one passes a hash
by reference then what the function receives is just a pointer to
that hash and thus all changes done to the hash via the reference
are done to the original hash in the calling function. So, if the
hash isn't changed in the function then passing a reference is fine
(and probably faster since the elements don't have to be copied),
but if that's not the case then passing a hash reference instead of
a list of its elements may lead to unexpected effects, i.e. the
%foo' hash suddenly being changed after a call of drink(). To
avoid that a copy of the original hash would have to be made and
a reference to that copy be passed to the function.

Regards, Jens
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,054
Latest member
TrimKetoBoost

Latest Threads

Top