Search code examples
perlhashreferencesubroutinedereference

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


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,

  1. I can create a reference to a subroutine ($subref) and then dereference it to run the subroutine (&$subref)
  2. I can use the reference as a hash 'value' and then easily dereference that
  3. 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.

Solution

  • 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));
    }