3

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.

9
  • I'm not sure that's what you're asking, but Perl considers an empty array to be the failure value. Commented Mar 26, 2012 at 23:03
  • 2
    @Neil "False" is not synonymous with "failure". Commented Mar 26, 2012 at 23:09
  • 1
    I differentiate between someone telling me "There were no results matching your criteria" and "500 Internal Server Error" :P Commented Mar 26, 2012 at 23:14
  • 1
    @Neil Ahh, that's a convention generally followed by functions when they return a fixed size list like getpwuid and caller. Commented Mar 28, 2012 at 23:38
  • 1
    Actually I think there is no "undefined array": An Array is an array, possibly empty. Undefined is not an array. However your get_apples() could either return an array reference or undef. Commented Mar 26, 2019 at 13:28

6 Answers 6

7

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

7 Comments

I love your answer, but I hate that now I will start Peeking at things.
Subs can't return arrays, so what's this @a you inspect?
@ikegami If that's intended as a list vs array thing, that is unhelpfully obtuse and pedantic. Clear explanation or leave it alone.
@Schwern, I don't see how the above can possibly tell whether get_apples returned an error or not. The OP is trying to distinguis my @apples = get_apples(); # Empty list because of error from my @apples = get_apples(); # Empty list without error yet both result in my @apples = (); So what's the other array mob is dumping?
@mob, MAX might not be -1 even immediately after my @a. perl -MB -E'for (1..2) { my @a; @a = (4,5,6) if $_ == 1; say B::svref_2object(\@a)->MAX; }'
|
7
  • Is it possible to in someway indicate if an Array in Perl is undefined or null?

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...
}

3 Comments

This does a good job of showing how to use exceptions in perl: c2.com/cgi/wiki?ExceptionHandlingInPerl
What do you mean by "to let more errors"? "to more errors"? "to a lot more errors"? Or something else?
@PeterMortensen Typo. "Leads to more errors" by conflating error handling with the return value.
2

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
}

Comments

0

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.

1 Comment

Yeah, but the problem isn't how to handle this with references, it's how it would be handled with an array.
0

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";
}

Comments

-1

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:

  • Count the number of scalar returned.
  • Inspect the scalars returned.

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.

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.