perl list context of hash refs - perl

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
]
};

Related

Dereferencing Conditionally in Perl

I have a scalar that may or may not be a reference to an array. If it is a reference to an array, I would like to dereference it and iterate over it. If not, I would like to treat it as a one-element array and iterate over that.
my $result = my_complicated_expression;
for my $value (ref($result) eq 'ARRAY' ? #$result : ($result)) {
# Do work with $value
}
Currently, I have the above code, which works fine but feels clunky and not very Perlish. Is there a more concise way to express the idea of dereferencing a value with fallback behavior if the value is not what I expect?
Just force it before the loop.
Limited, known ref type
my $result = *some function call* // [];
$result = [$result] if ref $result ne 'ARRAY';
for my $val ( #$result ){
print $val;
}
Ref type unknown
#!/usr/bin/perl
use 5.012;
use strict;
no warnings;
sub array_ref;
my $result = [qw/foo bar foobar/];
# $result = 'foo'; # scalar test case
# $result = {foo=>q{bar}}; # hash test case
$result = array_ref $result;
for my $val ( #$result ){
say $val;
}
sub array_ref {
my $ref = shift;
given(ref $ref){
$ref = [%$ref] when('HASH');
$ref = [$ref] when(['SCALAR','']);
when('ARRAY'){}
default {
die 'Did not prepare for other ref types';
}
}
return $ref;
}
This is for demo purposes (you shouldn't use given/when in production code), but shows you could easily test for the ref type and cast a new response. However, if you really don't know what type of variable your function is returning, how are you sure it's even a reference. What if it was an array or hash?
Being perl, there's going to be several answers to this with the 'right' one being a matter of taste - IMHO, an acceptable shortening involves relying on the fact that the ref function returns the empty string if the expression given it is scalar. This means you don't need the eq 'ARRAY' if you know there are only two possibilities (ie, a scalar value and an array ref).
Secondly, you can iterate over a single scalar value (producing 1 iteration, obviously), so you don't have to put the $result in parentheses in the "scalar" case.
Putting these two small simplifications togeather gives;
use v5.12;
my $result1 = "Hello World";
my $result2 = [ "Hello" , "World" ];
for my $result ($result1, $result2) {
for my $value ( ref $result ? #$result : $result) {
say $value ;
}
}
which produces;
Hello World
Hello
World
There's likely to be 'fancier' things you can do, but this seems a reasonable compromise between being terse and readable. Of course, YMMV.
I see that I'm late to this, but I can't help it. With eval and $#, and the comma operator
my $ra = [ qw(a b c) ];
my $x = 23;
my $var = $ra;
# my $var = $x; # swap comment to test the other
foreach my $el ( eval { #{$var} }, $# && $var )
{
next if $el =~ /^$/; # when #$var is good comma adds empty line
print $el, "\n";
}
Prints a b c (one per line), if we swap to my $var = $x it prints 23.
When $var has the reference, the $# is empty but the comma is still executed and this adds an empty line, thus the next in the loop. Alternatively to skipping empty lines one can filter them out
foreach my $el ( grep { !/^$/ } eval { #{$var} }, $# && $var )
This does, in addition, clean out empty lines. However, most of the time that is desirable.
sub deref {
map ref($_) eq 'ARRAY'? #$_ : ref($_) eq 'HASH'? %$_ : $_, #_
}
sub myCompExpr {
1, 2, 3, [4, 5, 6], {Hello => 'world', Answer => 42}
}
print $_ for deref myCompExpr

Perl hash, array and references

I have this 3 lines of code in a sub and I'm trying to write them together on one line only.. but I'm quite lost
my %p = #_;
my $arr = $p{name};
my #a = #$arr;
what's the correct way of doing this?
thank you!
my %p = #_;
#_ is assumed to contain key-value pairs which are then used to construct the hash %p.
my $arr = $p{name};
The argument list is assumed to have contained something along the lines of name, [1, 2, 3,] so that $p{name} is an reference to an array.
my #a = #$arr;
Dereference that array reference to get the array #.
Here is an invocation that might work with this prelude in a sub:
func(this => 'that', name => [1, 2, 3]);
If you want to reduce the whole prelude to a single statement, you can use:
my #a = #{ { #_ }->{name} };
as in:
#!/usr/bin/env perl
use strict;
use warnings;
use YAML::XS;
func(this => 'that', name => [1, 2, 3]);
sub func {
my #a = #{ { #_ }->{name} };
print Dump \#a;
}
Output:
---
- 1
- 2
- 3
If the array pointed to by name is large, and if you do not need a shallow copy, however, it may be better to just stick with references:
my $aref = { #_ }->{ name };
OK so what you're doing is:
Assign a list of elements passed to the sub, to a hash.
extract a value from that hash (that appears to be an array reference)
dereference that into a standalone array.
Now, I'm going to have to make some guesses as to what you're putting in:
#!/usr/bin/perl
use warnings;
use strict;
use Data::Dumper;
sub test {
my %p = #_;
my $arr = $p{name};
my #a = #$arr;
print Dumper \#a;
}
my %input = ( fish => [ "value", "another value" ],
name => [ "more", "less" ], );
test ( %input );
So with that in mind:
sub test {
print join "\n", #{{#_}->{name}},"\n";
}
But actually, I'd suggest what you probably want to do is pass in the hashref in the first place:
#!/usr/bin/perl
use warnings;
use strict;
use Data::Dumper;
sub test {
my ( $input_hashref ) = #_;
print Dumper \#{$input_hashref -> {name}};
}
my %input = ( fish => [ "value", "another value" ],
name => [ "more", "less" ], );
test ( \%input );
Also:
Don't use single letter variable names. It's bad style.
that goes double for a and b because $a and $b are for sorting. (And using #a is confusing as a result).

In perl, can I dynamically create variables within a subroutine?

Background
In the code I'm writing, I'm passing data into methods using a hash-ref (see note [1]).
This, unfortunately, leads to a lot of repetitive code:
sub thing {
my ($self, $params) = #_;
my ($foo, $bar, $baz, $biff,);
if ( exists $params->{foo} && $params->{foo} ) {
$foo = $params->{foo};
}
# repeat for `bar`, `baz`, `biff`
## rest of function ##
}
(and duplicate in every function with parameters)
What I want to do
What would be far easier is to define a list of parameters, and then
iterate of that list, creating both the variables and setting them to a value if needed.
So to test this, I tried:
my $params = { x => 1, y => 2};
my #params = qw(x y z a b c);
gno strict 'refs';
rep( ${$_}, #params );
use strict 'refs';
foreach my $p (#params) {
if ( exists $params->{$p} && $params->{$p} ) {
${$p} = $params->{$p};
}
}
print "x:$x, y:$y, z:$z, a:$a, b:$b, c:$c\n"
which gives me the following error:
Global symbol "$x" requires explicit package name at ./test.pl line 20.
Global symbol "$y" requires explicit package name at ./test.pl line 20.
Global symbol "$z" requires explicit package name at ./test.pl line 20.
Global symbol "$c" requires explicit package name at ./test.pl line 20.
Can I do this dynamic variable creation thing? (and if so, how?)
[1] By using a hash to pass data in, I gain in many ways:
There is a clear indication of What each item of data is
The ORDER of the pieces of data is no longer important
I can miss one or more pieces of data, and I don't need to add in random undef values
I'm passing less data: 1 scalar (a reference) rather than multiple scalars
(I accept the danger of functions being able to change the parent's data, rather that mucking around with a copy of it...)
Yes, you can do this in Perl. But it's a terrible idea for all of the reasons explained by Mark Dominus in these three articles.
It's a far better idea to store these values in a hash.
#!/usr/bin/perl
use strict;
use warnings;
my $params = { x => 1, y => 2};
my #params = qw(x y z a b c);
my %var;
foreach my $p (#params) {
# You need to take care exactly what you want in this
# logical statement. The options are:
# 1/ $p exists in the hash
# exists $params->{$p}
# 2/ $p exists in the hash and has a defined value
# defined $params->{$p}
# 3/ $p exists in the hash and has a true value
# $params->{$p}
# I think the first option is most likely. The last one has
# good chance of introducing subtle bugs.
if ( exists $params->{$p} ) {
$var{$p} = $params->{$p};
}
}
print join ', ', map { "$_: " . ($var{$_} // 'undef') } #params;
print "\n";
It's a really bad idea to use symbolic references like this... hashes pretty well completely eliminate the need for this.
use warnings;
use strict;
my $params = { x => 1, y => 2, foo => 3, };
thing($params);
sub thing {
my $params = shift;
my $foo;
if (defined $params->{foo}){
$foo = $params->{foo};
}
print $foo;
}
You can also pass in a hash itself directly (whether it be pre-created, or passed inline to the sub. If pre-created, the sub will operate on a copy).
thing(foo => 1, x => 2);
sub thing {
my %params = #_;
print $params{foo} if defined $params{foo};
}
With thanks to Dave Cross & others - the following test works:
#!/usr/bin/perl
use strict;
use warnings;
use English qw( -no_match_vars ) ;
use Carp;
use Data::Dumper;
my $params = { x => 1, y => 2, z => 0};
my #params = qw(x y z a b c);
my %var;
foreach my $p (#params) {
if ( exists $params->{$p} ) {
$var{$p} = $params->{$p};
} else {
$var{$p} = undef;
}
}
print Dumper \%var;
This gives me %var with all desired parameters (as listed in #params, with the ones that are not passed in (ie, not in the $params hashref) created with an undef value.
Thus I can confidently test for value and truth, without worrying about existence.
Thank you all.
I did this using soft references:
#!perl
no strict "refs";
my %vars = ( x => 1, y => 2 );
for my $k ( keys %vars ) {
$$k = $vars{$k};
}
print $x, $y;
But there's a reason why the recommended settings (use strict; use warnings;) prevent this kind of pattern. It is easy to shoot yourself in the foot with it.
perl -Mstrict -MData::Dumper -wE'
{package Data::Dumper;our($Indent,$Sortkeys,$Terse,$Useqq)=(1)x4}
my #aok = qw( x y z a b c );
my %dft = ( a => -1 );
say "- - - -";
my $arg = { x => 1, y => 2, foo => 42 };
$arg = { %dft, %$arg };
say "arg: ", Dumper($arg);
my %var;
#var{ #aok } = #$arg{ #aok };
say "var: ", Dumper(\%var);
my %aok = map { $_ => 1 } #aok;
my #huh = grep !$aok{$_}, sort keys %$arg;
#huh and say "huh: ", Dumper(\#huh);
'
- - - -
arg: {
"a" => -1,
"foo" => 42,
"x" => 1,
"y" => 2
}
var: {
"a" => -1,
"b" => undef,
"c" => undef,
"x" => 1,
"y" => 2,
"z" => undef
}
huh: [
"foo"
]

Perl Passing arguments to subroutine not working

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";
}

Getting these loop errors in basic Perl

I have the following code in Perl. I am very new to the language:
#!/usr/bin/perl
use strict;
use warnings;
my $date = $ARGV[0];
my $symbols = ('A', 'B', 'C');
foreach $symbol (%symbols)
{
my $print = "$symbol";
print "$print";
}
Getting:
Useless use of a constant in void context at (line of %symbols)
and
Global symbol "$symbol requires explicit package name at ..."
and
Global symbol "%symbols" require explicit package. name at ..."
You are using an Hash when an Array is all that is needed.
#!/usr/bin/perl
use strict;
use warnings;
my $date = $ARGV[0];
my #symbols = ('A', 'B', 'C');
foreach my $symbol (#symbols)
{
print $symbol;
}
1) Your $symbols should be #symbols, since it's an array. Later in the foreach, %symbols should be #symbols.
2) The $symbol is not declared. Say foreach my $symbol... instead.
You are declaring $symbols instead of #symbols, so it is putting that in scalar context and setting it to 'C'. Then you try to loop through a hash with the same name, which you never created. Remember, $a (scalar), #a (array) and %a (hash) are all different.
This is what you wanted:
my #symbols = qw/ A B C /; ## the same as ( 'A', 'B', 'C' )
foreach my $symbol ( #symbols ) {
print $symbol;
}
Really quick:
my #symbols = qw/ A B C /; ## new array with three values
my $symbols = qw/ A B C /; ## new scalar that is the last element of the "A B C" list ($symbols = 'C')
my %symbols = (
A => 1,
B => 2,
C => 3,
); ## a hash with three key/value pairs
Your foreach is looking at each symbol in a non-existent hash called %symbols, not your array #symbols.
foreach $symbol (#symbols)
{
my $print = "$symbol";
print "$print";
}