Why is the Hashref passed to the Net::Ping constructor, set to an empty hashref after Net::Ping->new($args)? - perl

What am I missing here?
When passing arguments to Net::Ping like this, then $args and $args_copy will both be set to an empty hashref after initializing the constructor Net::Ping->new($args).
use strict;
use warnings;
use Data::Dumper qw(Dumper);
use Net::Ping;
sub _ping {
my ($args) = #_;
my $p = Net::Ping->new($args);
$p->close();
}
my $args = { proto => 'udp' };
my $args_copy = $args;
print Dumper $args; # $VAR1 = { 'proto' => 'udp' }
print Dumper $args_copy; # $VAR1 = { 'proto' => 'udp' }
_ping($args);
print Dumper $args; # $VAR1 = {}
print Dumper $args_copy; # $VAR1 = {}
I see the same behavior on both Strawberry Perl and WSL2 running Ubuntu 20.04.4 LTS with Perl v5.30.0.

This is interesting, a class (constructor) deleting caller's data.
The shown code passes a reference to the Net::Ping constructor and that data gets cleared, and right in the constructor (see below).
To avoid having $args cleared, if that is a problem, pass its copy instead
_ping( { %$args } );
This first de-references the hash and then constructs an anonymous hash reference with it,† and passes that. So $args is safe.
The constructor new uses data from #_ directly (without making local copies), and as it then goes through the keys it also deletes them, I presume for convenience in further processing. (I find that scary, I admit.)
Since a reference is passed to new the data in the calling code can get changed.‡
† When copying a hash (or array) with a complex data structure in it -- when its values themselves contain references -- we need to make a deep copy. One way is to use Storable for it
use Storable qw(dclone);
my $deep_copy = dclone $complex_data_structure;
Here that would mean _ping( dclone $args );. It seems that new can only take a reference to a flat hash (or scalars) so this wouldn't be necessary.
‡ When a sub works directly with the references it gets then it can change data in the caller
sub sub_takes_ref {
my ($ref_data) = #_;
for my $k (keys %$ref_data) {
$ref_data->{$k} = ...; # !!! data in caller changed !!!
}
}
...
my $data = { ... }; # a hashref
sub_takes_ref( $data );
However, if a local copy of arguments is made in the sub then caller's data cannot be changed
use Storable qw(dclone); # for deep copy below
sub sub_takes_ref {
my ($ref_data) = #_;
my $local_copy_of_data = dclone $ref_data;
for my $k (keys %$local_copy_of_data) {
$local_copy_of_data->{$k} = ...; # data in caller safe
}
}
(Just remember to not touch $ref_data but to use the local copy.)
This way of changing data in the caller is of course useful when the sub is meant to work on data structures with large amounts of data, since this way they don't have to be copied. But when that is not the purpose of the sub then we need to be careful, or just make a local copy to be safe.

Related

Passing a hash to a subroutine without changing it input

I'm trying to debug some strange behavior while handling with a hash in Perl.
I'm passing a hash (not ref) to a subroutine and for some reason it updates it.
some_sub($a,%{$hash});
sub some_sub {
my ($a,%hash) = #_;
my #struct;
while (my ($dir, $data) = each %hash) {
foreach my $id (keys(%{$data})) {
my $entry = $data->{$id};
$entry->{id} = $id;
my $parent = $data->{$entry->{id}};
unless ($parent) {
push #struct, $entry
} else {
push #{$parent->{children}},$entry;
}
}
}
}
my %h= %{$hash};
print Dumper(\%h);
The sub some_sub does change %hash but only for the inner scope, so it should not change the data of the outside %hash. Also, I pass the hash as a hash and not as a hash ref. I suspected the sub some_sub inserts memory addresses into the %hash, but I'm not sure.
How should I debug and solve this issue?
EDIT: I also tried to pass a hash ref to the subroutine and do a dereferencing of the hash ref into another hash while doing all of the operations on the new hash.
Every value in a hash is a scalar. If you have a nested hash, the inner hash is stored as a scalar - a hash reference. Therefore, when changing the nested structures, the changes happen in the referenced hash, which is referenced from the original hash, too.
#! /usr/bin/perl
use warnings;
use strict;
sub change {
my %hash2 = #_;
for my $key (keys %hash2) {
++$_ for values $hash2{$key};
}
}
my %hash = (a => {b => 12, c => 24});
change(%hash);
use Data::Dumper; print Dumper \%hash;
Output:
$VAR1 = {
'a' => {
'b' => 13,
'c' => 25
}
};
The process of obtaining a structure that's similar as the original but contains different references is called cloning or deep copying. See Clone or dclone from Storable.
Arguments are passed to a function as a flat list of scalars, so
some_sub($a, %{$hashref})
has the keys and values of the hash passed as a list after $a
some_sub($a, key, value, ...);
since a function call always takes merely a list of scalars.
These key-value pairs are assigned to a hash in the function so when you work with that hash you directly use references from the calling code, your hash values. So data in the caller gets changed if those references are written to.
The details aren't given but in general one way to avoid changing caller's data in the sub is by introducing local variables for each reference the processing encounters. But then those may themselves contain references so you'd still need to be very careful.
It is simpler to make a full deep copy of the hash, ff the data structure isn't huge. For example
use Storable qw(dclone);
some_sub($v, $hashref);
sub some_sub {
my ($var, $hr) = #_;
my $cloned_hashref = dclone($hr);
# work away with $cloned_hashref
}

Moose object as a lazy hash

I want to replace some hashes used in my program with lazy Moose objects for performance reason. The problem is, there's a lot of code that I don't control that uses, of course, ->{key} to access elements. Such access works with Moose objects, but does not work with lazy ones before they were initialized:
package Stuff;
use Moose;
has foo => (
lazy=>1,
default=> sub { +{bar=>baz} }
);
package main;
my $x = Stuff->new();
print Dumper $x->{foo}; # undef =(
print Dumper $x->foo; # {bar => baz }
print Dumper $x->{foo}; # {bar => baz }
Any way to make that first $x->{foo} initialize the variable?
You can't use it as a hash directly, but with Tie::Moose you can create a hash which calls the underlying Moose methods. You can pass this hash along to your other code.
use Tie::Moose;
my $x = Stuff->new();
tie my %x, "Tie::Moose", $x;
print Dumper($x{foo}); # { 'bar' => 'baz' }
So you want code to run whenever a element of your hash object is accessed? That's possible using magic, but it will greatly slow down access to your object.

Manipulate Perl object by reference in subroutine

I have a Perl program and packages Worker and Log.
The Worker does almost all calculations, and I want to pass an object by reference to the Worker subroutine, as well as some other parameters (scalar and an array). I have seen examples like this and this.
They handle this by putting #_ in subs, then manipulating the object. I also found a way to manipulate them by using the index, like #{$_[i]}. Problem is, when I try the code like so, I get an error:
Can't call method "write" on unblessed reference at ...
Code snippets below.
Main:
use strict;
use warnings;
use Log;
use Worker;
my $log = Log->new();
my $worker = Worker->new();
my $scalar = "SomeURLhere";
my #array = ('red','blue','white');
# I do some stuff with $log object
#...
# Now I want to pass data to the Worker
$worker->subFromWorker($scalar, \$log, \#array);
Worker:
use strict;
use warnings;
package Worker;
sub new {
my $class = shift;
my $self = {};
bless $self, $class;
return $self;
}
sub subFromWorker{
my ($self) = shift;
my $scalar = $_[0];
#my ($log) = $_[1];
my #array = #{$_[2]};
foreach my $item (#array){
print $item;
}
$_[1]->write("The items from url $scalar are printed.");
#Same thing happens if I use $log here
}
In C#, this is handled in a different way - you can send a parameter to a method by value or by reference, and then do what you want in a specialized method (method is pre-written to handle parameters by reference or value). I thought that in Perl sending using \parameter will send the reference.
Objects are references. References are scalar values.
If you want to pass arrays or hashes into a subroutine then you usually want to pass references to them - because Perl parameter passing works far better with scalar values.
But $log is already a reference to your object. Therefore you don't need to take a reference to it. You end up passing a reference to a reference. So when you copy that parameter into $log inside your subroutine you have an extra, unnecessary, level of references.
The fix is to just pass the $log scalar into the subroutine.
$worker->subFromWorker($scalar, $log, \#array); # $log, not \$log
Everything else will then work fine.
You have read about the issues that prevent your program from working, but there are a few other things you should be aware of
Perl lexical identifiers and subroutine/method names consist of alphanumerics and underscore. Capital letters are reserved for global identifiers, such as package names like Worker and Log.
Packages that you use or require should end with the statement 1; so as to return a true value when they are imported, otherwise your program may fail to compile.
If a subroutine that you are writing happens to be a method, then it is clearest to start it by shifting off the $self parameter and making a copy of the rest:
my $self = shift;
my ($p1, $p2, $p3) = #_;
It is rare to use elements of #_ directly unless you're desperate for the minimal speed bonus
It is usually best to work directly with an array reference rather than copying the array, especially if it may be large.
Here is how I would code your program and associated modules:
program.pl
use strict;
use warnings;
use Worker;
use Log;
my $log = Log->new;
my $worker = Worker->new;
my $scalar = 'SomeURLhere';
my #array = qw/ red blue white /;
$worker->worker_method($scalar, $log, \#array);
Worker.pm
use strict;
use warnings;
package Worker;
sub new {
my $class = shift;
my $self = {};
bless $self, $class;
return $self;
}
sub worker_method {
my $self = shift;
my ($scalar, $log, $array) = #_;
foreach my $item (#$array) {
print $item, "\n";
}
$log->write("The items from URL $scalar are printed.");
}
1;
Log.pm
use strict;
use warnings;
package Log;
sub new {
my $class = shift;
bless {}, $class;
}
sub write {
my $self = shift;
my ($text) = #_;
print "Logging: $text\n"
}
1;
Output
red
blue
white
Logging: The items from URL SomeURLhere are printed.
A more common pattern is to use List assignment to unpack #_ into multiple variables all at once:
sub subFromWorker {
my ($self, $scalar, $log_ref, $array) = #_;
...
}
In reference to your specific problem:
my $log = Log->new();
$log is already a reference to your object, using \$log creates a reference to that reference which is not probably not what you want. You can handle this two ways:
only pass $log:
$worker->subFromWorker($scalar, $log, \#array);
dereference $log in subFromWorker before calling functions on it:
$$log_ref->write('...');

Filling hash by reference in a procedure

I'm trying to call a procedure, which is filling a hash by reference. The reference to the hash is given as a parameter. The procedure fills the hash, but when I return, the hash is empty. Please see the code below.
What is wrong?
$hash_ref;
genHash ($hash_ref);
#hash is empty
sub genHash {
my ($hash_ref)=(#_);
#cut details; filling hash in a loop like this:
$hash_ref->{$lid} = $sid;
#hash is generetad , filled and i can dump it
}
You might want to initialize hashref first,
my $hash_ref = {};
as autovivification happens inside function to another lexical variable.
(Not so good) alternative is to use scalars inside #_ array which are directly aliased to original variables,
$_[0]{$lid} = $sid;
And btw, consider use strict; use warnings; to all your scripts.
The caller's $hash_ref is undefined. The $hash_ref in the sub is therefore undefined too. $hash_ref->{$lid} = $sid; autovivifies the sub's $hash_ref, but nothing assigns that hash reference to the caller's $hash_ref.
Solution 1: Actually passing in a hash ref to assign to the caller's $hash_ref.
sub genHash {
my ($hash_ref) = #_;
...
}
my $hash_ref = {};
genHash($hash_ref);
Solution 2: Taking advantage of the fact that Perl passes by reference.
sub genHash {
my $hash_ref = $_[0] ||= {};
...
}
my $hash_ref;
genHash($hash_ref);
-or-
genHash(my $hash_ref);
Solution 3: If the hash is going to be empty initially, why not just create it in the sub?
sub genHash {
my %hash;
...
return \%hash;
}
my $hash_ref = genHash();

Passing hashes from a package to a method in another package and manipulating it in Perl

I have two packages. There is one hash in one package. I want to pass this hash to a method in another package, manipulate it and see the results in the previous package. Here's my code:
{
package Statistical_Analysis;
use Moose;
our $data;
our $ref;
our $k;
our $v;
sub countUseCase
{
my ($self, $value, $hash) = #_;
print "Passed value: ".$value."\n";
print "Hash Address: ".$hash."\n";
$self->{ref} = $hash;
$self->{%$ref}{'country'} = "something";
#print "IP Address: ".$self->{data}."\n";
#print "Hash Value: ".$self->{ref{'ip_count'}}."\n";
}
}
{
package Parse;
use Moose;
our %ip_address;
sub getFields
{
our $stanalyze_obj = Statistical_Analysis->new();
my $ref = \%ip_address;
$stanalyze_obj->countUseCase($ref);
dispHashMap();
}
sub dispHashMap
{
print \%ip_address."\n";
while ( my ($k,$v) = each %ip_address )
{
print "$k => $v\n";
}
}
But I cant see the changes in the hash. Any help?
You don't see any change because you never change it. Since it makes no sense, I presume you meant to change the $ip_address{country} when you do
$self->{%$ref}{'country'} = 'something';
If so, that should be
$hash->{country} = 'something';
Of course, $hash is stored in $self->{ref}, so you could also use
$self->{ref}->{country} = 'something';
which can be shortened to
$self->{ref}{country} = 'something';
PS — What's with all the our variables? You should almost never have to use our. #ISA and #EXPORT_OK are about the only uses I can think of. All of those should be my.
PSS — Actually, almost none of those should exist at all. What's with declaring variables you don't even use? One of these declarations is making your error a lot less obvious.
It seems that you called countUseCase with only one parameter, $ref. Calling that method with only one parameter, causes $hash to be undef.