How to check for an array reference in Perl
So you've got a Perl $variable
.
Can we use it as an array or hash reference?
If you do an online search for possible solutions,
you'll find a number of suggestions, most of them wrong.
TL;DR:
checking if ref $variable eq 'ARRAY'
is almost always a bug.
Depending on your use case, you want:
reftype $variable eq 'ARRAY'
from Scalar::Util as a check for physical array references, or_::is_array_ref $variable
from my module Util::Underscore as a check for logical array references.
What ref() actually does
Perl's ref()
builtin looks at a scalar value
and tells us the type:
-
it returns the empty string if the value doesn't hold a reference, but e.g. a string or is undef.
-
it returns the name of the class if the value contains an object, i.e. if it is a blessed reference.
-
it returns the name of the reference type if the value contains a plain reference.
Here is how it's supposed to work:
use Test::More;
is ref "foo", "";
is ref [], "ARRAY";
my $object = bless { a => 4 } => 'Some::Class';
is ref $object, 'Some::Class';
done_testing;
However, ref()
can tell us that we have an array even
if the variable can't actually be used as an array.
For certain values of $x
,
the following code will die with the message “not an ARRAY reference”:
if (ref $x eq 'ARRAY') {
say for @$x;
}
There are nearly no restrictions on class names, so we can use strings that usually refer to a plain reference:
use Test::More;
my $not_a_plain_reference = bless {} => 'ARRAY';
is ref $not_a_plain_reference, 'ARRAY';
done_testing;
(The only reason why we can't bless a reference into the empty string package
is that the empty string happens to be an alias for the main
package.)
The ref()
function will also tell us that we don't have an array
even if that value supports array access.
So with a suitable value, this code may run without any problem:
if (ref $x ne 'ARRAY') {
say for @$x;
}
This can happen in two cases: when the value is a blessed array reference, or when the value is an object that overloads the array dereference operator.
use Test::More;
BEGIN {
package LikeAnArray;
use Moo;
has values => (is => 'ro', required => 1);
use overload '@{}' => sub {
my ($self) = @_;
return $self->values
};
}
my $like_an_array = LikeAnArray->new(values => [1, 2, 3]);
my $some_object = bless ["foo", "bar"] => 'AnotherClass';
isnt ref $like_an_array, 'ARRAY';
is ref $like_an_array, 'LikeAnArray';
is_deeply [@$like_an_array], [1, 2, 3],
'can dereference anyway';
isnt ref $some_object, 'ARRAY';
is ref $some_object, 'AnotherClass';
is_deeply [@$some_object], ["foo", "bar"],
'can dereference anyway';
done_testing;
From these examples, it should now be clear:
ref()
is completely unreliable.
It will neither tell us consistently whether we have a plain array reference,
nor whether a value can be used as an array reference.
What kinds of values are supposed to be used as an array ref?
Given the above examples, it is probably best to define three types of array refs:
-
Plain array refs. What you usually expect.
-
Objects that happen to be implemented with an array ref. Since objects should provide encapsulation, we should not generally access the underlying storage directly.
-
Objects that explicitly support array-like usage via overloading.
These can be categorized as physical array references or logical array references.
-
A physical array reference uses a plain array as storage. This is the case for plain array refs, and for objects that happen to use array refs for storage.
-
A logical array reference exposes an array-like interface. This is the case for plain array refs, and for objects that overload the array dereference operator.
Test for physical array refs with Scalar::Util::reftype()
The Scalar::Util module provides better functions for working with references.
Scalar::Util is a core module, so you can always use it.
If you are using ref()
, you should probably switch to blessed()
or reftype()
from Scalar::Util.
-
reftype()
returns the type of a physical reference as a string. It ignores if the reference was blessed into a class. If the value isn't a reference, it returnsundef
. -
blessed()
returns the class of an object, orundef
if the value isn't a blessed reference.
The module documentation has excellent examples on how these work.
Instead of checking the reftype()
return value
against the expected type,
you can also use the is_arrayref()
family of functions from Ref::Util.
Test for logical array refs with _::is_array_ref() from Util::Underscore
The logical ref type is the same as the physical ref type,
except when the reference is a blessed object.
Then we have to check for overloading.
By overloading the special @{}
slot,
an object can return an array ref that is to be used in its place
when dereferenced as an array.
Luckily for us,
the overload
module provides an API
to check whether this operation is overloaded:
$object->overload::Method('@{}')
.
That either returns undef, or the code ref implementing this method.
This is basically the overload equivalent to $object->can('method_name')
.
To check whether a value is an object,
we can simply use blessed()
from Scalar::Util.
Together, this allows us to implement a logical array ref check:
use overload ();
use Scalar::Util ();
sub is_array_ref {
my ($value) = @_;
# If this is an object,
# check if array-dereferencing was overloaded.
if (defined Scalar::Util::blessed $value) {
return defined $value->overload::Method('@{}');
}
# If this is not an object,
# check whether this is a physical array ref.
return (Scalar::Util::reftype $value // '') eq 'ARRAY';
}
(Note that reftype()
may return undef
.
In that case, we substitute the empty string to avoid warnings
for the eq
comparison.)
This works similarly for other kinds of references
like hash refs, code refs, and glob refs,
except for scalar refs
(where the reftype is either SCALAR
or REF
),
and for regex objects
(where the reftype is REGEX
, except prior to 5.11 where it is SCALAR
, but is always a blessed object).
If remembering all of that is too burdensome,
I've written the Util::Underscore library
that includes an _::is_array_ref()
function in its toolbox.
I use it all the time in my code!
You can install Util::Underscore from CPAN via:
$ cpanm Util::Underscore
The documentation is available online on MetaCPAN. The source code is available on GitHub. If you encounter any problems during installation of usage of this module, please file an issue to the GitHub repository.
I later learned that the Data::Util module also implements
an is_array_ref()
function that tests for the logical reference type.
It is equivalent to the pure-Perl implementation above,
but is probably a lot faster since Data::Util implements these functions in XS (Perl's C binding language).
- next post: Unix is my IDE: script everything
- previous post: Dist::Zilla on Travis CI