GoldenNewby
GoldenNewby

Reputation: 4452

Undefined Array in Perl

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

Answers (6)

Schwern
Schwern

Reputation: 165606

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

Upvotes: 7

user554546
user554546

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

Phil Harvey
Phil Harvey

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

ikegami
ikegami

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:

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

Upvotes: -1

AmbroseChapel
AmbroseChapel

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

mob
mob

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

Related Questions