Having recently started a new job, I was exposed to old code with multi-step tests against autovivification in multi-level hashes. You get used to the code you have seen, but in a new environment it‘s irritating and jarring.
Moose does not generally have the problem, first because class structure is pre-declared, because values are accessed using accessor functions rather than directly, and because responsibility is delegated down to attributes, avoiding long chains. On the other hand, Moose has it's own overhead, so hand-rolled objects, and bare structures still have their use.
If you don‘t protect against autovivification, then mis-spelling a key, or referencing keys which haven‘t been instantiated in this instance, causes those keys to instantly come into existence.
This is ugly, and could potentially have more serious consequences.
It does lead to a clean data structure.
But there‘s a two-fold price:
One improvement is to create a brief alias to the portion of the structure you can rely on. Then the individual tests are shorter, clearer, and less likely to contain an error. And short tests make it easier to understand the set of tests.
Creating a variable is wasteful and distracting. In the early 80s the programming language Turing had a keyword to achieve this, I believe it was alias. Perl already provides a very short alias that comes naturally to Perl programmers, $_.
You might use a for loop with a single element, to alias $_ to the reliable portion.
Or, although smartmatch itself can lead to problems, using given to assign $_ avoids the question of why there‘s a for loop, especially one with a single element. given is designed to assign $_; that‘s all it does. It is Perl's alias.
The autovivification module comes to the rescue!
Faster than a speeding bullet! Able to leap tall buildings in a single bound! …
Oddly, the way you use the autovivification module is to say
The result is what you want. The module does all the boring stuff for us, and prevents non-existent keys from suddenly being autovivified.
Besides protecting during a fetch, the default configuration also protects during an exists test.
... as well as during attempted deletes.
You can also specify in the import line... which in this case actually invokes the unimport routine...that it should protect against storing values into keys which do not already exist. If, for example, you are using hash-based data structures or traditional hash-based objects , you can create the struct or object with all the keys it will need. You can invoke
Once the object exists, values of existing keys can be modified, but attempting to assign to a new key generates a fatal runtime error.
But, frankly, I don‘t mind taking responsibility for creating and modifying structures, so long as I‘m spared the task of checking component existence.
The
Unfortunately, I couldn‘t get it to work properly. In any case, it uses the external autovivification module, so it only provides a benefit if you like its Boilerplate condensation. But it is nice to not have to type
Moose does not generally have the problem, first because class structure is pre-declared, because values are accessed using accessor functions rather than directly, and because responsibility is delegated down to attributes, avoiding long chains. On the other hand, Moose has it's own overhead, so hand-rolled objects, and bare structures still have their use.
If you don‘t protect against autovivification, then mis-spelling a key, or referencing keys which haven‘t been instantiated in this instance, causes those keys to instantly come into existence.
#!/usr/bin/perl
use warnings;
use strict;
use Data::Dump 'dump';
use 5.024;
my $var = {key1 => {key2 => {key3 => 'a'}}};
say dump $var;
if ( $var->{key1}{key2}{key3b}[13]{foobarbaz} ) {
say 'Miracles DO happen!';
}
say dump $var;
This is ugly, and could potentially have more serious consequences.
{ key1 => { key2 => { key3 => "a" } } }
{
key1 => {
key2 => {
key3 => "a",
key3b => [
undef, undef, undef, undef,
undef, undef, undef, undef,
undef, undef, undef, undef,
undef, {},
],
},
},
}
Manual Solutions
The manual solution is to assume the existence only of that portion which you can rely on, and then check stage by stage. I suppose in this case I should be verifying that key3b actually is an array, before determining the number of elements it contains, and whether element 13 stores a defined value. And perhaps the other elements should be tested for hash-y-ness. But generally you know what an element is, the question is whether it exists or not. my $var = {key1 => {key2=> {key3 => 'a'}}};
say dump $var;
if ( $var->{key1}{key2}{key3b}
&& 13 <= scalar @{ $var->{key1}{key2}{key3b} }
&& $var->{key1}{key2}{key3b}[13]
&& $var->{key1}{key2}{key3b}{foobarbaz} ) {
say 'Miracles DO happen!';
}
say dump $var;
It does lead to a clean data structure.
{ key1 => { key2 => { key3 => "a" } } }
{ key1 => { key2 => { key3 => "a" } } }
But there‘s a two-fold price:
- many tests AND-ed together
- many stages of element reference.
One improvement is to create a brief alias to the portion of the structure you can rely on. Then the individual tests are shorter, clearer, and less likely to contain an error. And short tests make it easier to understand the set of tests.
my $var = {key1 => {key2=>{key3 => 'a'}}};
say dump $var;
my $alias = $var->{key1}{key2};
if ( $alias->{key3b}
&& 13 < @{ $alias->{key3b} } && $alias->[13]
&& $alias->{key3b}{foobarbaz} ) {
say 'Miracles DO happen!';
}
say dump $var;
Creating a variable is wasteful and distracting. In the early 80s the programming language Turing had a keyword to achieve this, I believe it was alias. Perl already provides a very short alias that comes naturally to Perl programmers, $_.
You might use a for loop with a single element, to alias $_ to the reliable portion.
Or, although smartmatch itself can lead to problems, using given to assign $_ avoids the question of why there‘s a for loop, especially one with a single element. given is designed to assign $_; that‘s all it does. It is Perl's alias.
no warnings 'experimental::smartmatch';
my $var = {key1 => {key2=>{key3 => 'a'}}};
say dump $var;
given ( $var->{key1}{key2} ) {
if ( $_->{key3b}
&& 13 < @{ $_->{key3b} } && $_->[13]
&& $_->{key3b}{foobarbaz} ) {
say 'Miracles DO happen!';
}
}
say dump $var;
Let's Use a Module
But none of these resolves the problem of the many tests.The autovivification module comes to the rescue!
Faster than a speeding bullet! Able to leap tall buildings in a single bound! …
Oddly, the way you use the autovivification module is to say
no autovification
. After all, you want to prevent unintended autovivification, and how confusing would it be if you said use autovivification
to disable autovivification.
no autovivification;
my $var = {key1 => {key2=>{key3 => 'a'}}};
say dump $var;
if ( $var->{key1}{key2}{key3b}[13]{foobarbaz} ) {
say 'Miracles DO happen!';
}
say dump $var;
The result is what you want. The module does all the boring stuff for us, and prevents non-existent keys from suddenly being autovivified.
{ key1 => { key2 => { key3 => "a" } } }
{ key1 => { key2 => { key3 => "a" } } }
Besides protecting during a fetch, the default configuration also protects during an exists test.
... as well as during attempted deletes.
# old way
delete $var->{key1}{key2}{key3b}[13]{foobarbaz}
if exists $var->{key1}{key2}{key3b}
&& 13 < @{ $var->{key1}{key2}{key3b}}
&& exists $var->{key1}{key2}{key3b}[13]
&& exists $var->{key1}{key2}{key3b}[13]{foobarbaz};
# new way
delete $var->{key1}{key2}{key3b}[13]{foobarbaz};
autovivification.pm
can also prevent accidental assignment to unintended keys.
You can also specify in the import line... which in this case actually invokes the unimport routine...that it should protect against storing values into keys which do not already exist. If, for example, you are using hash-based data structures or traditional hash-based objects , you can create the struct or object with all the keys it will need. You can invoke
use autovivification qw<store>
in the constructor, and protection will return when that goes out of scope.
{ key1 => { key2 => { key3 => "a" } } }
Can't vivify reference at 06_store.pl line 16.
Once the object exists, values of existing keys can be modified, but attempting to assign to a new key generates a fatal runtime error.
But, frankly, I don‘t mind taking responsibility for creating and modifying structures, so long as I‘m spared the task of checking component existence.
Alternatives
The
sanity.pm
module, a competitor for Modern::Perl
, common::sense
and similar Boilerplate condensors, claims to provide autovivification protection.
use sanity qw(Modern::Perl NO:autovivification);
use Data::Dump 'dump';
my $var = {key1 => {key2=>{key3 => 'a'}}};
say dump $var;
if ( $var->{key1}{key2}{key3b}[13]{foobarbaz} ) {
say 'Miracles DO happen!';
}
say dump $var;
Unfortunately, I couldn‘t get it to work properly. In any case, it uses the external autovivification module, so it only provides a benefit if you like its Boilerplate condensation. But it is nice to not have to type
use warnings use strict; use 5.024; # etc
Comments