Why should you NOT return an array ref?

11,798

Solution 1

You shouldn't return an array reference if it's inconsistent with the rest of your interface. If everything else that you work with returns lists instead of references, don't be the odd duck who causes other programmers to remember the exception.

Unless you have large lists, this is really a micro-optimization issue. You should be so lucky if this is the bottleneck in your program.

As far as complexity goes, the difference between a reference and a list is so far down on the complexity scale that you have bigger problems if your programmers are struggling with that. Complicated algorithms and workflows are complex, but this is just syntax.

Having said all of that, I tend to make everything return references and make interfaces consistent with that.

Solution 2

No. Except do "return $result;" for clarity.

I remember testing the efficiency of those, and the difference in performance was minimal for small arrays. For large arrays, returning a reference was way faster.

It's really a convenience thing for small result. Would you rather do this:

($foo,$bar) = barbaz();

Or returning a reference:

 $foobar = barbaz();
 $foobar->[0]; # $foo
 $foobar->[1]; # $bar

Another way to return a reference:

($foo,$bar) = @{barbaz()};

As a rule, once you decide which way to go, just keep to it for you module, since it makes it confusing to switch from one method to the next.

I typically return array references for lists of similar things, and an array when the response is composed of two to four different elements. More than that, I make a hash, since not all caller will care about all the response elements.

Solution 3

I'll copy the relevant portion of my answer from the other question here.

The oft overlooked second consideration is the interface. How is the returned array going to be used? This is important because whole array dereferencing is kinda awful in Perl. For example:

for my $info (@{ getInfo($some, $args) }) {
    ...
}

That's ugly. This is much better.

for my $info ( getInfo($some, $args) ) {
    ...
}

It also lends itself to mapping and grepping.

my @info = grep { ... } getInfo($some, $args);

But returning an array ref can be handy if you're going to pick out individual elements:

my $address = getInfo($some, $args)->[2];

That's simpler than:

my $address = (getInfo($some, $args))[2];

Or:

my @info = getInfo($some, $args);
my $address = $info[2];

But at that point, you should question whether @info is truly a list or a hash.

my $address = getInfo($some, $args)->{address};

Unlike arrays vs array refs, there's little reason to choose to return a hash over a hash ref. Hash refs allow handy short-hand, like the code above. And opposite of arrays vs refs, it makes the iterator case simpler, or at least avoids a middle-man variable.

for my $key (keys %{some_func_that_returns_a_hash_ref}) {
    ...
}

What you should not do is have getInfo() return an array ref in scalar context and an array in list context. This muddles the traditional use of scalar context as array length which will surprise the user.

I would like to add that while making everything consistently do X is a good rule of thumb, it is not of paramount importance in designing a good interface. Go a bit too far with it and you can easily steamroll other more important concerns.

Finally, I will plug my own module, Method::Signatures, because it offers a compromise for passing in array references without having to use the array ref syntax.

use Method::Signatures;

method foo(\@args) {
    print "@args";      # @args is not a copy
    push @args, 42;   # this alters the caller array
}

my @nums = (1,2,3);
Class->foo(\@nums);   # prints 1 2 3
print "@nums";        # prints 1 2 3 42

This is done through the magic of Data::Alias.

Solution 4

Since nobody mentioned about wantarray, I will :-)

I consider a good practice to let the caller decide what context it wants the result. For instance, in the code below, you ask perl for the context the subroutine was called and decide what to return.

sub get_things {
    my @things;
    ... # populate things
    return wantarray ? @things : \@things;
}

Then

for my $thing ( get_things() ) {
    ...
}

and

my @things = get_things();

works properly because of the list context, and:

my $things = get_things();

will return the array's reference.

For more info about wantarray you might want to check perldoc -f wantarray.

Edit: I over-sighted one of the first answers, which mentioned wantarray, but I think this is answer is still valid because it makes it a bit clearer.

Solution 5

I just want to comment on the idea about clumsy syntax of handling an array reference as opposed to a list. As brian mentioned, you really shouldn't do it, if the rest of the system is using lists. It's an unneeded optimization in most cases.

However, if that is not the case, and you are free to create your own style, then one thing that can make the coding less smelly is using autobox. autobox turns SCALAR, ARRAY and HASH (as well as others) into "packages", such that you can code:

my ( $name, $number ) = $obj->get_arrayref()->items( 0, 1 );

instead of the slightly more clumsy:

my ( $name, $number ) = @{ $obj->get_arrayref() };

by coding something like this:

sub ARRAY::slice { 
    my $arr_ref = shift;
    my $length  = @$arr_ref;
    my @subs    = map { abs($_) < $length ? $_ : $_ < 0 ? 0 : $#$arr_ref } @_;
    given ( scalar @subs ) { 
        when ( 0 ) { return $arr_ref; }
        when ( 2 ) { return [ @{$arr_ref}[ $subs[0]..$subs[1] ] ]; }
        default    { return [ @{$arr_ref}[ @subs ] ]; }
    }
    return $arr_ref; # should not get here.
}

sub ARRAY::items { return @{ &ARRAY::slice }; }

Keep in mind that autobox requires you to implement all the behaviors you want from these. $arr_ref->pop() doesn't work until you define sub ARRAY::pop unless you use autobox::Core

Share:
11,798
Joe Casadonte
Author by

Joe Casadonte

Updated on June 04, 2022

Comments

  • Joe Casadonte
    Joe Casadonte almost 2 years

    In the question "Is returning a whole array from a Perl subroutine inefficient" two people recommend against optimizing if there is no need for it. As a general rule, optimizing can add complexity, and if it's not needed, simple is better. But in this specific case, returning an array versus an array ref, I don't see that there's any added complexity, and I think consistency in the interface design would be more important. Consequently, I almost always do something like:

    sub foo
    {
       my($result) = [];
    
       #....build up the result array ref
    
       $result;
    }
    

    Is there a reason I should not do this, even for small results?

  • kmkaplan
    kmkaplan about 15 years
    Voted up, but little nitpick: I would ($foo, $bar) = @{barbaz()}
  • ysth
    ysth about 15 years
    Yes, perl copies data returned by subroutines.
  • brian d foy
    brian d foy about 15 years
    You don't really have copies of references. References point to the same data, so if you change the data through one reference, you'll see the change when you access it through the other.
  • Jacob
    Jacob about 15 years
    I could get even stranger, by using search.cpan.org/perldoc?Devel::Callsite.
  • Tom Alsberg
    Tom Alsberg about 15 years
    Yes, by copies of references, I meant of the reference itself (which can be changed to refer to something else), not of the data it refers to.
  • Joe Casadonte
    Joe Casadonte about 15 years
    That was pretty much my thinking, but I wanted to make sure that there wasn't a "Best Practices" answer that I didn't know about.
  • daotoad
    daotoad about 15 years
    The issue isn't unique to Perl; C can return a value or a reference. I'm sure there are plenty of other languages that offer similar choices in function call/return conventions. Java and Python are OOP languages--objects are passed as references, so it makes sense for them to pass by reference.
  • brian d foy
    brian d foy over 9 years
    Perl 5.20 added the postderef (experimental) syntax, so now you can do something like $obj->get_arrayref->@[0,1] to get that slice. I'm finding it really handy.
  • Axeman
    Axeman over 9 years
    @briandfoy, haven't tried that yet. Looks cool! I think my 5.20 Perl looks exactly like my 5.16 perl. :/