How to use a 'subroutine reference' as a hash key - perl

In Perl, I'm learning how to dereference 'subroutine references'. But I can't seem to use a subroutine reference as a hash 'key'.
In the following sample code,
I can create a reference to a subroutine ($subref) and then dereference it to run the subroutine (&$subref)
I can use the reference as a hash 'value' and then easily dereference that
But I cannot figure out how to use the reference as a hash 'key'. When I pull the key out of the hash, Perl interprets the key as a string value (not a reference) - which I now understand (thanks to this site!). So I've tried Hash::MultiKey, but that seems to turn it into an array reference. I want to treat it as a subroutine/code reference, assuming this is somehow possible?
Any other ideas?
use strict;
#use diagnostics;
use Hash::MultiKey;
my $subref = \&hello;
#1:
&$subref('bob','sue'); #okay
#2:
my %hash;
$hash{'sayhi'}=$subref;
&{$hash{'sayhi'}}('bob','sue'); #okay
#3:
my %hash2;
tie %hash2, 'Hash::MultiKey';
$hash2{$subref}=1;
foreach my $key (keys %hash2) {
print "Ref type is: ". ref($key)."\n";
&{$key}('bob','sue'); # Not okay
}
sub hello {
my $name=shift;
my $name2=shift;
print "hello $name and $name2\n";
}
This is what is returned:
hello bob and sue
hello bob and sue
Ref type is: ARRAY
Not a CODE reference at d:\temp\test.pl line 21.

That is correct, a normal hash key is only a string. Things that are not strings get coerced to their string representation.
my $coderef = sub { my ($name, $name2) = #_; say "hello $name and $name2"; };
my %hash2 = ( $coderef => 1, );
print keys %hash2; # 'CODE(0x8d2280)'
Tieing is the usual means to modify that behaviour, but Hash::MultiKey does not help you, it has a different purpose: as the name says, you may have multiple keys, but again only simple strings:
use Hash::MultiKey qw();
tie my %hash2, 'Hash::MultiKey';
$hash2{ [$coderef] } = 1;
foreach my $key (keys %hash2) {
say 'Ref of the key is: ' . ref($key);
say 'Ref of the list elements produced by array-dereferencing the key are:';
say ref($_) for #{ $key }; # no output, i.e. simple strings
say 'List elements produced by array-dereferencing the key are:';
say $_ for #{ $key }; # 'CODE(0x8d27f0)'
}
Instead, use Tie::RefHash. (Code critique: prefer this syntax with the -> arrow for dereferencing a coderef.)
use 5.010;
use strict;
use warnings FATAL => 'all';
use Tie::RefHash qw();
my $coderef = sub {
my ($name, $name2) = #_;
say "hello $name and $name2";
};
$coderef->(qw(bob sue));
my %hash = (sayhi => $coderef);
$hash{sayhi}->(qw(bob sue));
tie my %hash2, 'Tie::RefHash';
%hash2 = ($coderef => 1);
foreach my $key (keys %hash2) {
say 'Ref of the key is: ' . ref($key); # 'CODE'
$key->(qw(bob sue));
}

From perlfaq4:
How can I use a reference as a hash key?
(contributed by brian d foy and Ben Morrow)
Hash keys are strings, so you can't really use a reference as the key.
When you try to do that, perl turns the reference into its stringified
form (for instance, HASH(0xDEADBEEF) ). From there you can't get back
the reference from the stringified form, at least without doing some
extra work on your own.
Remember that the entry in the hash will still be there even if the
referenced variable goes out of scope, and that it is entirely
possible for Perl to subsequently allocate a different variable at the
same address. This will mean a new variable might accidentally be
associated with the value for an old.
If you have Perl 5.10 or later, and you just want to store a value
against the reference for lookup later, you can use the core
Hash::Util::Fieldhash module. This will also handle renaming the keys
if you use multiple threads (which causes all variables to be
reallocated at new addresses, changing their stringification), and
garbage-collecting the entries when the referenced variable goes out
of scope.
If you actually need to be able to get a real reference back from each
hash entry, you can use the Tie::RefHash module, which does the
required work for you.
So it looks like Tie::RefHash will do what you want. But to be honest, I don't think that what you want to do is a particularly good idea.

Why do you need it? If you e.g. need to store parameters to the functions in the hash, you can use HoH:
my %hash;
$hash{$subref} = { sub => $subref,
arg => [qw/bob sue/],
};
foreach my $key (keys %hash) {
print "$key: ref type is: " . ref($key) . "\n";
$hash{$key}{sub}->( #{ $hash{$key}{arg} } );
}
But then, you can probably choose a better key anyway.

Related

Perl: passing hash by ref using rule1

I am still unclear about why by ref portion is showing undefined value for %Q and $_ uninitialized. I have been looking through perlreftut and still unable to see what I have done wrong. Passing the hash as a flat array has no issue.
Doing it by ref with testRef(\%mkPara) passes a scalar hash reference to the subroutine, right? So, does my %Q = %{$_} not turn it back into a hash?
use strict;
use diagnostics;
use warnings;
my %mkPara = ('aa'=>2,'bb'=>3,'cc'=>4,'dd'=>5);
sub testFlat
{
my %P = #_;
print "$P{'aa'}, $P{'bb'}, ", $P{'cc'}*$P{'dd'}, "\n";
}
sub testRef
{
my %Q = %{$_}; #can't use an undefined value as HASH reference
#print $_->{'aa'}, "\n";#Use of uninitialized value
print $Q{'aa'},"\n";
}
#testFlat(%mkPara);
testRef(\%mkPara);
When you use arguments in a function call (\%mkPara in your case), you can access them through #_ array inside the function.
Here, you pass a single argument to the function : \%mkPara, which you can then access by accessing the first element of #_ by using $_[0].
$_ is the default variable for some builtin functions/operators (print, m//, s///, chomp and a lot more). Usually seen in while or for loops. But in your code, you have no reason to use it (you are never setting it to anything, so it's still set to undef, hence the error "Can't use an undefined value as a HASH reference".
So your function should actually be :
sub testRef
{
my %Q = %{$_[0]}; # instead of %{$_}
print $_[0]->{'aa'}, "\n"; # instead of $_->{'aa'}
print $Q{'aa'},"\n";
}
If needed, you can find more about functions on perlsub.
However, as #Ikegami pointed out in the comments, using my %Q = %{$_[0]}; creates a copy of the hash you sent to the function, which in most cases (including that one where you just print a key of the hash) is very suboptimal as you could just use a hashref (like you are doing when you do $_[0]->{'aa'}).
You can use hash references like this (roughly the same example as the answer of #Zaid) :
sub testRef
{
my ( $Q ) = #_;
print $Q->{aa} ;
print $_, "\n" for keys %$Q;
}
testRef(\%mkPara);
There are quite a lot of resources about references online, for instance perlreftut that you were already looking at.
This can seem a bit tricky at first, but the reason is that $_ is not the same as #_.
From perlvar:
$_ is the implicit/"default" variable that does not have to be spelled out explicitly for certain functions (e.g. split )
Within a subroutine the array #_ contains the parameters passed to that subroutine
So the reason why
my %Q = %{$_};
says you can't use an undefined value as hash reference is because $_ is not defined.
What you really need here is
my %Q = %{$_[0]};
because that is the first element of #_, which is what was passed to testRef in the first place.
In practice I tend to find myself doing things a little differently because it lends itself to flexibility for future modifications:
sub testRef {
my ( $Q ) = #_;
print $_, "\n" for keys %$Q; # just as an example
}

How do I return an array and a hashref?

I want to make a subroutine that adds elements (keys with values) to a previously-defined hash. This subroutine is called in a loop, so the hash grows. I don’t want the returning hash to overwrite existing elements.
At the end, I would like to output the whole accumulated hash.
Right now it doesn’t print anything. The final hash looks empty, but that shouldn’t be.
I’ve tried it with hash references, but it does not really work. In a short form, my code looks as follows:
sub main{
my %hash;
%hash=("hello"=>1); # entry for testing
my $counter=0;
while($counter>5){
my(#var, $hash)=analyse($one, $two, \%hash);
print ref($hash);
# try to dereference the returning hash reference,
# but the error msg says: its not an reference ...
# in my file this is line 82
%hash=%{$hash};
$counter++;
}
# here trying to print the final hash
print "hash:", map { "$_ => $hash{$_}\n" } keys %hash;
}
sub analyse{
my $one=shift;
my $two=shift;
my %hash=%{shift #_};
my #array; # gets filled some where here and will be returned later
# adding elements to %hash here as in
$hash{"j"} = 2; #used for testing if it works
# test here whether the key already exists or
# otherwise add it to the hash
return (#array, \%hash);
}
but this doesn’t work at all: the subroutine analyse receives the hash, but its returned hash reference is empty or I don’t know. At the end nothing got printed.
First it said, it's not a reference, now it says:
Can't use an undefined value as a HASH reference
at C:/Users/workspace/Perl_projekt/Extractor.pm line 82.
Where is my mistake?
I am thankful for any advice.
Arrays get flattened in perl, so your hashref gets slurped into #var.
Try something like this:
my ($array_ref, $hash_ref) = analyze(...)
sub analyze {
...
return (\#array, \#hash);
}
If you pass the hash by reference (as you're doing), you needn't return it as a subroutine return value. Your manipulation of the hash in the subroutine will stick.
my %h = ( test0 => 0 );
foreach my $i ( 1..5 ) {
do_something($i, \%h);
}
print "$k = $v\n" while ( my ($k,$v) = each %h );
sub do_something {
my $num = shift;
my $hash = shift;
$hash->{"test${num}"} = $num; # note the use of the -> deference operator
}
Your use of the #array inside the subroutine will need a separate question :-)

How to "keys %h" if $h is an object?

$h below is an object, but it only contains a regular hash.
my $h = YAML::Syck::LoadFile('have_seen.yaml');
If it was a normal hash then the number of keys would just be keys $h.
Question
How to get the numbers of keys when the hash is in an object?
Update
This is code
#!/usr/bin/perl
use strict;
use YAML::Syck;
my $h = YAML::Syck::LoadFile('h.yaml');
my $links = 100;
print $links - keys $h . "\n";
The yaml file contains
---
010711: 1
---
$h is not an object, but a plain hashref. This is really an operator precedence problem. Use parentheses to bind the argument to the keys function tight.
print $links - keys($h) . "\n";
As Greg Bacon pointed out, on old Perls it is necessary to manually dereference first with %$h or %{ $h } (which is the better style).
Use the keys operator as in
print scalar keys %$h;
Most of the time, an explicit scalar is unnecessary, e.g.,
my $n = keys %$h;
But it’s usually a bad idea to go poking into the internals of an object. Use the public interface instead. Why do you want to do it this way?
My code was also producing the same error Type of argument to keys on reference must be unblessed hashref or arrayref but the difference is that the hash was produced from my own object.
sub getAttributes {
my $self = shift;
return $self->{ATTRIBUTES};
}
I tried a few ways to get keys to de-reference what is returned by $instance->getAttributes() but it seems that once it has been blessed the keys function doesn't want to know.

Perl : Dereference between #_ and $_

I have a question with the following Code:
#!/usr/bin/perl
use strict;
use warnings;
my %dmax=("dad" => "aaa","asd" => "bbb");
my %dmin=("dad" => "ccc","asd" => "ddd");
&foreach_schleife(\%dmax,\%dmin);
sub foreach_schleife {
my $concat;
my $i=0;
foreach my $keys (sort keys %{$_[0]}) {
while ($_[$i]) {
$concat.="$_[$i]{$keys} ";
print $_[$i]{$keys}."\n";
$i++;
}
$i=0;
$concat="";
}
}
The Output is:
bbb
ddd
aaa
ccc
I don't understand this.
Normally you must dereference references on hashes,arrays etc.
Why not here? Its enough to write :
$_[$i]{$keys}."\n";
and not something like that:
$$_[$i]{$keys}."\n";
Why?
Has it something to do with the speciality of the variable #_/$_?
My guess is that because an array (or a hash, for that matter) can only contain hash references your second act of indexing means that the reference is understood.
I think the developers need to document this a little better.
To see that it is not special to *_, you can try this before the loop:
my #a = #_;
And this during:
print $a[$i]{$keys}."\n";
I think the main thing is that if you only have a scalar reference as the base, then at least one -> is required. So
my ( $damxr, $dminr ) = #_;
would require
$dmaxr->{ $key };
The reason why you don't have to dereference $_[0] and $_[1] is that $_[$i]{$keys} is a valid short-hand notation for $_[$i]->{$keys} when your reference is in an array or a hash.
$$_[$i]{$keys} won't work, because it will try to dereference the special variable $_ to a scalar. The correct syntax is %{$_[$i]}, but then you'll have to use %{$_[$i]}->{$keys}, which is more verbose.
#_ is the array of subroutine arguments, hence $_[$index] accesses the element at $index
Dereferencing is only good if you have references, but #_ isn't one.

How can I reference a hash with a variable name?

I have three hashes named %hash1, %hash2, %hash3. I need to reference each hash by variable and am not sure how to do it.
#!/usr/bin/perl
# Hashes %hash1, %hash2, %hash3 are populated with data.
#hashes = qw(hash1 hash2 hash3);
foreach $hash(#hashes){
foreach $key(keys $$hash){
.. Do something with the hash key and value
}
}
I know this is a fairly simplistic, comparatively noob question so my apologies for that.
This should work for you.
#!/usr/bin/perl
use strict;
use warnings;
my( %hash1, %hash2, %hash3 );
# ...
# load up %hash1 %hash2 and %hash3
# ...
my #hash_refs = ( \%hash1, \%hash2, \%hash3 );
for my $hash_ref ( #hash_refs ){
for my $key ( keys %$hash_ref ){
my $value = $hash_ref->{$key};
# ...
}
}
It uses hash references, instead of using symbolic references. It is very easy to get symbolic references wrong, and can be difficult to debug.
This is how you could have used symbolic references, but I would advise against it.
#!/usr/bin/perl
use strict;
use warnings;
# can't use 'my'
our( %hash1, %hash2, %hash3 );
# load up the hashes here
my #hash_names = qw' hash1 hash2 hash3 ';
for my $hash_name ( #hash_names ){
print STDERR "WARNING: using symbolic references\n";
# uh oh, we have to turn off the safety net
no strict 'refs';
for my $key ( keys %$hash_name ){
my $value = $hash_name->{$key};
# that was scary, better turn the safety net back on
use strict 'refs';
# ...
}
# we also have to turn on the safety net here
use strict 'refs';
# ...
}
To reference a hash by a reference you can do it one of two ways.
my $ref_hash = \%hash;
or create an anonymous referenced hash
my $ref_hash = {
key => value,
key => value
}
Now in order to access this hash you'll need to de-reference the variable or use the arrow syntax.
Example 1 (Arrow syntax)
print $ref_hash->{key};
Example 2
print ${$ref_hash}{key};
I hope that helps.