1

I'm getting the error Can't use an undefined value as an ARRAY reference in a Perl script. Below is a highly simplified version.

Basically I have setup a hash of arrays where some of them might be empty (B in this case). It works just fine if I DON'T sort the data array. I've tried adding a conditional to test if that particular array exists, but it doesn't like that in this setup. It would not be easy to sort the array as it's populated (unlike in this example).

use strict;
use warnings;

my @list = ('A', 'B', 'C');
my %data_for;
$data_for{'A'} = ['apple', 'astronaut', 'acorn'];
$data_for{'C'} = ['car', 'cook', 'candy'];

# Creates error
foreach my $letter (@list) {
    print "$letter: ";
    foreach my $item ( sort @{$data_for{$letter}}) {
        print "$item, ";
    }
    print "\n";
}

This is the output I want (seems obvious, but eh):

A: acorn, apple, astronaut, 
B:
C: candy, car, cook,

As a strange aside, if I print the working version (without the sort) first, the second version with the sort works without an error. I do not understand this, but I may be able to use that as a work around.

ikegami
  • 367,544
  • 15
  • 269
  • 518
LadyCygnus
  • 663
  • 7
  • 14
  • 1
    plus one for coming up with a simpler example that demonstrates your issue – mob Jul 13 '17 at 20:29
  • @mob - I would say that I didn't have a choice given how complex hash-of-arrays can be, but I know how bad some can be. Technically this question is a repeat, but I simply couldn't follow the earlier ones who had incoherent code, which lead to incomplete answers. – LadyCygnus Jul 13 '17 at 20:39
  • I am surprised that it hasn't made it into even *Merriam-Webster*, but ***setup*** isn't an English verb anywhere. There seems to be a fashion for removing spaces from phrases that is making American English more and more like German. I don't like it. I am sorry that this is so off-topic. – Borodin Jul 13 '17 at 23:07
  • @Borodin - it may be a duplicate question title, but that code didn't clearly show where the problem was and the accepted answer was "use diagnostics. There is another question about this same error code, but the code was so confusing that they couldn't figure out what it was written in (perl, python, php??) - and the final answer was "use diagnostics". My question got an actual answer that explains what was wrong. – LadyCygnus Jul 19 '17 at 11:56
  • @ikegami - thanks for the fix for that. It bothered my OCD, but didn't really matter so I was forcing myself to ignore it :-) – LadyCygnus Jul 19 '17 at 12:05
  • 1
    Reopened, but there was nothing useful in the question of which this one was flagged as a duplicate. [This one](https://stackoverflow.com/questions/7818243/how-should-i-handle-undefined-references-with-xmlsimple) is an actual duplicate, but I'd rather close the old one in preference to this clearer new one. I have done so. – ikegami Jul 19 '17 at 15:13

2 Answers2

4

$data_for{B} is not defined, so the attempt to dereference it as an array (in the sort @{$data_for{$letter}} expression) is an error.

One workaround is to assign a (empty) value for $data_for{B}:

$data_for{'A'} = ['apple', 'astronaut', 'acorn'];
$data_for{'B'} = [];
$data_for{'C'} = ['car', 'cook', 'candy'];

but a more general workaround is to use the '||' operator (or '//' operator for Perl >=v5.10) to specify a default value when a hash value is not defined

foreach my $item ( sort @{$data_for{$letter} || []}) { ... }

When $data_for{$letter} is undefined (and therefore evaluates to false), the expression $data_for{$letter} || [] evaluates to a reference to an empty array, and the array dereference operation will succeed.

mob
  • 117,087
  • 18
  • 149
  • 283
2

So you need to check if $data_for{$letter} is a reference or not. Possible checks:

  • exists($data_for{$letter}).
  • defined($data_for{$letter}) because a non-existent hash element is undefined.
  • $data_for{$letter} because references are always true.

We can use the check to provide us something to dereference:

sort @{ $data_for{$letter} // [] }

We can use the check to avoid dereferencing at all.

sort $data_for{$letter} ? @{ $data_for{$letter} } : ()

This gives us the following:

print "$letter: ";
for my $item ( sort $data_for{$letter} ? @{ $data_for{$letter} } : () ) {
    print "$item, ";
}
print "\n";

Or better yet, we can avoid the trailing , with the following:

use feature qw( say );

say "$letter: ", join ", ", sort $data_for{$letter} ? @{ $data_for{$letter} } : ();

It works just fine if I DON'T sort the data array.

When you dereference an undefined variable that's used as a modifiable value (lvalue), Perl will automatically create a variable of the appropriate type, and it will place a reference to the newly created variable in the undefined variable. This is called "autovivification".

Since autovivifying only happens in lvalue context, derefencing an undefined value elsewhere leads to the an error.

 my ($a,$b);  # Both are undefined.
 @$a = @b;    # OK. Equivalent to @{ $a //= [] } = @b.
 @a = @$b;    # XXX. "Can't use an undefined value as an ARRAY reference."

sort doesn't modify its operands, so it doesn't evaluate them in lvalue context.

A foreach loop evaluates its operands in lvalue context to allow for for (@$a) { $_ = uc($_); }. That means that

for (@{ $data_for{$letter} }) { ... }

implicitly modifies $data_for{$letter} as follows:

for (@{ $data_for{$letter} //= [] }) { ... }
ikegami
  • 367,544
  • 15
  • 269
  • 518