tags:

views:

299

answers:

6

How do I tell what type of value is in a Perl variable?

$x might be a scalar, a ref to an array or a ref to a hash (or maybe other things).

+10  A: 

ref():

http://www.cs.cf.ac.uk/Dave/PERL/node62.html

ctd
I thought ref() would only tell you what type of reference it is and return nothing if it isn't one.
Chris Kloberdanz
thx - well i wanted to say thx only - but its not allowed
pm100
@Chris: Right, so if the variable is not a reference, you can infer that it is a simple scalar from the fact that it returns nothing. Otherwise, you will know what kind of reference it is.
Adam Bellaire
@pm100: You're absolutely welcome; perhaps you could edit your question saying what constraints you have?
ctd
@Adam: I guess I was thinking in more in general, but the OP did say $x (as opposed to @x or %x). So, you're right.
Chris Kloberdanz
I think pm100's was talking about the 15 char comment constraint on SO.
mobrule
+23  A: 

$x is always a scalar. The hint is the sigil $: any variable (or dereferencing of some other type) starting with $ is a scalar. (See perldoc perldata for more about data types.)

A reference is just a particular type of scalar. The built-in function ref will tell you what kind of reference it is. On the other hand, if you have a blessed reference, ref will only tell you the package name the reference was blessed into, not the actual core type of the data (blessed references can be hashrefs, arrayrefs or other things). You can use Scalar::Util 's reftype will tell you what type of reference it is:

use Scalar::Util qw(reftype);

my $x = bless {}, 'My::Foo';
my $y = { };

print "type of x: " . ref($x) . "\n";
print "type of y: " . ref($y) . "\n";
print "base type of x: " . reftype($x) . "\n";
print "base type of y: " . reftype($y) . "\n";

...produces the output:

type of x: My::Foo
type of y: HASH
base type of x: HASH
base type of y: HASH

For more information about the other types of references (e.g. coderef, arrayref etc), see this question: http://stackoverflow.com/questions/1399833/how-can-i-get-perls-ref-function-to-return-ref-io-and-lvalue and perldoc perlref.

Note: You should not use ref to implement code branches with a blessed object (e.g. $ref($a) eq "My::Foo" ? say "is a Foo object" : say "foo not defined";) -- if you need to make any decisions based on the type of a variable, use isa (i.e if ($a->isa("My::Foo") { ... or if ($a->can("foo") { ...). Also see polymorphism.

Ether
Note that reftype, by definition, violates encapsulation, so should be avoided unless you have a very good reason.
ysth
If you do use reftype, be aware that it returns undef for a non-reference, so code like `reftype($x) eq 'HASH'` may result in warnings. (ref, on the other hand, conveniently returns '' for non-references.)
ysth
@ysth: quite so! I've updated my response.. it's rare to find a good use for `ref` on blessed objects.
Ether
(er, I meant to say `reftype` of course.) :) If one needs to access a blessed object directly, there should be an accessor method which abstracts away the implementation details.
Ether
+7  A: 

A scalar always holds a single element. Whatever is in a scalar variable is always a scalar. A reference is a scalar value.

If you want to know if it is a reference, you can use ref. If you want to know the reference type, you can use the reftype routine from Scalar::Util.

If you want to know if it is an object, you can use the blessed routine from Scalar::Util. You should never care what the blessed package is, though. UNIVERSAL has some methods to tell you about an object: if you want to check that it has the method you want to call, use can; if you want to see that it inherits from something, use isa; and if you want to see it the object handles a role, use DOES.

If you want to know if that scalar is actually just acting like a scalar but tied to a class, try tied. If you get an object, continue your checks.

If you want to know if it looks like a number, you can use looks_like_number from Scalar::Util. If it doesn't look like a number and it's not a reference, it's a string. However, all simple values can be strings.

If you need to do something more fancy, you can use a module such as Params::Validate.

brian d foy
+1  A: 

At some point I read a reasonably convincing argument on Perlmonks that testing the type of a scalar with ref or reftype is a bad idea. I don't recall who put the idea forward, or the link. Sorry.

The point was that in Perl there are many mechanisms that make it possible to make a given scalar act like just about anything you want. If you tie a filehandle so that it acts like a hash, the testing with reftype will tell you that you have a filehanle. It won't tell you that you need to use it like a hash.

So, the argument went, it is better to use duck typing to find out what a variable is.

Instead of:

sub foo {
    my $var = shift;
    my $type = reftype $var;

    my $result;
    if( $type eq 'HASH' ) {
        $result = $var->{foo};
    }
    elsif( $type eq 'ARRAY' ) {
        $result = $var->[3];
    }
    else {
        $result = 'foo';
    }

    return $result;
}

You should do something like this:

sub foo {
    my $var = shift;
    my $type = reftype $var;

    my $result;

    eval {
        $result = $var->{foo};
        1; # guarantee a true result if code works.
    }
    or eval { 
        $result = $var->[3];
        1;
    }
    or do {
        $result = 'foo';
    }

    return $result;
}

For the most part I don't actually do this, but in some cases I have. I'm still making my mind up as to when this approach is appropriate. I thought I'd throw the concept out for further discussion. I'd love to see comments.

Update

I realized I should put forward my thoughts on this approach.

This method has the advantage of handling anything you throw at it.

It has the disadvantage of being cumbersome, and somewhat strange. Stumbling upon this in some code would make me issue a big fat 'WTF'.

I like the idea of testing whether a scalar acts like a hash-ref, rather that whether it is a hash ref.

I don't like this implementation.

daotoad
I'm probably the one you're thinking about. I say never test against literal strings. Test against prototypes: if( ref $f eq ref {} ). As for tie, you start with tied(): if you get an object, do the normal stuff.
brian d foy
Please never, never actually test refs that way. It's so easy to do it easier. :)
brian d foy
brain, that's another interesting point--not the one I was thinking of. I can see your point though.
daotoad
Very interesting, but this is better suited for a discussion forum like clp.misc than SO.
Michael Carman
@brian: what's wrong with testing against literal strings? The values `ref()` returns aren't going to change and it's clearer (at least arguably so). Moreover, how do you construct a prototype for things like SCALAR and GLOB references?
Michael Carman
I don't like literal strings because people tend to type them incorrectly or misremember them. It's part of a more general issue of any "magic strings" as source code or constants. Since there is a way to do it without special knowledge of magic strings that, I do that.
brian d foy
If you think clp.misc is good for anything, you haven't been there in awhile. I already know what the five clueful people on that list will say, and two of them are permanantly killfilled. :) Seriously though, I think SO is a fine place for this, although it probably deserves it's own question.
brian d foy
@Michael Carman: FWIW, the values ref returns historically *have* changed, but not in ways that brian d foy's suggestion would have helped with.
ysth
The acts-like test is great if you want to validate that, say, what you expect is a hashref is in fact one. A series of ifs breaks the strength of this approach, which is to correctly handle multiple overloaded deref types. (Though the auto-creation of subs makes acts-like-a-coderef tests impossible to do correctly, and pseudohashes used to be a problem too.)
ysth
The problem with "acts like" tests is that a tied variable might have side effects even for mere accesses. There are already ways to introspect the variable without actually doing anything with it.
brian d foy
@Michael: surely you know how to get a scalar and glob ref: `ref \ ''` for scalar and `ref \*STDOUT` (or similar) for glob.
brian d foy
+2  A: 

I like polymorphism instead of manually checking for something:

use MooseX::Declare;

class Foo {
    use MooseX::MultiMethods;

    multi method foo (ArrayRef $arg){ say "arg is an array" }
    multi method foo (HashRef $arg) { say "arg is a hash" }
    multi method foo (Any $arg)     { say "arg is something else" }
}

Foo->new->foo([]); # arg is an array
Foo->new->foo(40); # arg is something else

This is much more powerful than manual checking, as you can reuse your "checks" like you would any other type constraint. That means when you want to handle arrays, hashes, and even numbers less than 42, you just write a constraint for "even numbers less than 42" and add a new multimethod for that case. The "calling code" is not affected.

Your type library:

package MyApp::Types;
use MooseX::Types -declare => ['EvenNumberLessThan42'];
use MooseX::Types::Moose qw(Num);

subtype EvenNumberLessThan42, as Num, where { $_ < 42 && $_ % 2 == 0 };

Then make Foo support this (in that class definition):

class Foo {
    use MyApp::Types qw(EvenNumberLessThan42);

    multi method foo (EvenNumberLessThan42 $arg) { say "arg is an even number less than 42" }
}

Then Foo->new->foo(40) prints arg is an even number less than 42 instead of arg is something else.

Maintainable.

jrockway
A: 

wow - lot of stuff comes back from the interwebs.

I had a specific thing in mind. I wrote a module that reads data off a network and stores each 'record' as a hash of key->value in an array of hashes. Sometimes the the value is an array, sometimes a single value. The multi value case is rare so I wanted to allow the user of the module to just go $rec->{foo}, but if they do need a way to tell if it is multi valued

Also when a caller passes me a 'record' to write I need to see if the value is multi-valued or not

Maybe I should just make all values always an array - with a single value 99% of the time.

pm100
Edit your question, rather than post "answers".
Brad Gilbert