1

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.

4
  • 2
    $value should be a reference to an array. @$value is the array referred to by $value. scalar(@$value) is the number of elements contained in that array. scalar($value) is a just string like "ARRAY(0xce1460)". Commented Nov 16, 2018 at 12:35
  • "I'm using perl 5.8." Why, oh why? :-( Commented Nov 16, 2018 at 12:44
  • @DaveMitchell you, sir, just rewired my brain. Thank you. I had no idea that it'd be a reference. I just assumed perl did some crazy concatenation to get it into a scalar context. Brilliant! Commented Nov 16, 2018 at 13:29
  • @DaveCross Two things you don't choose. Your parents and which perl version currently runs your legacy systems :) I appreciate your prayers though hahah Commented Nov 16, 2018 at 13:29

2 Answers 2

2

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
Sign up to request clarification or add additional context in comments.

4 Comments

Beautifully explained. Thank you! Reading Dave Mitchell's comment above, it seems you could also do scalar(@$value) without the {}. Does that mean that the {} is just like an anonymous subroutine? Or does it serve some other functionality?
@AbdoSalem @$value and @{$value} are exactly the same... it's just syntactic sugar. :) The braces are useful (sometimes needed) when you have to disambiguate access to an arrayref that's nested inside other objects: @{ $thing->{foo}->{bar} }
Brilliant! Really appreciate your detailed answer! :)
@ysth that's a mini treasure right there. Thank you!
1

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

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.