I am looking for a more effective way to write this affect
subroutine in perl:
#!/usr/bin/perl
use 5.010;
use strict;
my @keys = qw/foo bar baz/;
my %hash = ( foo => {babel => 'fish'} );
affect (\%hash, 42);
sub affect {
my $ref = shift;
$ref = $ref->{$keys[$_]} //= {} for (0.. $#keys - 1);
$ref->{$keys[$#keys]} = shift;
}
use Data::Dumper;
print Dumper \%hash;
And the expected result:
$VAR1 = {
'foo' => {
'bar' => {
'baz' => 42
},
'babel' => 'fish'
}
};
I was thinking about things like this, but obviously it can't work like this:
%hash{ @keys } = 42;
Any idea?
it would be more effective if you actually passed the keys to it! Let's make it an lvalue sub at the same time.
sub dive_val :lvalue {
my $p = \shift;
$p = \( $$p->{$_} ) for @_;
$$p
}
my %hash;
dive_val(\%hash, qw( foo babel )) = 'fish';
dive_val(\%hash, @keys) = 42;
There are two added advantages to this implementation.
First, you can use it to peek into the data structure.
print(dive_val($hash, @keys), "\n");
Second, it will autovivify its first argument.
my $hash;
dive_val($hash, @keys) = 42;
This function already exists as Data::Diver's DiveVal
.
use Dive::Val qw( DiveVal );
DiveVal(\%hash, map \$_, @keys) = 42;
Comparison of the flow of affect
and dive_val
:
affect
$ref
is a reference to a hash.
Pre-loop: $ref references %hash
After loop pass 0: $ref references %{ $hash{$key[0]} }
After loop pass 1: $ref references %{ $hash{$key[0]}{$key[1]} }
Post loop uses: $ref->{$key[2]}
dive_val
$p
is a reference to a scalar.
Pre-loop: $p references $hash
After loop pass 0: $p references $hash->{$key[0]}
After loop pass 1: $p references $hash->{$key[0]}{$key[1]}
After loop pass 2: $p references $hash->{$key[0]}{$key[1]}{$key[2]}
Post loop uses: $$p
dive_val
's approach is purer.
- There's no need to create the hash before it's dereferenced (unlike
affect
which creates it in the previous loop pass).
- In fact, the scalar doesn't need to be in a hash at all. This means
dive_val
could easily be extended to support mixed array/hash structures.
- There's no need to treat the root specially (if you want to accept a potentially undefined argument).
- There's no need to treat the last key specially.