Search code examples
perlstringify

How to convert an array into a hash, with variable names mapped as keys in Perl?


I find myself doing this pattern a lot in perl

sub fun {
    my $line = $_[0];
    my ( $this, $that, $the_other_thing ) = split /\t/, $line;
    return { 'this' => $this, 'that' => $that, 'the_other_thing' => $the_other_thing};
}

Obviously I can simplify this pattern by returning the output of a function which transforms a given array of variables into a map, where the keys are the same names as the variables eg

sub fun {
    my $line = $_[0];
    my ( $this, $that, $the_other_thing ) = split /\t/, $line;
    return &to_hash( $this, $that, $the_other_thing );
}

It helps as the quantity of elements get larger. How do I do this? It looks like I could combine PadWalker & closures, but I would like a way to do this using only the core language.

EDIT: thb provided a clever solution to this problem, but I've not checked it because it bypasses a lot of the hard parts(tm). How would you do it if you wanted to rely on the core language's destructuring semantics and drive your reflection off the actual variables?

EDIT2: Here's the solution I hinted at using PadWalker & closures:

use PadWalker qw( var_name );

# Given two arrays, we build a hash by treating the first set as keys and
# the second as values
sub to_hash {
    my $keys = $_[0];
    my $vals = $_[1];
    my %hash;
    @hash{@$keys} = @$vals;
    return \%hash;
}

# Given a list of variables, and a callback function, retrieves the
# symbols for the variables in the list.  It calls the function with
# the generated syms, followed by the original variables, and returns
# that output.
# Input is: Function, var1, var2, var3, etc....
sub with_syms {
    my $fun = shift @_;
    my @syms = map substr( var_name(1, \$_), 1 ), @_;
    $fun->(\@syms, \@_);
}

sub fun {
    my $line = $_[0];
    my ( $this, $that, $other) = split /\t/, $line;
    return &with_syms(\&to_hash, $this, $that, $other);
}

Solution

  • You could use PadWalker to try to get the name of the variables, but that's really not something you should do. It's fragile and/or limiting.

    Instead, you could use a hash slice:

    sub fun {
       my ($line) = @_;
       my %hash;
       @hash{qw( this that the_other_thing )} = split /\t/, $line;
       return \%hash;
    }
    

    You can hide the slice in a function to_hash if that's what you desire.

    sub to_hash {
       my $var_names = shift;
       return { map { $_ => shift } @$var_names };
    }
    
    sub fun_long {
       my ($line) = @_;
       my @fields = split /\t/, $line;
       return to_hash [qw( this that the_other_thing )] @fields;
    }
    
    sub fun_short {
       my ($line) = @_;
       return to_hash [qw( this that the_other_thing )], split /\t/, $line;
    }
    

    But if you insist, here's the PadWalker version:

    use Carp      qw( croak );
    use PadWalker qw( var_name );
    
    sub to_hash {
       my %hash;
       for (0..$#_) {
          my $var_name = var_name(1, \$_[$_])
             or croak("Can't determine name of \$_[$_]");
          $hash{ substr($var_name, 1) } = $_[$_];
       }
       return \%hash;
    }
    
    sub fun {
       my ($line) = @_;
       my ($this, $that, $the_other_thing) = split /\t/, $line;
       return to_hash($this, $that, $the_other_thing);
    }