Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best way to use "isa" method?

Tags:

perl

What is the "best" way to use "isa()" reliably? In other words, so it works correctly on any value, not just an object.

By "best", I mean lack of un-handled corner cases as well as lack of potential performance issues, so this is not a subjective question.

This question mentions two approaches that seem reliable (please note that the old style UNIVERSAL::isa() should not be used, with reasons well documented in the answers to that Q):

eval { $x->isa("Class") }
#and check $@ in case $x was not an object, in case $x was not an object

use Scalar::Util 'blessed';
blessed $x && $x ->isa($class);

The first one uses eval, the second uses B:: (at least for non-XS flavor of Scalar::Util).

The first does not seem to work correctly if $x is a scalar containing a class name, as illustrated below, so I'm leaning towards #2 (using blessed) unless somoene indicates a good reason not to.

$ perl5.8 -e '{use IO::Handle;$x="IO::Handle";
  eval {$is = $x->isa("IO::Handle")}; print "$is:$@\n";}'          
1:

Are there any objective reasons to pick one of these two approaches (or a 3rd one i'm not aware of) such as performance, not handling some special case, etc...?

like image 371
DVK Avatar asked Jun 04 '10 21:06

DVK


2 Answers

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.

like image 32
Eric Strom Avatar answered Oct 04 '22 07:10

Eric Strom


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

like image 163
NO WAR WITH RUSSIA Avatar answered Oct 04 '22 06:10

NO WAR WITH RUSSIA