Perl: not an array reference while calculating array size

526 views Asked by At

I'm trying to untangle some legacy code where an operation is done on $value iff its size is more than x (where x is a hard coded int). This is what it currently looks like:

if (scalar(@{$value}) > x) {
    ...
}

As with all legacy code, this $value can be almost anything (hash, scalar, array) although it's expected to be an array of different objects. This code currently fails with a "Not an ARRAY reference" around 5% of the time and I'm trying to figure out what's the possible $value that can break it.

I assumed it might fail if the $value is undefined so I even gave it a || [] but to no avail (same error):

if (scalar(@{$value || []}) > x) {
    ...
}

I'm also trying to figure out why I need the @{}? My understanding is that that evaluates $value in a list context so that scalar can later ensure I get the size. Does that make the code more robust or can I just directly use scalar $value? Does @{} even do what I think it does?

I'm using perl 5.8.

2

There are 2 answers

4
Pedro LM On BEST ANSWER

You have two questions there. I'll answer them separately:

Question: What is @{} doing?

When you write @{$thing}, you are dereferencing $thing into an array. As you have noticed, this only works when $thing is an array reference. (Other dereferencing operators are %{$thing} for hash references and ${$thing} for scalar references.)

You do need a dereferencing operator there. Consider this:

my $arrayref = [ 'Alice', 'Bob', 'Charlie' ];
my $hashref  = { x => 4, y => 5 };
my $string   = "Hello, world";
for my $thing ($arrayref, $hashref, $string) {
    print "thing         --> ", $thing, "\n";
    print "scalar(thing) --> ", scalar($thing), "\n";
}

Output:

thing         --> ARRAY(0x7f3b8054e468)
scalar(thing) --> ARRAY(0x7f3b8054e468)
thing         --> HASH(0x7f3b80560678)
scalar(thing) --> HASH(0x7f3b80560678)
thing         --> Hello, world
scalar(thing) --> Hello, world

There's no point in forcing $thing to a scalar context. It's already a scalar!

Question: How can I safely dereference a scalar?

If you don't know what kind of reference is contained in $thing, you can use Ref::Util to inspect it:

use Ref::Util qw( is_arrayref is_hashref );

for my $thing ($arrayref, $hashref, $string) {
    if (is_arrayref($thing)) {
        print "array: thing         --> ", @{$thing}, "\n";
        print "array: scalar(thing) --> ", scalar(@{$thing}), "\n";
    }
    elsif (is_hashref($thing)) {
        print "hash:  thing         --> ", %{$thing}, "\n";
        print "hash:  scalar(thing) --> ", scalar(%{$thing}), "\n";
    }
    else
    {
        print "else:  thing         --> ", $thing, "\n";
    }
}

Output:

array: thing         --> AliceBobCharlie
array: scalar(thing) --> 3
hash:  thing         --> y5x4
hash:  scalar(thing) --> 2/8
else:  thing         --> Hello, world

Observations:

  • The arrayref, when dereferenced, becomes a list of its elements. print outputs every element with no separators: AliceBobCharlie
  • The arrayref, when dereferenced and forced into a scalar, becomes the number of elements: 3
  • The hashref, when dereferenced, becomes a list of keys and values. print outputs every pair with no separators: y5x4
  • The hashref, when dereferenced and forced into a scalar, becomes a string where the first number is the number of keys and the second number is the number of buckets in the hashtable: 2/8
0
Sachin Dangol On

The following code will solve:

if ($value and ref $value eq 'ARRAY' and @$value > x) { ... }

Basically you can de-reference to ARRAY only if it's a ARRAY ref. So making sure it's a ARRAY ref is must, so that it won't fail for HASH etc