Ilia Ross
Ilia Ross

Reputation: 13412

Accessing array when passed to subroutine as reference in Perl

I have misunderstanding, in using curly brackets in Perl with references. I've built a basic function to check if array contains a value.

Example 1:

sub array_contains
{
    my ($arr_ref, $search, $strict) = @_;
    if ($strict) {
        return (grep {$_ eq $search} @$arr_ref);
    } else {
        return grep {index($_, $search) != -1} @$arr_ref;
    }
}

Later, I wanted to make it look more concise.

Example 2:

sub array_contains
{
    return (!$_[2] ? 
       (grep {$_ eq $_[1]} @{$_[0]}) : 
       (grep {index($_, $_[1]) != -1} @{$_[0]})
    );
}

Then, it took me some time to realize that I can't use @$_[0] for some reason, like in the first example, and have to go with @{$_[0]}.


Could Perl experts please explain, why does it have to be on the second example @{$_[0]} and can't be @$_[0], just like in the first example, where I used @$arr_ref. Does it have to do anything with assigning @_ to the locally scoped variables?

Upvotes: 1

Views: 101

Answers (2)

haukex
haukex

Reputation: 3013

The way to think about this is that the dereferencing "operator" has higher "precedence" than the array indexing (using quotes here because dereferencing isn't officially part of Perl's operator precedence table).

use warnings;
use strict;

my $foo = ['arrayref elem 0','arrayref elem 1'];
my @foo = (['array of arrayrefs elem 0',' one']);

print  @$foo[0] , "\n";  # prints "arrayref elem 0"

# is the same as
print @{$foo}[0], "\n";  # prints "arrayref elem 0"

# is NOT the same as
print @{$foo[0]}, "\n";  # prints "array of arrayrefs elem 0 one"

That is, in the first two examples, first the array reference stored in $foo is dereferenced as an array, and then that array is indexed with [0] (although that's using slice syntax, $$foo[0] and ${$foo}[0] would make more sense here). Only in the last example is the array @foo element [0] accessed first, and then that element is dereferenced as an array, resulting in all the elements of that array being printed. The behavior is the same for $_/@_.

See also Using References in perlref: the @{...} style of dereferencing always works, while the braces can only be omitted in simple cases, such as your @$arr_ref.

If you have Perl 5.24 or newer, you can make use of the Postfix Dereference Syntax (it was experimental in 5.20 and 5.22):

use 5.024;
my @bar = (['Hello,',' World!']);
print $bar[0]->@*, "\n";  # prints "Hello, World!"

Upvotes: 4

amon
amon

Reputation: 57630

Prefix dereference operators as in @$arr or @$_[0] bind very tightly – so the latter is parsed as @{ $_ }[0]. This is an array slice, which uses $_ as an array reference. This is unrelated to the @_ array. A slice of one element would usually be written as a scalar access ${ $_ }[0] or $_->[0].

Because prefix dereference operators parse confusingly, I recommend the postfix dereference syntax that is available since Perl 5.24. Then @{ $_[0] } can be written as $_[0]->@*.

Note that code should be clear, which is not necessarily the same as short. Naming your arguments instead of accessing the @_ array directly is much more maintainable. I only access @_ directly if there's only one argument (e.g. in an accessor method sub foo { shift->{foo} }) or when I benchmarked the code and have determined that avoiding a copy will save me half a microsecond that I really need. (Doesn't happen very often.)

If the goal of your function is a boolean check rather than selecting all matching elements, I'd write it like this:

use List::Util 'any';

sub array_contains {
  my ($arr, $search, $is_strict) = @_;
  return any { $_ eq $search } @$arr if $is_strict;
  return any { -1 != index $_, $search } @$arr;
}

Upvotes: 2

Related Questions