Warm tip: This article is reproduced from serverfault.com, please click

Properly detecting if Perl subroutine argument is array

发布于 2020-11-29 16:37:33

I'm trying to port a library from Javascript to Perl, but having some issues detecting array arguments.

A stripped-down example (in Javascript) looks something like this:

const say = console.log;

function test(array, integer)
{
 if(Array.isArray(array))
 {
  say("First argument is array");
 }
 else
 {
  say("First argument is not array");
  array = [array];
 }
 for(let index = 0; index < array.length; ++index)
 {
  say(array[index]);
 }
 say("Second argument: ", integer);
 say("");
}

var a = ["foo", "bar"];
var t = "baz";
var i = 1024;

say("Testing on array");
test(a, i);

say("Testing on string");
test(t, i);

Output:

Testing on array
First argument is array
foo
bar
Second argument:  1024

Testing on string
First argument is not array
baz
Second argument:  1024

And here's the Perl version:

use feature qw(say);

sub test 
{
 my ($text, $integer ) = @_;
 my (@array) = @_;  
 if(ref(@array) == "ARRAY") 
 {
  say("First argument is array");
 }
 else 
 {
  say("First argument is not array");
  @array = ($text);
 }
 for my $index (0 .. $#array)
 {
  say($array[$index]);
 }
 say("Second argument: ", $integer);     
}

my @a = ("foo", "bar");
my $t = "baz";
my $i = 1024;

say("Testing on array");
test(@a, $i);

say("Testing on string");
test($t, $i);

Output:

Testing on array
First argument is array
foo
bar
1024
Second argument: bar

Testing on string
First argument is array
baz
1024
Second argument: 1024

I've also tried numerous other ways, such as prepending the backslash to the array name and so forth, but to no avail. I'm fairly certain that this must be possible in Perl. Or perhaps this is some sort of limitation of the language itself?

Questioner
Sir Galahad
Viewed
0
TLP 2020-11-30 01:14:16

Perl subroutines do not take arrays as arguments, only scalars. Arrays in the sub call are collapsed into a list of scalars. You have 2 options if you want to pass an array.

  1. Pass an array ref, e.g. test(\@a, $i), or test([ @a ], $i). Or better yet, pass the scalar arguments first, then take the rest of the arguments into an array.
  2. Delve into Perl prototypes and learn how they work.

I suggest solution 1. Typically you can then do something like

test($i, @a);

sub test {
    my $i = shift;
    my @a = @_;
    ....
}

Or

test($i, [ @a ]);

sub test {
    my $i = shift;
    my $aref = shift;
    my @a = @$aref;       # expand to normal array (not strictly needed)
    ....
}

Note that passing the reference to the array @a will allow the sub to alter the values in the array, so passing it as an anonymous array [ @a ] is safer.

Also, the code:

my @a;
print ref(\@a);

Will print ARRAY, but ref(@a) will print nothing.

Also, if you had used

use strict;
use warnings;

You would have gotten the very enlightening warning

Argument "ARRAY" isn't numeric in numeric eq (==) 

Which tells you that in string comparison, you should use eq and not == which is numerical. Because Perl will silently cast the empty string to 0 in numerical context, and it will cast the string ARRAY to 0, the result of the comparison is true, which leads your comparison to return a false positive.

Never write Perl code without strict and warnings.