28

I have a perl variable $results that gets returned from a service. The value is supposed to be an array, and $results should be an array reference. However, when the array has only one item in it, $results will be set to that value, and not a referenced array that contains that one item.

I want to do a foreach loop on the expected array. Without checking ref($results) eq 'ARRAY', is there any way to have something equivalent to the following:

foreach my $result (@$results) {
    # Process $result
}

That particular code sample will work for the reference, but will complain for the simple scalar.

EDIT: I should clarify that there is no way for me to change what is returned from the service. The problem is that the value will be a scalar when there is only one value and it will be an array reference when there is more than one value.

Community
  • 1
  • 1
Rudd Zwolinski
  • 26,712
  • 17
  • 57
  • 60
  • This behavior makes me want to yell and be like, "STUPID PERL!" But then I realize that languages that dont require this nonsense still are doing it under the hood, which makes it bother me slighty less.... – Rooster Mar 10 '17 at 22:50

6 Answers6

26

im not sure there's any other way than:

$result = [ $result ]   if ref($result) ne 'ARRAY';  
foreach .....
svrist
  • 7,042
  • 7
  • 44
  • 67
12

Another solution would be to wrap the call to the server and have it always return an array to simplify the rest of your life:

sub call_to_service
{
    my $returnValue = service::call();

    if (ref($returnValue) eq "ARRAY")
    {
        return($returnValue);
    }
    else
    {
       return( [$returnValue] );
    }
}

Then you can always know that you will get back a reference to an array, even if it was only one item.

foreach my $item (@{call_to_service()})
{
  ...
}
user1917
  • 121
  • 1
  • 2
2

Well if you can't do...

for my $result ( ref $results eq 'ARRAY' ? @$results : $results ) {
    # Process result
}

or this...

for my $result ( ! ref $results ? $results : @$results ) {
    # Process result
}

then you might have to try something hairy scary like this!....

for my $result ( eval { @$results }, eval $results ) {
    # Process result
}

and to avoid that dangerous string eval it becomes really ugly fugly!!....

for my $result ( eval { $results->[0] } || $results, eval { @$results[1 .. $#{ $results }] } ) {
    # Process result
}

PS. My preference would be to abstract it away in sub ala call_to_service() example given by reatmon.

AndyG
  • 39,700
  • 8
  • 109
  • 143
draegtun
  • 22,441
  • 5
  • 48
  • 71
  • That's not a string eval. And looping over (some expression involving @$results) is very different from looping over (@$results). The former will copy the array (consuming memory); the latter will alias to it (and allow modifying elements via the loop variable). – ysth Dec 01 '08 at 03:06
  • @ysth - There is... see "eval $results". My suggestion was to use call_to_service() example given earlier. My answer was a bit of a "tongue in cheek solution" to the restriction imposed by the poster so yes its good to point out its flaws. – draegtun Dec 01 '08 at 14:41
0

I would re-factor the code inside the loop and then do

if( ref $results eq 'ARRAY' ){
    my_sub($result) for my $result (@$results);
}else{
    my_sub($results);
}

Of course I would only do that if the code in the loop was non-trivial.

Brad Gilbert
  • 33,846
  • 11
  • 78
  • 129
-1

I've just tested this with:

#!/usr/bin/perl -w
use strict;

sub testit {

 my @ret = ();
 if (shift){
   push @ret,1;
   push @ret,2;
   push @ret,3;
}else{
  push @ret,"oneonly";
}

return \@ret;
}

foreach my $r (@{testit(1)}){
  print $r." test1\n";
}
foreach my $r (@{testit()}){
   print $r." test2\n";
}

And it seems to work ok, so I'm thinking it has something to do with the result getting returned from the service? If you have no control over the returning service this might be hard one to crack

brian d foy
  • 129,424
  • 31
  • 207
  • 592
svrist
  • 7,042
  • 7
  • 44
  • 67
-1

You can do it like this:

my @some_array
push (@some_array, results);
foreach my $elt(@some_array){
  #do something
}
kayess
  • 3,384
  • 9
  • 28
  • 45
  • While this code snippet may solve the question, [including an explanation](//meta.stackexchange.com/questions/114762/explaining-entirely-code-based-answers) really helps to improve the quality of your post. Remember that you are answering the question for readers in the future, and those people might not know the reasons for your code suggestion. Please also try not to crowd your code with explanatory comments, this reduces the readability of both the code and the explanations! – kayess Nov 23 '16 at 11:03