perlautovivification

"no autovivication" pragma fails with grep in Perl


I am trying to turn off autovivication using the module: https://metacpan.org/pod/autovivification but it is failing for grep:

#!/usr/bin/env perl

use strict;
use warnings FATAL => 'all';
use feature 'say';
use autodie ':all';
use DDP;
no autovivification;

my %h = (
    'a' => 1,
    'b' => 2,
);
p %h; # pretty print the original hash
my @fake_keys = ('x', 'y', 'z');
if (grep {defined} @h{@fake_keys}) {
    say "found fake keys";
}
p %h; # show that 'x', 'y', & 'z' are added as undef keys

How can I turn off autovivication for grep?


Solution

  • Solution:

    if (grep { defined($h{$_}) } @fake_keys) {
       say "found fake keys";
    }
    

    Explanation follows.


    Autovivification, as used in the Perl documentation, is the creation of anon vars and references to them when an undefined scalar is dereferenced.

    For example, autovivification dictates that $x->[0] is equivalent to ( $x //= [] )->[0].

    For example, autovivification dictates that $h{p}{q} is equivalent to ( $h{p} //= {} )->{q}.

    There is no dereferencing in your code, so autovivifaction couldn't possibly occur in your code, so no autovivification; doesn't help.


    What you have in your code is a hash element used as an lvalue. "lvalue" means assignable value. It's named after the fact that such expressions are usually found on the left of assignments.

      $h{key} = ...;
    # ^^^^^^^
    # lvalue
    

    But they are also found elsewhere in Perl.

    for ($h{key}) {
       # ^^^^^^^
       # lvalue
    }
    
    map { } $h{key}
          # ^^^^^^^
          # lvalue
    
    grep { } $h{key}
           # ^^^^^^^
           # lvalue
    
    some_sub($h{key});
           # ^^^^^^^
           # lvalue
    

    This is because the block of each of looping constructs can modify the items being processed by modifying $_, and subs can modify their arguments my modifying the elements of @_.

    for ($h{key}) {
       $_ = uc($_);                 # Modifies $h{key}
    }
    
    grep { $_ = uc($_) } $h{key}    # Modifies $h{key}  # Bad practice, but possible.
    
    map { $_ = uc($_) } $h{key}     # Modifies $h{key}
    
    sub some_sub {
       $_[0] = uc($_[0]);           # Modifies $h{key}
    }
    
    some_sub($h{$k});
    

    For that to be possible, $h{$k} must exist before the loop body is entered or the sub is called.

    $ perl -M5.010 -e'for ($h{key}) { }  say 0+keys(%h);'
    1
    

    Sub calls use expensive magic to avoid this.

    $ perl -M5.010 -e'sub f { }  f($h{key});  say 0+keys(%h);'
    0
    

    But grep and map do not.

    $ perl -M5.010 -e'grep { 1 } $h{key};  say 0+keys(%h);'
    1
    
    $ perl -M5.010 -e'map {; } $h{key};  say 0+keys(%h);'
    1