Perl Passing arguments to subroutine not working - perl

I'm trying to pass parameters to a perl subroutine and for whatever reason inside the subroutine the parameters are coming out empty.
...
...
...
print "Passing arguments $a, $b, $c, $d \n";
beforeEnd($a, %b, $c, $d);
sub beforeEnd() {
my ($a, %b, $c, $d) = #_;
print "a is $a, b is $b, c is $c, d is $d \n";
}
The output of the print statements give me an idea that something is wrong. The weird part? The first 2 parameters are passing properly.
> Passing arguments 1, (1,2,3), 2, 3
> a is 1, b is (1,2,3), c is , d is
Any help would be greatly appreciated.

Because when you pass arguments into or out of a subroutine, any hashes and arrays are smashed flat.
You are assigning into %b which will gobble up any arguments.
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
sub test1 {
my ( $first, #rest, $last ) = #_;
print Dumper \#rest;
print "First = $first, last = $last, rest = #rest\n";
}
sub test2 {
my ( $first, $second ) = #_;
print "#$first ; #$second";
}
test1 ( 1, 2, 3, 4 );
test2 ( [1,2], [ 3,4] );
my #list1 = ( 1,2,3,4 );
my #list2 = ( 5,6,7,8 );
test1 ( #list1, #list2 );
test2 ( \#list1, \#list2 );
If you want to keep arrays or hashes intact, you need to either pass them by reference or as the last argument.
You would also probably get a warning if you turned on strict and warnings here - which is one of the reasons it's strongly recommended - because $b and %b are not the same. You'd also get a warning about an odd number of assignments:
Odd number of elements in hash assignment at line 5.
Use of uninitialized value $b in print

When arguments are passed to a Perl subroutine, they are flattened into a single List represented by #_. Conceptually, this means that if you are not passing references to arrays or hashes, you will "lose" some data. "Lose" is not exactly correct, because all of the data is still there; it is just not in the variable you expect. An example of this could be:
sub f {
my (#a, #b) = #_;
say 'a: ' . join(', ', #a);
say 'b: ' . join(', ', #b);
}
f( qw(1 2 3), qw(a b c) );
You will get the following output:
a: 1, 2, 3, a, b, c
b:
This is happening because the first array #a consumes all of the values from #_ and there a no more left to be stored in #b. The same thing is happening with the hash in your beforeEnd subroutine. The values of $c and $d are getting stored inside of %b. As an example since I can't see the variables values, if you passed
beforeEnd(1, ( a => 1, b => 2 ), 'c', 3);
inside your sub, you get something like this:
$a = 1
%b = ( a => 1, b => 2, c => 3 )
$c = undef
$d = undef
You can solve this by passing a reference to your hash %b:
beforeEnd($a, \%b, $c, $d);

Subroutines accept a list of scalar as arguments. If you pass an array or a hash, the contents of the array or hash is passed instead. That means that
f($a, %b, $c, $d)
is the same as
f($a, $b_key_1, $b_val_1, $b_key_2, $b_val_2, $b_key_3, $b_val_3, $c, $d);
How many of the scalars in #_ should be assigned to %b? Perl keeps it simple and assigns all remaining scalars, so
my ($a, %b, $c, $d) = #_;
is really no different than
my $a = $_[0]; # The first argument
my %b = #_[1..$#_]; # All but the first argument
my $c;
my $d;
It's best if you pass a reference to the hash. That avoids the problem, and it's far more efficient.
use Data::Dumper qw( Dumper );
sub beforeEnd {
my ($a, $b, $c, $d) = #_;
local $Data::Dumper::Terse = 1;
print "a is $a, b is ".Dumper($b).", c is $c, d is $d \n";
}
beforeEnd($a, \%b, $c, $d);
Off-topic comments about your code:
You had a prototype indicating no arguments are expected (), but you expect four. Rid yourself of that prototype.
You should avoid using $a and $b as variables as it can issues with sort.

The arguments can be passed in subroutine only as list of scalar variables.
We need to pass the reference of the hash (or an array, object) whenever passing the arguments to subroutine.
...
...
...
print "Passing arguments $a, $b, $c, $d \n";
beforeEnd($a, \%b, $c, $d);
sub beforeEnd() {
my ($a, $b, $c, $d) = #_;
print "a is $a, b is %$b, c is $c, d is $d \n";
}

Related

Passing multiple Hashes as argument to Perl subroutine ( Perl 5.16 / Perl 5.30)

We are trying to pass multiple hashes together with bunch of scalars as arguments to a subroutine. The problem is in multiple calls of this subroutine, ( and if we print the two hashes after we retrieved them inside the function), only one of them ( the first) is getting proper value. The second one is coming off as null. We tried bunch of things -
1 ) two separate hashes with different syntaxes from Stackoverflow/PerlMonks etc with pass on as reference.
&mySub(\%hash0, \%hash1, $var0, $var1, $var2);
sub mySub{
my (%hash0, %hash1, $var0, $var1, $var2) = (#_);
}
OR
&mySub(\%hash0, \%hash1, $var0, $var1, $var2);
sub mySub{
my %hash0 = %{$_[0]};
my %hash1 = %{$_[1]};
my $var0 = $[2]; my $var1 = $[3]; my $var3 = $[4];
}
2 ) Create array of two hashes and pass
my #joined_arr = (\%hash0, \%hash1);
&mySub (\#joined_arr, $var0, $var1, $var2);
sub mySub{
my (#joined_arr, $var0, $var1, $var2) = (#_);
my %hash0 = %{joined_arr[0]};
my %hash1 = %{joined_arr[1]};
# rest of the variables.
}
4 ) Create hash of hashes and pass
my %joined_hash;
%joined_hash{"first_one"} = %hash0;
%joined_hash{"second_one"} = %hash1;
&mySub (\%joined_hash, $var0, $var1, $var2);
sub mySub{
my %joined_hash, %hash0, %hash1;
%joined_hash = %{$_[0]};
%hash0 = %joined_hash{"first_one"};
%hash1 = %joined_arr{"second_one"};
# rest of the variables.
}
Try them in Perl 5.16 ( default distribution of CentOS 7) and hen in Perl 5.30.
Till this point, this hasn't been a success. If anybody has an idea and like to share, it will be great help.
EDIT
Following the suggestion of #zdim and #Polar Bear, I have tried these things ->
Syntax for offloading #_ into scalars inside function ->
a)
my ($ref_to_hash0, $ref_to_hash1, $var0, $var1, $var2) = #_;
b)
$ref_to_hash0 = shift;
$ref_to_hash1 = shift;
$var0 = shift;
$var1 = shift;
$var2 = shift;
I have also tried these 3 style of hash-reference to hash assignment.
a)
my %local_hash_shallow_copy = %$ref_to_hash0;
b)
my $local_hashref_deep_copy = dclone $ref_to_hash0;
c)
my %local_hash_shallow_copy = %{$ref_to_hash0};
It seems out of 9 iterations of this sub call, I am getting right hash inside the sub 2 times. At other times I simply get a pointer dumped -
$VAR1 = {
'HASH(0x1e32cc8)' => undef
};
I am using Dumper to dump the hashes just Outside - right before the sub call, and just Inside - right after I transferred the value from ref to actual hash. This should avoid any silly mistake.
Either I might be doing a very basic mistake here or hit upon an uncanny issue. Am debugging it.
FYI.
A function call in Perl passes a list of scalars as arguments, what you are correctly doing. The function receives a list of scalars in #_, which are aliases of those arguments.
So with the call†
mySub(\%hash0, \%hash1, $var0, $var1, $var2);
the function gets in #_ five scalars, the first two being the hash-references of interest.
But now you assign those to a hash!
sub mySub{
my (%hash0, %hash1, $var0, $var1, $var2) = (#_); # WRONG
}
So that %hash0 variable in the subroutine gets populated with everything in #_, like
# MySub variable # variables from the caller, (their aliases) passed in
my %hash0 = (\%hash0 => \%hash1, $var0 => $var1, $var2 => undef);
since consecutive scalars are assigned as key-value pairs. The rest of the variables in the list, starting with %hash1, are also introduced as lexical symbols in that subroutine and are undef.
And you should be getting a warning if the number of arguments is indeed odd. (There is use warnings; line on the top of your program, right?)
Instead, need to assign elements of #_ to suitable scalars, for example like
sub mySub{
my ($ref_to_hash0, $ref_to_hash1, $var0, $var1, $var2) = #_;
}
Now you have two options for how to work with this
Work directly with the reference(s), $ref_to_hash0. Like
foreach my $key (keys %$ref_to_hash0) {
$ref_to_hash0->{$key} ...
}
In short, this is
Efficient since you aren't copying data
It may be inefficient since every access needs to dereference
It allows you to change data in the caller by writing to $ref_to_hash0
$ref_to_hash0->{some_key} = 'value'; # changes data IN THE CALLER
This may be convenient, or dangerous (as it can be done by a mistake)
Make a local copy of the caller's data and work with that. Note that there may be a little catch with that, as you may need a deep copy
use Storable qw(dclone); # may be needed
sub mySub{
my ($ref_to_hash0, $refhash1, $var0, $var1, $var2) = #_;
# If the caller's %hash0 has no references for values just dereference
# Tricky though -- what when the calling hash changes in the future?
my %local_hash_shallow_copy = %$ref_to_hash0;
# If the caller's %hash0 is a complex data structure, need a deep copy
my $local_hashref_deep_copy = dclone $ref_to_hash0;
# Changes to local hash/hashref do not affect data in the caller
}
† There is generally no need for that leading & in front of the name in the question, omitted here, unless you mean to suppress its prototype in the call.
You can not to pass hash or an array into subroutine, instead you pass hash reference or array reference (\%hash or \#array).
In subroutine you should treat received argument as a reference. You can access data directly through reference $hashref->{key} or $arrayref->[index], or create a copy (more expensive on CPU cycles and memory) my %hash = %{$hashref} or my #array = #{$arrayref}.
Please see demonstration sample code bellow.
use strict;
use warnings;
use feature 'say';
use Data::Dumper;
my %hash_0 = ( data => [ 'it is snowing', 'this month', 'almost every day'] );
my %hash_1 = ( data => [ 1, 2, 5, 7, 11 ] );
my $var0 = 'The summer at it\'s end';
my $var1 = 3356;
my $var2 = 'Adorable child';
my $agregate = { hash_0 => \%hash_0,
hash_1 => \%hash_1,
var0 => $var0,
var1 => $var1,
var2 => $var2
};
subHashes_1(\%hash_0, \%hash_1, $var0, $var1, $var2);
say "\n";
subHashes_2(\%hash_0, \%hash_1, $var0, $var1, $var2);
say "\n";
subHashes_3($agregate);
say "\n";
subHashes_4(\%hash_0, \%hash_1, $var0, $var1, $var2);
sub subHashes_1 {
my($href_0, $href_1, $var0, $var1, $var2) = #_;
say '--- subHashes_1 --------------';
say 'Hash 0';
say Dumper($href_0->{data});
say 'Hash 1';
say '-' x 45;
say Dumper($href_1->{data});
say 'Var0: ' . $var0;
say 'Var1: ' . $var1;
say 'Var2: ' . $var2;
}
sub subHashes_2 {
my $href_0 = shift;
my $href_1 = shift;
my $var0 = shift;
my $var1 = shift;
my $var2 = shift;
say '--- subHashes_2 --------------';
say 'Hash 0';
say Dumper($href_0->{data});
say 'Hash 1';
say '-' x 45;
say Dumper($href_1->{data});
say 'Var0: ' . $var0;
say 'Var1: ' . $var1;
say 'Var2: ' . $var2;
}
sub subHashes_3 {
my $args = shift;
my $href_0 = $args->{hash_0};
my $href_1 = $args->{hash_1};
my $var0 = $args->{var0};
my $var1 = $args->{var1};
my $var2 = $args->{var2};
say '--- subHashes_3 --------------';
say 'Hash 0';
say Dumper($href_0->{data});
say 'Hash 1';
say '-' x 45;
say Dumper($href_1->{data});
say 'Var0: ' . $var0;
say 'Var1: ' . $var1;
say 'Var2: ' . $var2;
}
sub subHashes_4 {
my $href_0 = shift;
my $href_1 = shift;
my $var0 = shift;
my $var1 = shift;
my $var2 = shift;
say '--- subHashes_4 --------------';
say 'Hash 0';
say "\t$_ => " . join("\n\t",#{$href_0->{$_}}) for (keys %$href_0);
say 'Hash 1';
say '-' x 45;
say "\t$_ => " . join("\n\t",#{$href_1->{$_}}) for (keys %$href_1);
say 'Var0: ' . $var0;
say 'Var1: ' . $var1;
say 'Var2: ' . $var2;
}
Output
--- subHashes_1 --------------
Hash 0
$VAR1 = [
'it is snowing',
'this month',
'almost every day'
];
Hash 1
---------------------------------------------
$VAR1 = [
1,
2,
5,
7,
11
];
Var0: The summer at it's end
Var1: 3356
Var2: Adorable child
--- subHashes_2 --------------
Hash 0
$VAR1 = [
'it is snowing',
'this month',
'almost every day'
];
Hash 1
---------------------------------------------
$VAR1 = [
1,
2,
5,
7,
11
];
Var0: The summer at it's end
Var1: 3356
Var2: Adorable child
--- subHashes_3 --------------
Hash 0
$VAR1 = [
'it is snowing',
'this month',
'almost every day'
];
Hash 1
---------------------------------------------
$VAR1 = [
1,
2,
5,
7,
11
];
Var0: The summer at it's end
Var1: 3356
Var2: Adorable child
--- subHashes_4 --------------
Hash 0
data => it is snowing
this month
almost every day
Hash 1
---------------------------------------------
data => 1
2
5
7
11
Var0: The summer at it's end
Var1: 3356
Var2: Adorable child

Assignment of multiple array subroutine parameters in Perl doesn't work

I'm confused about perl subroutine parameters in this example
when i use references in subroutine parameters it works:
#a = ( 1, 2 );
#b = ( 5, 8 );
#c = add_vecpair( \#a, \#b );
print "#c\n";
print $a[0];
sub add_vecpair { # assumes both vectors the same length
my ( $x, $y ) = #_; # copy in the array references
my #result;
#$x[0] = 2;
for ( my $i = 0; $i < #$x; $i++ ) {
$result[$i] = $x->[$i] + $y->[$i];
}
return #result;
}
but when i don't use references as parameters like this:
#a = ( 1, 2 );
#b = ( 5, 8 );
#c = add_vecpair( #a, #b );
print "#c\n";
print $a[0];
sub add_vecpair { # assumes both vectors the same length
my ( #x, #y ) = #_; # copy in the array references
my #result;
print #y;
for ( my $i = 0; $i < #x; $i++ ) {
$result[$i] = $x[$i] + $y[$i];
}
return #result;
}
..it doesn't work. When do i need to use references as subroutine parameters?
Short version: The issue is this line:
my (#x, #y) = #_;
Assignments are greedy. #x is treated first, and is given as many values from #_ as it can handle. And as it can handle all of them, it ends up getting all of the contents of #_, and #y get none.
The result is the same as this:
my #x = #_; # Gets all of the arguements
my #y; # Gets nothing, and is therefore declared but uninitialized.
This is why using references is recommended when subroutines take more than one value as arguement, and at least one of those values are arrays or hashes.
Longer version:
#_ is a composite of all of the arguements passed to the subroutine, so the original container doesn't matter. Consider the code snippets below. The first one is yours, the second one does the exact same thing, but more clearly displays what is happening.
#a = (1, 2);
#b = (5, 8);
add_vecpair(#a,#b);
....is the same as:
add_vecpair(1, 2, 5, 8);
To further hilight the problem, hashes get really messy if treated this way:
%a = ('a' => 1,
'b' => 2);
%b = ('c' => 3,
'd' => 4);
somefunction(%a, %b);
...is the same as:
somefunction('a', 1, 'b', 2, 'c', 3, 'd', 4);
When you call Perl subroutines with array or hash parameters, they are flattened out to a single list. Therefore in the second case your two array parameters loose their identities and #_ becomes a single array with the elements of both #a and #b.

What do dollar, at-sign and semicolon characters in Perl parameter lists mean?

I have encountered a number of Perl scripts in the codebase at my job. Some of them contain subroutines with the following syntax oddity:
sub sum($$$) {
my($a,$b,$m)=#_;
for my $i (0..$m) {
$$a[$i] += $$b[$i] if $$b[$i] > 0;
}
}
sub gNode($$;$$) {
my($n,$l,$s,$d) = #_;
return (
"Node name='$n' label='$l' descr='$d'" ,
$s ? ("Shape type='$s' /") : (),
'/Node'
);
}
sub gOut($$#) {
my $h = shift;
my $i = shift;
if ($i > 0) {
print $h (('')x$i, map '<'.$_.'>', #_);
} else {
print $h map '<'.$_.'>', #_;
}
}
Leaving aside the question of what these subroutines are meant to do (I'm not entirely sure myself...), what do the sequences of characters in the 'parameter list' position mean? Viz. the $$$, $$;$$ and $$# sequences in these examples.
I have a very limited understanding of Perl, but I believe that the my($a,$b,$m)=#_; line in the first example (sum) unpacks the parameters passed to the subroutine into the $a, $b and $m local variables. This suggests that the $$$ indicates the arity and type signature of sum (it expects three scalars, in this case). This would potentially suggest that gOut expects two scalars and an array. Is this the correct interpretation?
Even if the above interpretation is correct, I'm lost as to the meaning of the semicolon in the second routine (gNode).
See perldoc perlsub entry on Prototypes.
# Declared as Called as
sub mylink ($$) mylink $old, $new
sub myvec ($$$) myvec $var, $offset, 1
sub myindex ($$;$) myindex &getstring, "substr"
sub mysyswrite ($$$;$) mysyswrite $buf, 0, length($buf) - $off, $off
sub myreverse (#) myreverse $a, $b, $c
sub myjoin ($#) myjoin ":", $a, $b, $c
sub mypop (+) mypop #array
sub mysplice (+$$#) mysplice #array, 0, 2, #pushme
sub mykeys (+) mykeys %{$hashref}
sub myopen (*;$) myopen HANDLE, $name
sub mypipe (**) mypipe READHANDLE, WRITEHANDLE
sub mygrep (&#) mygrep { /foo/ } $a, $b, $c
sub myrand (;$) myrand 42
sub mytime () mytime
Don't forget: This is all very powerful, of course, and should be used only in moderation to make the world a better place.
I agree with the rest: don't use sub prototypes unless you know what you're doing. "With great power comes great responsibility." Those look like they were created by someone used to C prototypes. For example, the sub sum really should have this prototype:
sub sum (\$\$\$) {

implicit-conversion in perl

I am new to Perl, can anyone explain the following scripts for me please:
#!/usr/bin/env perl
use strict;
use warnings;
sub f1($) { my ($v) = #_; print "f1 $v\n"; }
sub f2(#) { my ($v) = #_; print "f2 $v\n"; }
my $s = "ww";
my #a = ("xx", "yy", "zz");
f1 $s; f1 #a; f2 $s; f2 #a;
The output in my computer is :
f1 ww
f1 3
f2 ww
f2 xx # why!!
Can anyone explain why the fourth output is xx? I thought it should be zz, since when array converts to scalar, it should be the last element of array.
No, with a statement such as:
my ($v, $foo, $bar) = #_;
$v will be assigned the first value in the #_ array, $foo the second, and so on. This is because the parentheses impose a list context. Any excess values will be ignored, unless one of your variables is an array, in which case it will slurp all remaining values.
my ($v, #foo, $bar) = #_; # wrong! $bar will never get any value
$v will get the first value, #foo all the rest. $bar will be undefined.
You may be thinking of assignment using a list:
my $v = qw(a b c);
But this is wrong, and will cause an error:
Useless use of a constant (a) in void context at -e line 1.
Useless use of a constant (b) in void context at -e line 1.
This is because the LHS using scalar context, it will (more or less) be similar to:
'a';
'b';
my $v = 'c';
You may notice that if we impose list context by putting $v inside parentheses, we get a different result:
my ($v) = qw(a b c); # $v is now 'a'
ETA: Regarding prototypes:
In f1, what you see is that the array is forced into scalar context because the subroutine expects a scalar argument. That is why f1 with an array prints 3 (the size). When the prototype looks for an array, the array remains in default list context, and the assignment is done as per normal (as described above).
As an extra note: prototypes have a very specific use, to make subroutines behave more like certain built-ins with regard to argument handling. Such as sort { code here } or push #array, $foo.
If this is not what you are after, you should skip prototypes all together and simply write:
sub f1 {
...
}
Documentation here
Perl array behaves in 2 differents flavours, regarding context:
Scalar context:
my $a = #tab; # get the array length
Array context:
my #newTab = #tab; # newTab is a copy of tab
Which can be reworded like this:
# 3 scalars in an array context (see parens) gets the contents of the tab
my ($a,$b,$c) = #tab;
Here, since #tab can be wider than the number of scalars, these scalars are populated from the beginning of the tab (and not from the end). So your code:
my ($a) = #tab;
will echo the first element of the tab
An array can't be converted to a scalar. An array can be evaluated in scalar context, though.
An array evaluated in scalar context does not return the last element of the array. As you saw with f1, it returns the number of elements in the array.
$ perl -E'my #a = qw( xx yy zz ); say #a; say scalar(#a);'
xxyyzz
3
Neither array is being evaluated in scalar context. An argument list expression is evaluated in list context.
f(#a);
is the same as
f($a[0], $a[1], $a[2]);
The RHS of a list assignment in evaluated in list context.
my ($v) = #_;
is the same as
my ($v) = ($_[0], $_[1], $_[2]);
which is the same as
my ($v, undef, undef) = ($_[0], $_[1], $_[2]);

perl list context of hash refs

Why does this work? that is line 2
DB<1> $a = {'a'=>1}; $b = {'a'=>2, 'b'=>0};
DB<2> $c = ($a, $b);
DB<3> print $c;
HASH(0x8743e68)
DB<4> print $c->{a},$c->{b};
20
I understand if I carefully use %$a and %$b that perl would know what I meant, but with just bare refs in the list, why does it work?
Or maybe it just looks like it works and I really did something else?
There is no list context in
$c = ($a, $b);
Instead, what you are seeing is the comma operator in action:
Binary "," is the comma operator. In scalar context it evaluates its left argument, throws that value away, then evaluates its right argument and returns that value. This is just like C's comma operator.
To see this more clearly, take a look at:
#!/usr/bin/perl
use strict; use warnings;
my $x = {a => 1};
my $y = {a => 2, b => 0};
my $z = ($x, $y);
print "\$x = $x\t\$y = $y\t\$z = $z\n";
my #z = ($x, $y);
print "#z\n";
First, I used warnings. Therefore, when I run this script, I get the warning:
Useless use of private variable in void context at C:\Temp\t.pl line 7.
Always enable warnings.
Now, the output shows what's happening:
$x = HASH(0x39cbc) $y = HASH(0x39dac) $z = HASH(0x39dac)
HASH(0x39cbc) HASH(0x39dac)
Clearly, $z refers to the same anonymous hash as does $y. No copying of values was done.
And, $z[0] refers to the same anonymous hash as does $x and $z[1] refers to the same anonymous has as do $y and $z.
Note that parentheses alone do not create list context. In the case of
my #z = ($x, $y);
they are necessary because = binds more tightly than the comma operator.
my #z = $x, $y;
would assign $x to $z[0] and discard $y (and emit a warning) whereas
my #z = 1 .. 5;
would work as expected.
Finally, if you wanted to assign to $z a new anonymous hash which contains copies of the anonymous hashes to which both $x and $y point, you would do
#!/usr/bin/perl
use strict; use warnings;
use Data::Dumper;
my $x = {a => 1};
my $y = {a => 2, b => 0};
my $z = { %$x, %$y };
print Dumper $z;
which would output:
$VAR1 = {
'a' => 2,
'b' => 0
};
because hash keys are, by definition, unique. If you want to preserve all values associated with the keys of both hashes, you need to do something slightly more complicated (and use anonymous arrayrefs as values in the "union" hash):
#!/usr/bin/perl
use strict; use warnings;
use Data::Dumper;
my $x = {a => 1};
my $y = {a => 2, b => 0};
my $z;
push #{ $z->{$_} }, $x->{$_} for keys %$x;
push #{ $z->{$_} }, $y->{$_} for keys %$y;
print Dumper $z;
Output:
VAR1 = {
'a' => [
1,
2
],
'b' => [
0
]
};