Beefy Boxes and Bandwidth Generously Provided by pair Networks
Don't ask to ask, just ask
 
PerlMonks  

Idomatic Handling of Subroutine Error

by dvergin (Monsignor)
on Sep 18, 2001 at 22:28 UTC ( #113166=perlquestion: print w/replies, xml ) Need Help??

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

I'm looking for an idiom or pattern.

I have a subroutine. I'd like to call it and handle problems it might encounter. I could implement so that I can call it this way:

($success, $err_msg) = my_sub(); unless ($success) { # handle error condition using $err_msg }
But I would much rather set things up so I can use the more concise:
unless ( my_sub() ) { # handle error condition }
or...
unless ( $result = my_sub() ) { # handle error condition }
Can I co-opt a global special error var to set on error in my sub? I've checked on a couple possibilities but haven't found anything promising. E.g. you can assign a number to $! but apparently only to cause it to point to an existing system error description if $! is then used in string context.

My religion forbids me to use globals of my own creation, so I'd like to avoid that if I can. ;-)

TIA, David

Replies are listed 'Best First'.
Re: Idomatic Handling of Subroutine Error
by dragonchild (Archbishop) on Sep 18, 2001 at 22:40 UTC
    You're looking for some sort of throw/catch error-handling for special subs.
    eval { some_sub() }; if ($@) { # Handle exception here } sub some_sub { die "Bad stuff happened" if $some_exception_happens; }
    That's the way Perl does it.

    Now, for a stylistic note - you shouldn't be doing the eval/$@ syntax for every single function call you make. Keep it for the critical parts of your system. In part, this is because of the overhead of eval. *shrugs*

    Update: I stand corrected on the overhead. Thanks, btrott!

    ------
    We are the carpenters and bricklayers of the Information Age.

    Don't go borrowing trouble. For programmers, this means Worry only about what you need to implement.

      That's not right. eval BLOCK has no overhead (or rather negligible overhead).

      eval EXPR (string eval) *does* have overhead, because the string needs to be compiled at runtime, but that's the issue here.

      In general, actually, I would tend to agree with you, but for a different reason: there is no sense in wrapping *every* function call in its own eval. Instead wrap an entire self-contained block in an eval, like this:

      eval { ... ... ... }; if ($@) { ## Got error }
      You will jump out of the block as soon as your code hits an exception, and you might as well keep your exception-handling in one place, rather than scattered all over the place.

      To the OP: check out Exception::Class and Error, among others.

      I have no doubt that this will work. But I'm looking for a way to hide the ugly details from the calling code.

      Perhaps (looking at your suggestion and making this up as I go...)

      sub my_sub { # do stuff eval { potential icky bit }; if ($@) { return 0; } else { return $something_useful; } } # MAIN unless ( $result = my_sub() ) { # use $@ to figure out what to do }
      Well and good. But what if the problem arises from the logic of my task and not from an eval-able error. Bad data for example.

      I'm willing to hid any ugliness in the sub. But how do I best set a retreavable value of my own chosing to flag the problem and still allow succinct, clean code where the sub is called:

      unless ( $result = my_sub() ) { # handle error condition based on error var }
        You're almost there.
        # MAIN unless ($result = my_sub() ) { # Now you use $result if it's not 0 } # WRAPPER function sub my_sub { eval { _my_sub(@_) }; if ($@) { return $@; } else { return ''; } } # Actual worker... sub _my_sub { # Do your stuff here. If something fails, do a die with a useful error + message. Otherwise, return 0. # For example ... die "Bad data passed in.\n"; }
        You actually do a die which doesn't end your script - it gets trapped by the eval and the string you passed die will be put in $@.

        Now, I personally dislike this type of error-handling because 0 is FALSE and non-zero is TRUE. Thus, you have to flip your thinking the way the C libs force you to and say you're calling the function and hope it "fails" for success. (Sorta like a drug screening ...)

        Instead, I would something similar. Instead of passing back '' for success, I'd pass back undef instead.

        # MAIN if (defined ($error = my_sub()) ) { # Now you use $error if it's defined } # WRAPPER function sub my_sub { eval { _my_sub(@_) }; if ($@) { return $@; } else { return undef; } } # Actual worker... sub _my_sub { # Do your stuff here. If something fails, do a die with a useful error + message. Otherwise, return 0. # For example ... die "Bad data passed in.\n"; }
        It seems like a semantic difference, but now your code use TRUE and FALSE the way they intuitively used, to indicate success and failure.

        ------
        We are the carpenters and bricklayers of the Information Age.

        Don't go borrowing trouble. For programmers, this means Worry only about what you need to implement.

Re: Idomatic Handling of Subroutine Error
by btrott (Parson) on Sep 18, 2001 at 22:58 UTC
    Other posters have mentioned exceptions; that's a good way to go.

    Another option is more similar to the sample code you posted. If you are willing to call your subroutines as object or class methods, you can have your methods return undef on error, and set either an instance variable or a class variable, which the calling code can then access using some other method you set up, like 'errstr'. For example, here is some error-handling code I use:

    use vars qw( $ERROR ); sub new { bless {}, shift } sub error { my $msg = $_[1]; $msg .= "\n" unless $msg =~ /\n$/; if (ref($_[0])) { $_[0]->{_errstr} = $msg; } else { $ERROR = $msg; } return; } sub errstr { ref($_[0]) ? $_[0]->{_errstr} : $ERROR }
    In your methods you can now do:
    return $obj->error("Got an error") unless $foo; ## OR return $class->error("Got an error") unless $foo;
    And in the calling code you can do:
    $obj->foo or die $obj->errstr; ## OR My::Class->foo or die My::Class->errstr;
Re: Idomatic Handling of Subroutine Error
by perrin (Chancellor) on Sep 18, 2001 at 22:34 UTC
    You should be using exceptions. Read up on the block form of eval.
Re: Idomatic Handling of Subroutine Error
by blakem (Monsignor) on Sep 18, 2001 at 22:50 UTC
    Just for fun, take a look at "Falsify" scalar strings. Its a quick hack that overloads certain strings so that they act FALSE in boolean context. For instance, you could have the odd looking construct:
    unless ($error_or_result = my_sub()) { # FALSE in boolean context # but still contains text when stringified die "Your error was: $error_or_result\n"; } print "Your result was $error_or_result\n";
    Not recommended for production code (for one thing maintanence would be a beast) but fun to play with...

    -Blake

(ichimunki) Re: Idomatic Handling of Subroutine Error
by ichimunki (Priest) on Sep 18, 2001 at 23:01 UTC
    You can always test for results and return an undef on failure, which will make both of your last examples work.

    You might want to consider handling as much of the error in the sub itself as possible, that is, your first example may as well do the if-then work inside the sub so that you don't have to write handlers for it every time you call the sub. The main thing you'd want to have in your calling block is logic to protect the rest of the block from a failure in the sub... but the warning should be issued from the place where it occurred if at all possible.
Re: Idomatic Handling of Subroutine Error
by dvergin (Monsignor) on Sep 19, 2001 at 00:25 UTC
    The Exception module available on CPAN looks promising for this sort of thing.
Re: Idomatic Handling of Subroutine Error
by broquaint (Abbot) on Sep 19, 2001 at 14:51 UTC
    Here's a rather funky example taken from the perl docs (man perlsub) that may provide a worthwhile solution -
    sub try (&@) { my($try,$catch) = @_; eval { &$try }; if ($@) { local $_ = $@; &$catch; } } sub catch (&) { $_[0] } try { die "phooey"; } catch { /phooey/ and print "unphooey\n"; };
    This is simliar to a lot of the above posts, in that it eval()s and then handles any errors. Although it's not *exactly* the sub wrapper you were looking for, I think it's a very cool alternative (but then again I'm impressed by all things functional ;o)
    HTH

    broquaint

Re: Idomatic Handling of Subroutine Error
by andye (Curate) on Sep 19, 2001 at 15:21 UTC
    You're after something like this:
    unless ( $result = my_sub() ) { # handle error condition }
    I've often seen this, which is quite similar:
    my $result; unless ( my_sub(\$result) ) { # handle error condition }
    where &my_sub might look like this:
    sub my_sub { my $r_result = shift; $$r_result = whatever(); was_there_an_error() ? 0 : 1 ; }
    andy.

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlquestion [id://113166]
Approved by root
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others taking refuge in the Monastery: (2)
As of 2020-07-03 23:20 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found

    Notices?