Anonymous Monk has asked for the wisdom of the Perl Monks concerning the following question:

Hello, If I run the following code:
use Data::Dumper; use List::Util qw(first); my @x = ({"aaa" => 100}); say Dumper([grep { $_->{"aaa"} < 4 } @x]->[0]); say Dumper((first { $_->{"aaa"} < 4 } @x));
I get:
$VAR1 = undef; $VAR1 = undef;
But if I instead do
say Dumper([grep { $_->{"aaa"} < 4 } @x]->[0]->{"aaa"}); say Dumper((first { $_->{"aaa"} < 4 } @x)->{"aaa"});
I get
$VAR1 = undef; Can't use an undefined value as a HASH reference at test.pl line 7.
Why are these two undefs different? Thanks.

Replies are listed 'Best First'.
Re: Why are these undefs different?
by haukex (Archbishop) on Sep 13, 2023 at 06:15 UTC

    The answer is: Autovivification.

    In the case of [grep { $_->{"aaa"} < 4 } @x]->[0]->{"aaa"}, the grep returns an empty list, which means the expression is the equivalent of []->[0]->{"aaa"}. [] creates a new anonymous array and returns a reference to it, and then the following dereference operations autovivify both the 0th element of the array, as well as the reference to the new anonymous hash in that element. And since the element aaa doesn't exist in that hash, the whole expression returns undef.

    In the second case, first is simply returning undef, which can't be dereferenced with ->{"aaa"}, hence the error.

    If the reason you're using first here is simply to stop the search on the first found element, then you could use the same "trick" as described above*. [first { $_->{"aaa"} < 4 } @x]->[0]->{"aaa"} will work, with a minor difference: the first arrayref in the chain will not be empty, but it will be [undef], that is, an array with one element, that element being undef. Perl doesn't need to autovivify the 0th element, and still autovivifies the hash reference in place of the undef in that case.

    $ perl -MData::Dump -e 'my $x = []; dd !!$x->[0]->{"aaa"}; dd $x;' "" [{}] $ perl -MList::Util=first -MData::Dump -e 'my @x; dd [first {$_} @x]' [undef] $ perl -le 'print( (undef)->{"aaa"} )' Can't use an undefined value as a HASH reference at -e line 1. $ perl -MList::Util=first -MData::Dump -e \ 'my @x; my $y=[first {$_} @x]; dd $y; dd !!$y->[0]->{"aaa"}; dd $y' [undef] "" [{}]

    See also perlref.

    * Update: Though personally, I would probably tend towards a two-step process: first using first to find the element, and then checking whether that result is undef or not before attempting to access it. Of course this would require two statements instead of one, so it'd be a bit more verbose, but thereby also more clear. And at least I think probably a tiny bit more efficient, since it wouldn't need to autovivify anything just for the test - but whether that would even make a difference depends on how often the code gets called. But TIMTOWTDI and I also understand the desire for more terse code.

Re: Why are these undefs different?
by tobyink (Canon) on Sep 13, 2023 at 11:24 UTC

    Consider this:

    use Data::Dumper; use List::Util qw(first); my @x = ({"aaa" => 100}); [grep { $_->{"aaa"} < 4 } @x]->[0] = 'foo'; (first { $_->{"aaa"} < 4 } @x) = 'foo';

    Line 6 (grep) executes fine. Line 7 (first) dies with:

    Can't modify non-lvalue subroutine call of &List::Util::first in list assignment at foo.pl line 7, near "'foo';"

    You're putting the results of your grep into an arrayref. That arrayref and its elements are lvalues—they can be assigned to—so autovivification can happen to them.

Re: Why are these undefs different?
by BillKSmith (Monsignor) on Sep 13, 2023 at 17:58 UTC
    A little more code handles all the common errors correctly without throwing an exception and without altering the original data.
    use strict; use warnings; use List::Util qw(first); use Scalar::Util qw(looks_like_number); use Test::More tests=>5; sub first_a_value { my @x = @_; my $refer = first { exists $_->{aaa} and looks_like_number($_->{aaa}) and $_->{aaa} < 4 } @x; my $value = undef; $value = $refer->{aaa} if defined $refer; return $value; } my @x; my $aaa; @x = ( {aaa=>3} ); $aaa = first_a_value(@x); ok ($aaa == 3, 'valid value'); @x = ( {aaa=>100} ); $aaa = first_a_value(@x); ok (!defined($aaa), 'no numer small enough'); @x = ( {aaa=>'foo'} ); $aaa = first_a_value(@x); ok (!defined($aaa), 'not a number'); @x = ( {bbb=>3} ); $aaa = first_a_value(@x); ok (!defined($aaa), 'no aaa'); @x = ( {} ); $aaa = first_a_value(@x); ok (!defined($aaa), 'no hash');

    UPDATD - Added tests accidentally omitted.

    Bill
Re: Why are these undefs different?
by Anonymous Monk on Sep 13, 2023 at 20:33 UTC
    Thanks for the replies! The goal was to replace [...]->[0] with first to make the code a bit more readable but at this point I might as well leave it alone (performance isn't really a concern). Out of curiosity, why does this still work:
    no autovivification; my @x = ({"aaa" => 100}); say Dumper([grep { $_->{"aaa"} < 4 } @x]->[0]->{"aaa"}); #$VAR1 = undef;
      Out of curiosity, why does this still work

      My understanding of autovivification is that by default it doesn't prevent the dereferencing, it prevents the creation of the new refs (= the autovivification). Compare:

      $ perl -MData::Dump -e 'my $x; $x->[0]->{"aaa"}; dd $x' [{}] $ perl -MData::Dump -M-autovivification -e \ 'my $x; $x->[0]->{"aaa"}; dd $x' undef

      So in the second case $x was not autovivified to an arrayref containing a hashref. If you want the pragma to throw errors, you need to tell it:

      $ perl -MData::Dump -M-autovivification=strict,fetch -e \ 'my $x; $x->[0]->{"aaa"}; dd $x' Reference vivification forbidden at -e line 1.