reftype() lazziness

Burak on 2009-09-10T03:24:00

I was doing a lot of foo() if ref($thing) eq 'ARRAY' or foo() if ref($thing) eq 'HASH' (even something idiotic like foo() if ref($thing) && "$thing" =~ m{.+?=HASH(0x.+?)}) checks inside a pet project of mine. So thought about making it in an elegant way instead. I mean ref($thing)->array or even ref($thing)->is_array seemed much better as a syntactic sugar. However ref() is clearly not the way to go for the implementation as it fails to identify the underlying type of objects. So, the obvious choice is Scalar::Util::reftype. I've named the module Scalar::Util::Reftype. In a way, I can say that it's similar to File::stat. Btw, Scalar::Util::reftype has one oddity: unlike CORE::ref it returns undef if you pass a non-ref parameter to it. So, instead of foo() if reftype($thing) eq 'ARRAY' one must say foo() if defined reftype($thing) && reftype($thing) eq 'ARRAY' or just foo() if reftype($thing) && reftype($thing) eq 'ARRAY' to prevent an annoying warning:

C:\>perl -MScalar::Util=reftype -wle "my $x; print reftype($x) eq 'ARRAY'" Use of uninitialized value in string eq at -e line 1. It also can not detect Regexp (CORE::ref can, as long as it's not blessed): C:\>perl -MScalar::Util=reftype -wle "my $x = qr//; print reftype $x" SCALAR C:\>perl -wle "my $x = qr//; print ref $x" Regexp C:\>

Fortunately, perl 5.10 comes with re::is_regexp to detect if an object is based on a regex or not. But what about older perls? We can remedy the situation with the help of Data::Dump::Streamer::regex under at least perl 5.8.x. Unfortunately Data::Dump::Streamer seems to fail under anything older than that. I wasn't aware that re::is_regexp is a new functionality until reached "ref() and Regexp" discussion on PerlMonks.

I also checked ref documentation. As of perl 5.10 it lists these reference types: SCALAR ARRAY HASH CODE REF GLOB LVALUE FORMAT IO VSTRING Regexp Only perl 5.10's ref seems to detect VSTRING refs and since they are deprecated and the usage seems to be rare, the module does not support them. Also FORMAT is only available in perl 5.8 and newer. But frankly, I can't imagine anyone creating refs/objects based on LVALUE, FORMAT or VSTRING (hmmm... maybe only TheDamian). So, they exist only for the sake of compatibility. For the Regexp type, I've just added a dynamic dependency on Data::Dump::Streamer if Scalar::Util::Reftype is tried to be installed under anything older than perl 5.10.

The interface is simple. Just use the module to get a brand new reftype function:

use Scalar::Util::Reftype; foo() if reftype( "string" )->hash; # foo() will never be called bar() if reftype( \$var )->scalar; # bar() will be called baz() if reftype( [] )->array; # baz() will be called xyz() if reftype( sub {} )->array; # xyz() will never be called $obj = bless {}, "Foo"; my $rt = reftype( $obj ); $rt->hash; # false $rt->hash_object; # true $rt->class; # "Foo"

reftype will create an object based on the parameter you specified and it is possible to call test methods on the return value. It currently has these test methods:

scalar array hash code glob lvalue format ref io regexp scalar_object array_object hash_object code_object glob_object lvalue_object format_object ref_object io_object regexp_object class

Here, class can be thought as analogous to Scalar::Util' s blessed function. It returns the package/class name of the reference if it happens to be a blessed reference. The rest of the methods test if the parameter matches the type they define.

Oh, I've also overloaded the object Scalar::Util::Reftype:reftype returns, to be sure that it will not be used in boolean contexts. If one makes such a thing, then the code will suffer the consequences :)


One level too-removed

jjore on 2009-09-10T17:05:46

I'm biased - I wrote UNIVERSAL::ref. I think you should be writing $thing->is_array instead of ref($thing)->is_array or Scalar::Util::reftype($thing)->is_array. That is, the thing itself should be responsible for telling you about itself and not some external bit of code.

I trend toward the Smalltalk end of the idea spectrum and I'd prefer that $thing be able to respond for itself. Outsourcing introspection prevents $thing from being able to intelligently decide to answer the question *differently* sometimes.

Also, when you're writing your ->is_array code, are you accounting for things that aren't arrays but act like them because of overloading?

package ...;
use overload '@{}' => 'act_like_an_array';