Reputation: 4452
Is it possible to in some way indicate if an array in Perl is undefined or null?
I find myself coming across situations where I would like to be able to differentiate between an empty array and one that hasn't been populated yet (or failed to populate for some reason).
So for instance, with an array reference I can do this:
my $apples;
$apples = get_apples();
if(defined $apples){
if(scalar @$apples == 0){
# We successfully got the list of apples, but there were none
}
}
else{
# There was a problem getting the list of apples
}
My only complaint about this is that "my $apples" doesn't tell you that $apples is intended to be an array reference, so @apples would be more specific.
It doesn't look there is a way to do something with an array explicitly. Is that the case? Will another variable always be required to indicate if the array was successfully populated?
The following could never be tested for a successful return of apples, right? Or am I missing something neat?
my @apples;
(@apples) = get_apples();
I know that get_apples could both return a success and a list to populate the array with, but I'm curious if there is a way to indicate a null or undefined value with just an array.
Upvotes: 3
Views: 9471
Reputation: 165606
No. Arrays can only be empty or contain scalars.
There is a better way to do what you want: throw an exception. Separating error codes and return values have been a bugaboo since the days of C. It complicates using the function and leads to more errors. Exceptions handily solve this problem AND you don't have to pepper your code with error checking (or more likely forget to).
sub get_apples {
...
die "How do you like them apples?" if $it_didnt_work;
return @apples;
}
# If get_apples() fails, the program throws an error. Good, that
# should be the default behavior.
my @apples = get_apples();
# Or maybe you want to do something with the error.
my @apples = eval { get_apples() };
if( $@ ) {
...handle the error...
}
Upvotes: 7
Reputation:
The reason why you can do my $apples;
, populate @$apples
in the get_apples()
subroutine, and later do if(@$apples==0)
is because of the autovivification of scalars.
As mob points out, this doesn't work for arrays.
A way around this might be to have get_apples()
pass a hash reference (or, if you want to be more Enterprisey, a GetAppleReturn
object) that, in pseudocode, looks like
{
success => 1,# or 0 if it failed
apples => [$apple1,$apple2,...] # Array reference of apples
}
So, then you could do:
my @apples;
my $rv = get_apples();
if($rv->{success})
{
if(scalar(@{$rv->{apples}}) == 0)
{
print "Success, but no apples.\n";
}
else
{
# Do whatever
}
}
else
{
print "There was a problem getting the apples. How do you like the apples?\n";
}
Upvotes: 0
Reputation: 400
You could return a single-element array containing an undef value to signify an error, then test like this:
my @apples = get_apples();
if (@apples) {
if (defined $apples[0]) {
# you have apples
} else {
# some error occurred
}
} else {
# no apples
}
Upvotes: 2
Reputation: 386706
Even if Perl could tell the difference between uninitialised and an empty array (which it can't), it wouldn't help you determine if get_apples
returned an error because you would have no way of making my @apples = get_apples()
not do the assignment when an error occurred.
You might be under the misconption that return @a
returns an array. Subs cannot return arrays. They can only return 0 or more scalars. return @a
returns the result of @a
, which is the contents of the array in list context.
There's is no way to distinguish zero elements returned due to an error from a successful response of zero elements through the returned values. (You could use an out-of-band channel such as an exception or a global variable, of course.)
Since subs can only return a list of scalars, there is only two things you can do:
In order to achieve your goal, you need to find a case where one of these differs for an error and for success.
When returning an array ref, one inspects if the returned value is defined or not.
You could do something similar if the first value returned (if any) on success will always be defined, but it's pretty ugly.
sub apples {
if (...error...) {
return undef;
} else {
return ...;
}
}
my @apples = apples();
if (@apples && !defined($apples[0])) {
... an error occurred...
}
I recommend against that.
Upvotes: -1
Reputation: 12097
How about using ref()
?
my $apples;
print 'what type of object is $apples? ' . ref($apples) . $/;
$apples = get_apples();
print 'what type of object is $apples now? ' . ref($apples) . $/;
sub get_apples {
my $empty_apple_array = [];
return $empty_apple_array;
}
when $apples is first created ref()
returns nothing because it's not a reference to anything yet.
Then we make it a reference to an empty array. Now ref()
knows it's an array reference, even if it's empty.
Upvotes: 0
Reputation: 118695
In Perl there is no difference between an empty array and an uninitialized array.
$ perl -MDevel::Peek -e 'print Dump(\@a)'
SV = RV(0x20033b00) at 0x20033af0
REFCNT = 1
FLAGS = (TEMP,ROK)
RV = 0x20091830
SV = PVAV(0x200350c0) at 0x20091830
REFCNT = 2
FLAGS = ()
ARRAY = 0x0
FILL = -1
MAX = -1
ARYLEN = 0x0
FLAGS = (REAL)
$ perl -MDevel::Peek -e '@a=(); print Dump(\@a)'
SV = RV(0x20033b00) at 0x20033af0
REFCNT = 1
FLAGS = (TEMP,ROK)
RV = 0x20091818
SV = PVAV(0x200350c0) at 0x20091818
REFCNT = 2
FLAGS = ()
ARRAY = 0x0
FILL = -1
MAX = -1
ARYLEN = 0x0
FLAGS = (REAL)
Your only hope may be to inspect the MAX
attribute of the internal AV
object to see whether an array used to contain any data:
use B;
@b = ();
@c = (1..100); @c = ();
print B::svref_2object(\@b)->MAX; # -1
print B::svref_2object(\@c)->MAX; # 99
Upvotes: 7