Best way to use "isa" method?

You can wrap the safety checks in a scalar and then use the scalar as a method to keep things clean:

use Scalar::Util 'blessed';

my $isa = sub {blessed $_[0] and $_[0]->isa($_[1])};

my $obj;

if ($obj->$isa('object')) { ... } # returns false instead of throwing an error 

$obj = {};

if ($obj->$isa('object')) { ... } # returns false as well

bless $obj => 'object';

if ($obj->$isa('object')) { say "we got an object" }

Note that $obj->$isa(...) is just a different spelling of $isa->($obj, ...) so no method call actually takes place (which is why it avoids throwing any errors).

And here is some code that will allow you to call isa on anything and then inspect the result (inspired by Axeman's answer):

{package ISA::Helper;
    use Scalar::Util;
    sub new {
        my ($class, $obj, $type) = @_;
        my $blessed = Scalar::Util::blessed $obj;
        bless {
            type    => $type,
            obj     => $obj,
            blessed => $blessed,
            isa     => $blessed && $obj->isa($type)
        } => $class
    }
    sub blessed         {$_[0]{blessed}}
    sub type            {$_[0]{isa}}
    sub ref     {ref     $_[0]{obj}}
    sub defined {defined $_[0]{obj}}

    use overload fallback => 1,
                 bool     => sub {$_[0]{isa}};
    sub explain {
        my $self = shift;
        $self->type    ? "object is a $$self{type}" :
        $self->blessed ? "object is a $$self{blessed} not a $$self{type}" :
        $self->ref     ? "object is a reference, but is not blessed" :
        $self->defined ? "object is defined, but not a reference"
                       : "object is not defined"
    }
}
my $isa = sub {ISA::Helper->new(@_)};

By placing the code reference in a scalar, it can be called on anything without error:

my @items = (
    undef,
    5,
    'five',
    \'ref',
    bless( {} => 'Other::Pkg'),
    bless( {} => 'My::Obj'),
);

for (@items) {
    if (my $ok = $_->$isa('My::Obj')) {
        print 'ok: ', $ok->explain, "\n";
    } else {
        print 'error: ', $ok->explain, "\n";
    }
}

print undef->$isa('anything?')->explain, "\n";

my $obj = bless {} => 'Obj';
print $obj->$isa('Obj'), "\n";

my $ref = {};
if (my $reason = $ref->$isa('Object')) {
    say "all is well"
} else {
    given ($reason) {
        when (not $_->defined) {say "not defined"}
        when (not $_->ref)     {say "not a reference"}
        when (not $_->blessed) {say "not a blessed reference"}
        when (not $_->type)    {say "not correct type"}
    }
}

this prints:

error: object is not defined
error: object is defined, but not a reference
error: object is defined, but not a reference
error: object is a reference, but is not blessed
error: object is a Other::Pkg not a My::Obj
ok: object is a My::Obj
object is not defined
1
not a blessed reference

If anyone thinks this is actually useful, let me know, and I will put it up on CPAN.


This might sound a little bit harsh to Perl, but neither one of these is ideal. Both cover up the fact that objects are a tack on to Perl. The blessed idiom is wordy and contains more than a couple simple pieces.

blessed( $object ) && object->isa( 'Class' )

I would prefer something more like this:

object_isa( $object, 'Class' )

There is no logical operation to get wrong, and most of the unfit uses will be weeded out by the compiler. (Quotes not closed, no comma, parens not closed, calling object_isa instead...)

It would take undefined scalars, simple scalars (unless they are a classname that is a Class), unblessed references, and blessed references that do not extend 'Class' and tell you that no, they are not Class objects. Unless we want to go the route of autobox-ing everything, we're going to need a function that tells us simply.

Perhaps there might be a third parameter for $how_close, but there could also be something like this:

if ( my $ranking = object_isa( $object, 'Class' )) { 
   ...
}
else { 
   given ( $ranking ) { 
       when ( NOT_TYPE )    { ... }
       when ( NOT_BLESSED ) { ... }
       when ( NOT_REF )     { ... }
       when ( NOT_DEFINED ) { ... }
   }
}

About the only way I can see that we could return this many unique falses is if $ranking was blessed into a class that overloaded the boolean operator to return false unless the function returned the one value indicating an ISA relationship.

However, it could have a few members: EXACTLY, INHERITS, IMPLEMENTS, AGGREGATES or even MOCKS

I get tired of typing this too:

$object->can( 'DOES' ) && $object->DOES( 'role' )

because I try to implement the future-facing DOES in lesser perls (on the idea that people might frown on my polluting UNIVERSAL) on them.


Here's an update for 2020. Perl v5.32 has the isa operator, also known as the class infix operator. It handles the case where the left-hand argument is not an object it returns false instead of blowing up:

use v5.32;
if( $something isa 'Animal' ) { ... }

The Scalar::Util implementation is categorically better. It avoids the overhead of the eval {} which always results in the setting of an additional variable.

perl -we'$@=q[foo]; eval {}; print $@'

The Scalar::Util implementation is easier to read (it doesn't die for a reason that is unknown to the code). If the eval fails too, I believe what happens is you have walk backwards in the tree to the state prior to the eval -- this is how resetting state is achieved. This comes with additional overhead on failure.

Benchmarks

Not an object at all

          Rate eval   su
eval  256410/s   -- -88%
su   2222222/s 767%   --

Object passing isa check

          Rate   su eval
su   1030928/s   -- -16%
eval 1234568/s  20%   --

Object failing isa check

         Rate   su eval
su   826446/s   --  -9%
eval 909091/s  10%   --

Test code:

use strict;
use warnings;
use Benchmark;
use Scalar::Util;

package Foo;

Benchmark::cmpthese(
    1_000_000
    , {
        eval => sub{ eval{ $a->isa(__PACKAGE__) } }
        , su => sub { Scalar::Util::blessed $a && $a->isa(__PACKAGE__) }
    }
);

package Bar;

$a = bless {};

Benchmark::cmpthese(
    1_000_000
    , {
        eval => sub{ eval{ $a->isa(__PACKAGE__)} }
        , su => sub { Scalar::Util::blessed $a && $a->isa(__PACKAGE__) }
    }
);

package Baz;

$a = bless {};

Benchmark::cmpthese(
    1_000_000
    , {
        eval => sub{ eval{ $a->isa('duck')} }
        , su => sub { Scalar::Util::blessed $a && $a->isa( 'duck' ) }
    }
);

I used This is perl, v5.10.1 (*) built for i486-linux-gnu-thread-multi, and Scalar::Util, 1.21

Tags:

Perl