How do I handle errors in methods chains in Perl?
Asked Answered
A

3

10

What is the best way to deal with exceptions threw in a method chaining in Perl? I want to assign a value of 0 or undef if any of the methods chained throw an exception

Code sample:

my $x = $obj->get_obj->get_other_obj->get_another_obj->do_something;

What the best way to do it? Do I have to wrap in a try/catch/finally statement everytime? The context I want to apply this is: Im working in web development using Catalyst and DBIC and I do a lot of chained resultsets and if some of this resultset throw an exception I just want to assign a value of 0 or undef and then treat this error in the template (Im using Template Toolkit). If there is another way to do that without wrapping every call in try/catch, please let me know. If you know a better way to treat this type of error in the same context (Catalyst/DBIC/TT), please suggest. A practical example would be when the user search for something and this does not exists.

Activist answered 15/8, 2011 at 12:36 Comment(0)
E
9

I handle this by returning a null object at the point of failure. That object responds to every method by simply returning itself, so it keeps doing that until it eats up the remaining methods. At the end, you look in $x to see if it's the result you expected or this null object.

Here's an example of such a thing:

use v5.12;

package Null {
    my $null = bless {}, __PACKAGE__;
    sub DESTROY { 1 }
    sub AUTOLOAD { $null }
    }

For every method called, the AUTOLOAD intercepts it and returns the empty object.

When you run into an error, you return one of these Null objects. In the middle of a method chain you still get an object back so Perl doesn't blow up when you call the next method.

sub get_other_obj {
    ...;
    return Null->new if $error;
    ...;
    }

At the end of the chain, you can check what you got back to see if it's a Null object. If that's what you got, something bad happened.

That's the basic idea. You can improve on the Null class to make it remember a message and where it was created, or add some polymorphic methods (such as sub is_success { 0 }) to make it play nicely with the interfaces of the objects you expected to get.

I thought I had written something long about this somewhere, but now I can't find it.

UPDATE: found some of those writings:

Edelweiss answered 15/8, 2011 at 17:23 Comment(6)
One problem with this: Setter methods will use a null entry to mean return the current value. For example, $foo->Name("David") will set the name to David, and $foo->Name` will return the current name. Thus, a null return from one method might be a valid input to another method.Undesirable
That's not really a problem. It's a no-op. The subsequent methods don't do anything. You aren't passing the null object as an argument; it's the referent. If the previous method isn't returning an object, you can't chain anyway.Edelweiss
Sorry I didnt understand. How could I apply it in a DBIC chaining resultsets?Activist
I know this is old, but what does that look like? This thread ( perlmonks.org/?node_id=899958 ) seems to indicate some dislike among Moose/Mouse folks for returning undef (perlcritic also complains about it by default), specifically note stvn's comments. And, what is a "null object" in Perl? Are you blessing a hashref containing null or is it simply "return undef"?Merilyn
They are talking about an explicit return undef, which has a problem in list context because it's a list of one item. I'm talking about returning a single object and everything else expecting a single object.Edelweiss
This is a really brilliant idea. To make it possible to chain or return undef, there is this great module Want which makes it possible to implement what Brian was suggesting like this: package Null; BEGIN { use strict; use Want; }; sub new { return( bless( {}, __PACKAGE__ ) ); } AUTOLOAD { rreturn( $_[0] ) if( want( 'OBJECT' ) ); return; }; DESTROY {}; It can then be used like this: my $rc = $my_object->get->something->die->finish; die sets and return a Null object and finish returns undef as expected.Loya
K
3

You can write a scalar method that will wrap a method chain in error handling:

my $try = sub {
    @_ > 1 or return bless {ok => $_[0]} => 'Try';

    my ($self, $method) = splice @_, 0, 2;
    my $ret;
    eval {
        $ret = $self->$method(@_);
    1} or return bless {error => $@} => 'Try';
    bless {ok => $ret} => 'Try'
};

{package Try;
    use overload fallback => 1, '""' => sub {$_[0]{ok}};
    sub AUTOLOAD {
        my ($method) = our $AUTOLOAD =~ /([^:]+)$/;
        $_[0]{ok} ? $_[0]{ok}->$try($method, @_[1..$#_]) : $_[0]
    }
    sub DESTROY {}
    sub error {$_[0]{error}}
}

to use it:

{package Obj;
    sub new {bless [0]}
    sub set {$_[0][0] = $_[1]; $_[0]}
    sub add {$_[0][0] += ($_[1] || 1); $_[0]}
    sub show {print "Obj: $_[0][0]\n"}
    sub dies  {die "an error occured"}
}

my $obj = Obj->new;

say "ok 1" if $obj->$try(set => 5)->add->add->show; # prints "Obj 7"
                                                    # and "ok 1"

say "ok 2" if $obj->$try('dies')->add->add->show;   # prints nothing 

say $obj->$try('dies')->add->add->show->error;  # prints "an error occured..."

The first line of the $try method also allows the following syntax:

say "ok 3" if $obj->$try->set(5)->add->add->show;
Krug answered 15/8, 2011 at 17:58 Comment(0)
K
-1

One idea would be to create a class that uses overload to return a false value when an instance object is evaluated in string/number/boolean contexts, but would still allow methods to be called on it. An AUTOLOAD method could always return $self allowing a method chain to propagate the same error.

Keever answered 15/8, 2011 at 17:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.