Aric has asked for the wisdom of the Perl Monks concerning the following question:

In general, what is the best way of testing if $object->top->bottom exists? I don't care (IOW it is OK) if bottom returns undef. Perhaps bottom or top do not exist. I don't want to put it in an eval because that could potentially execute bottom. Is it bad to rely on UNIVERSAL::can:
if $object->can('top') and $object->top->can('bottom') ...
? (Using perl 5.8)

Replies are listed 'Best First'.
Re: Does Method Exist?
by ikegami (Patriarch) on Aug 24, 2009 at 15:00 UTC

    I don't want to put it in an eval because that could potentially execute bottom.

    Do you have a problem executing top?
    $object->top->bottom
    is the same as
    ( $object->top() )->bottom
    and that executes top.


    If the problem is that you are dealing with multiple classes, and some don't implement top and bottom, using a role/trait might make more sense (Class::Role, Class::Trait)

    If the problem is that an object might not have a bottom or top sibling and returns false/undefined in those cases, the following would be better:

    if ($object->top && $object->top->bottom) { ... }
    or
    if (defined($object->top) && defined($object->top->bottom)) { ... }
Re: Does Method Exist?
by moritz (Cardinal) on Aug 24, 2009 at 14:56 UTC
    UNIVERSAL::can isn't harmful (IMHO), and indeed the way to go.

    Depending on your context you might also use an eval block, a non-existing method throws an exception. Sadly other errors also throw exceptions, so you might have to check for the exact exception.

    (Update: s/isa/can/, sorry for the thinko)

    Perl 6 projects - links to (nearly) everything that is Perl 6.
Re: Does Method Exist?
by Sue D. Nymme (Monk) on Aug 24, 2009 at 15:49 UTC

    The problem with calling can directly is that you will get an error if you invoke it on something that's not an object:

    my $obj = MyClass->new(); # what if it returns undef on error? if ($obj->can('top') # dies!

    The traditional way around this is to invoke UNIVERSAL::can (or isa) directly:
        if (UNIVERSAL::can($obj, 'top'))

    But this should be avoided. The problem with using these directly is that you don't know if the class author has chosen to implement some specific can or isa functionality. I'm not sure what such functionality might be, mind you, but the principle is that you shouldn't explicitly call a base class's method.

    The solution is to use the blessed function from the Scalar::Util module:

    # Instead of: if (UNIVERSAL::isa($obj, 'MyClass') # do this: if (blessed $obj && $obj->isa('MyClass')) # Instead of: if (UNIVERSAL::can($obj, 'top') && UNIVERSAL::can($obj->top, 'bottom')) # do this: if (blessed $obj && $obj->can('top') && blessed $obj->top && $obj->top->can('bottom'))

      If you suspect $obj isn't an object, the following is the suggested solution:
      if (eval { $obj->can($top) })

      However, I don't see any problems with using blessed (aside from being wordier).