7

Im working with some code that has a subroutine which includes an array reference as one of the parameters. The elements in this incoming array can be either small arrays or strings.

I want to determine what type each element is in order to do something specific (i.e. if the element is an array, drill into it further via indexing, if the element is a string, use the string)

I have tried using the ref function to interrogate each array element. It seems to work for elements that are ARRAYs, but if the element is a string, I was expecting the ref to return SCALAR. However ref() seems to return nothing. What am I doing wrong? I would think ref() would return something.

Here is some sample code:

my @array = ("string1", 
             ["ele1_arraystr1", "ele1_arraystr2"], 
             "string2", 
             ["ele4_arraystr1", "ele4_arraystr2"], 
             "etc");
my $tmp;
&foobar( 30, 20, \@array);

sub foobar {
    my($var1, $var2, $array_ref) = @_;
    foreach $element (@$array_ref) {
        my $tmp = ref($element);
        print "Array element type: $tmp\n";
        if ($tmp eq 'ARRAY') {
            print "  ARRAY: $element->[1]\n";

        } elsif ($tmp eq 'SCALAR') {
            print "  SCALAR: $element\n";
        } else {
            print "  Unexpected type: $tmp\n";
       }
    }
 }

The output looks something like this:

ARRAY element test:
Array element type: 
  Unexpected type: 
Array element type: ARRAY
  ARRAY: ele1_arraystr2
Array element type: 
  Unexpected type: 
Array element type: ARRAY
  ARRAY: ele4_arraystr2
Array element type: 
  Unexpected type: 
yoniyes
  • 1,000
  • 11
  • 23
BentChainRing
  • 382
  • 5
  • 14

2 Answers2

5

The ref returns an empty string if its argument isn't a reference. Docs say

Returns a non-empty string if EXPR is a reference, the empty string otherwise. The value returned depends on the type of thing the reference is a reference to.

The list that follows, which includes SCALAR, are the types that the reference can be to.

So when it has a string it returns an empty string, which evaluates to false. If you were to know for fact that it's ARRAY or string, you could do

if (ref($element) eq 'ARRAY') {
    # process the arrayref
}
else { 
    # process the string
}

Better check for the string (false) specifically, as you do, so to be able to detect any other types

my $ref_type = ref($element);
if ($ref_type eq 'ARRAY') {
    # process arrayref
}
elsif (not $ref_type) {  
    # process string
}
else { print "Got type $ref_type\n" }
zdim
  • 64,580
  • 5
  • 52
  • 81
  • I left the elsif in there because there may be other types of references in the future. I see what Im missing now, based on this answer and the one below. Apparently, because the string itself is not a reference ref() returns zero or false, not a type string. Thank you. – BentChainRing Sep 12 '16 at 18:19
  • @BentChainRing OK, good practice. Then you can test for the string by `elsif (not $tmp) { }`, since it is the only case where `ref` returns a false (empty string). Then you'd have `else { }` for detecting references to yet other types in the future (or spell them out in a sequence of specific `elsif`). – zdim Sep 12 '16 at 18:27
  • @BentChainRing Thanks again for the comment, that is of course better practice. I got misled by the question's statement that it is only one of the two (and wanted to convey the basic point). Adjusted the answer. – zdim Sep 12 '16 at 18:49
4

All of this is documented in perldoc perlfunc under ref

ref returns a false value if its parameter isn't a reference. It will return SCALAR only if the parameter is a reference to a scalar

You may also need to know that for a reference to blessed data—a Perl object—ref returns the class that the data has been blessed into, and not the underlying data type. If you need to distinguish between the two, then the core Scalar::Util module provides blessed, which returns the class into which the data has been blessed, and reftype, which returns the type of the underlying data in the same way as ref

You could make your foobar recursive to process an indefinitely-nested data structure, like this

use strict;
use warnings 'all';
use feature 'say';

my @array = (
    "string1", [ "ele1_arraystr1", "ele1_arraystr2" ],
    "string2", [ "ele4_arraystr1", "ele4_arraystr2" ],
    "etc",     [ "etc1", "etc2" ]
);

foobar(\@array);

sub foobar {
    my ($val, $indent) = (@_);
    $indent //= 0;
    my $ref = ref $val;

    if ( $ref eq 'ARRAY' ) {
        foobar($_, $indent+1) for @$val;
    }
    elsif ( not $ref ) {
        say '  ' x $indent, $val;
    }
}

output

  string1
    ele1_arraystr1
    ele1_arraystr2
  string2
    ele4_arraystr1
    ele4_arraystr2
  etc
    etc1
    etc2

Alternatively, if your array always consists of alternating strings and array references, you may find it easier to just assign it to a hash. That would use the strings as hash keys with their corresponding array references as hash values

This code shows the idea. I have used Data::Dump to reveal the resulting data structure

my %data = @array;

use Data::Dump;
dd \%data;

output

{
  etc => ["etc1", "etc2"],
  string1 => ["ele1_arraystr1", "ele1_arraystr2"],
  string2 => ["ele4_arraystr1", "ele4_arraystr2"],
}
Borodin
  • 126,100
  • 9
  • 70
  • 144
  • I may use that routine logic the next time I get back to this code. I have a couple of other priorities in the short term right now. Thanks for pointing out, as the other poster did, that ref() was not seeing the independent strings as references, and ref() only returns the type of _reference_. – BentChainRing Sep 12 '16 at 18:57
  • @BentChainRing: Yes, hence the name of the operator! You may also need to know that for a reference to *blessed* data—a Perl object—`ref` returns the *class* that the data has been blessed into, and not the underlying data type. If you need to distinguish between the two, then the [Scalar::Util](https://metacpan.org/pod/Scalar::Util) module provides `blessed`, which returns the class into which the data has been blessed, and `reftype`, which returns the type of the underlying data in the same way as `ref`. – Borodin Sep 12 '16 at 20:56