Search code examples
perlperl5.8

Perl: not an array reference while calculating array size


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.


Solution

  • 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