Beefy Boxes and Bandwidth Generously Provided by pair Networks
more useful options
 
PerlMonks  

conditional catch-blocks 'try {} catch(COND) { }'

by LanX (Saint)
on Sep 19, 2021 at 20:30 UTC ( [id://11136862]=perlquestion: print w/replies, xml ) Need Help??

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

Hi

this is part of a longer meditation, but I want to keep it short:

I want to be able to catch error-objects thrown with die and I want to have an easy syntax:

the JS model is reproducible 1-to-1 with Try::Tiny it's even easier because Perl's $_ is automatically set to JS's e

from try...catch#conditional_catch-blocks

try { myroutine(); // may throw three types of exceptions } catch (e) { if (e instanceof TypeError) { // statements to handle TypeError exceptions } else if (e instanceof RangeError) { // statements to handle RangeError exceptions } else if (e instanceof EvalError) { // statements to handle EvalError exceptions } else { // statements to handle any unspecified exceptions logMyErrors(e); // pass exception object to error handler } }

But in most cases the final else will just do die $_ to propagate the error to higher call levels, hence it's boilerplate.

Python has a model for this by providing a condition after the 'catch', if non is matched the error is raised again.

from https://pythonbasics.org/try-except/

try: # your code here except FileNotFoundError: # handle exception except IsADirectoryError: # handle exception except: # * # all other types of exceptions print('Should reach here')

(* the last except must be removed to automatically raise the error again)

So ideally one could define in Perl a prototype for catch (COND) { CODE } where the (COND) part is optional. Alas that's not possible in Perl, even with mandatory (COND) (otherwise experiments with syntax extension were easy)

Three workarounds come into mind

1. a bail out function only {} (or maybe handle {} )

try { # your code here } catch { only { FileNotFoundError }; # ° # handle exception } catch { only { IsADirectoryError }; # handle exception } catch { # * # all other types of exceptions }; print('Should reach here')

(* again, if the simple catch is removed the error is automatically thrown again die $_ )

( ° the COND is either a boolean expression or a constant resp. object representing an error-class. Since an error-class is blessed into a type like "ErrorClass" this can be tested by only ... hence a shorthand for $_->isa(FileNotFoundError) ... not sure if Python has the same flexibility ;)

2. extend catch (&;&) {...} with optional second sub {}

try { # your code here } catch { FileNotFoundError } sub { # handle exception }, catch { IsADirectoryError } sub { # handle exception }, catch { # * # all other types of exceptions }; print('Should reach here')

UPDATE: added commas, plz see here why.

3. as a variant of 2. use an "underscore" sub _ as syntactic sugar to chain code-blocks

with sub _ (&;@){ return @_ }

try { # your code here } catch { FileNotFoundError }_ { # handle exception } catch { IsADirectoryError }_ { # handle exception } catch { # * # all other types of exceptions }; print('Should reach here')

This variant could help implementing a future built-in syntax catch () {} by parsing {...}_ as (...)

Like that module authors wanting to be backwards compatible to older versions could keep writing {...}_ without loosing the full performance of newer versions.

And new syntax for compound statements could be experimentally implemented and tested.

finally

I wanted to keep it short and didn't show much implementation detail. I'm more interested in comments regarding the interface... or probably I'm missing a good CPAN module already?

comments? suggestions?

Cheers Rolf
(addicted to the Perl Programming Language :)
Wikisyntax for the Monastery

Replies are listed 'Best First'.
Re: conditional catch-blocks 'try {} catch(COND) { }'
by kcott (Archbishop) on Sep 20, 2021 at 07:23 UTC

    G'day Rolf,

    Just throwing a few ideas around.

    "... catch error-objects ..."

    I'll assume, if $e is the error-object, then 'ref $e' returns "TypeError", "RangeError", and so on.

    "... didn't show much implementation ... more interested in comments regarding the interface ..."

    I'll use the Try::Tiny syntax. You can implement something else; e.g. there's some alternatives in the "SEE ALSO" section of that module, there's the experimental "Try Catch Exception Handling" introduced in Perl v5.34, and no doubt many more.

    Probably in some error handling module, you might have something like:

    { my %dispatch_error; BEGIN { %dispatch_error = ( TypeError => sub { ... handle TypeError exceptions ... }, RangeError => sub { ... handle RangeError exceptions ... } +, ..., '' => sub { ... handle any unspecified exceptions ... }, ); } sub handle_error { my ($e) = @_; my $error_type = ref $e; $error_type = '' unless exists $dispatch_error{$error_type}; $dispatch_error{$error_type}->($e); return; } }

    Then your try/catch code could be something like:

    try { myroutine(); } catch { handle_error($_); };

    So now, the interface in the main code becomes very simple. Beyond whatever try/catch syntax you choose, you can do pretty much whatever you want with the implementation (as long as it has a handle_error() routine).

    I would assume your error objects are instantiated from classes with appropriate information; for example, a RangeError class has min and max attributes.

    — Ken

      { my %dispatch_error; BEGIN { %dispatch_error = ( TypeError => sub { ... handle TypeError exceptions ... }, RangeError => sub { ... handle RangeError exceptions ... } +, ..., '' => sub { ... handle any unspecified exceptions ... }, ); } sub handle_error { my ($e) = @_; my $error_type = ref $e; $error_type = '' unless exists $dispatch_error{$error_type}; $dispatch_error{$error_type}->($e); return; } } try { myroutine(); } catch { handle_error($_); };

      One obvious disadvantage is that you need a different handle_error() routine for each different set of exceptions you want to handle. Also, the exception handling code moves away from the catch. And the exception handlers might be outside the scope of the try-catch-block (they aren't, in your example), so they don't have access to local variables.

      How would you solve this?

      # not quite Perl sub work { # Handle a few exceptions my $result1; try { $result1=doSomeMath(); } catch (DivByZeroException) { $result1="div by 0"; } catch (NotANumberException) { $result="not a number"; } # no explicit handler for other exceptions, so re-throw # Handle a different set of exceptions my $result2; try { $result2=doSomeMoreMath(); } catch (DivByZeroException) { $result2="div by 0"; } # no check for NaN, will be re-thrown like any other Exception h +ere # Handle all possible exceptions, with a special handling for one +exception try { writeToFile($result1,$result2); } catch (IOException) { say "oopsie - I/O problem"; } catch { say "something else went wrong"; } }

      Alexander

      --
      Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)
        Something like
        #!/usr/bin/perl use warnings; use strict; use Try::Tiny; sub handle_error { my ($e, $dispatch_error) = @_; my $error_type = ref $e; $error_type = '' unless exists $dispatch_error->{$error_type}; $dispatch_error->{$error_type}->($e); return } for my $case (0, 1, 2) { try { [ sub { die bless {source => 'Int', target => 'Float'}, 'TypeE +rror' }, sub { die bless {value => 2**65, min => -2**32, max => 2**32}, 'RangeError' }, sub { die bless {}, 'Segmentation Fault' } ]->[$case]->() } catch { handle_error($_, { TypeError => sub { warn "Coercing type $_[0]{source} to $_[0]{target}" }, RangeError => sub { warn "$_[0]{value} not between $_[0]{min} and $_[0]{ma +x}" }, '' => sub { die $_[0] }, }); }; print "next\n"; }
        maybe?

        map{substr$_->[0],$_->[1]||0,1}[\*||{},3],[[]],[ref qr-1,-,-1],[{}],[sub{}^*ARGV,3]

        G'day Alexander,

        As per the original question, I focussed on the interface rather than the implementation. I did, however, leave some clues but I didn't expand upon them.

        When I said "I would assume your error objects are instantiated from classes with appropriate information; for example, a RangeError class has min and max attributes.", I was thinking that myroutine() might look something like:

        sub myroutine { ... if ($n < 5 or $n > 10) { die RangeError::->new(min => 5, max => 10, got => $n, ...); + } ... }

        Then a generic $dispatch_error{RangeError} routine would have sufficient information to generate an error message. That might look something like:

        ERROR: Out of bounds. The allowed range is $e->min() to $e->max() (inc +lusive). The number received was $e->got().

        The RangeError class might even have some boilerplate to create such a message using the attribute values of the error object.

        My main intent was to suggest an interface with "an easy syntax" (as requested) and to decouple the implementation. I hadn't really given the implementation details much thought; however, what I've described above, is roughly what had in the back of my mind.

        — Ken

      Thanks Ken,

      I agree with afoken that an uniform global handle_error doesn't solve the need to define an ad-hoc catch condition.

      > there's the experimental "Try Catch Exception Handling" introduced in Perl v5.34

      a great I forgot about this one, have to see if it covers all the cases.

      And it's certainly not backwards compatible. Which is a general problem of most experimental features.

      Having a stable strategy which works with older Perls albeit slowly and nice and fast with newer once would facilitate the development and evalution of new features tremendously.

      > and no doubt many more.

      It would be nice to formulize the requirements covered and to test all those "many more" modules against them.

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery

        >> there's the experimental "Try Catch Exception Handling" introduced in Perl v5.34

        > And it's certainly not backwards compatible. Which is a general problem of most experimental features. Having a stable strategy which works with older Perls albeit slowly and nice and fast with newer once would facilitate the development and evalution of new features tremendously.

        Which is why there is Feature::Compat::Try (FCT), which will choose between the v5.34 native implementation or the Syntax::Keyword::Try (SKT) implementation, which will mean you can write code in such a way that it is backwards compatible down to the v5.14 that SKT supports, using the same syntax on with newer and older versions of Perl. (If I've understood the docs correctly, SKT actually has additional syntax that isn't in the v5.34 native implementation, but the FCT wrapper only exposes the v5.34-compatible syntax. I think.)

        Caveat: I haven't used that pair, because I am often on systems that only has v5.8, so I don't want to get in the habit of using a try/catch pair that's not compatible back that far. But I remembered having seen the FCT/SKT pair mentioned in some other recent thread, so it stuck in my head. So if v5.14 is backward-compatible enough for you, looking at FCT would be a good idea.

        "I agree with afoken that an uniform global handle_error doesn't solve the need to define an ad-hoc catch condition."

        My response may (or may not) alter your view on that. If it's not suitable for your needs, that's fine: as I said, "Just throwing a few ideas around.".

        On the try/catch in 5.34, I generally don't use or suggest experimental features; in fact, I normally only mention them when recommending not to use them. In this instance, you had said, "... new syntax ... experimentally implemented ..."; accordingly, I pointed out a core experimental feature (in the certain knowledge that you're smart enough to understand the pitfalls) — I actually don't know if what you're doing is purely academic or for a production system.

        — Ken

Re: conditional catch-blocks 'try {} catch(COND) { }'
by Fletch (Bishop) on Sep 20, 2021 at 14:10 UTC

    Just kibitzing on your choices, but option one just feels . . . bleh. Can't articulate why but it just doesn't . . . feel right; having the extra filtering sub inside the block . . . meh.

    WRT to the other two, while a bit more verbose I like two more than three (which feels (for lack of a better word) like an overly cute hack (and you're also presuming nothing else has used the name _ for a sub)). It strikes me as much less "hah ha look what games I can play with the parser" and more of a "this is just normal perl with two minor magic keywords" (again, my two cents gut feel when I read).

    The cake is a lie.
    The cake is a lie.
    The cake is a lie.

      I think there are many arguments to be made in favor for using sub _ {} as syntactic sugar, but I forgot one of the most important:

      Chaining sub {} requires commas as separators!

      Please compare

      use strict; use warnings; sub try (&;@) {}; sub catch (&;@){}; sub _ (&;@) {}; sub error_classes { return map { $_ => bless {}, $_ } @_; } use constant { error_classes qw( FileNotFoundError IsADirectoryError ) }; try { # your code here } catch { FileNotFoundError } sub { # handle exception }, # extra comma needed catch { IsADirectoryError } sub { # handle exception }, catch { # all other types of exceptions }; warn ('Should reach here'); try { # your code here } catch { FileNotFoundError }_{ # handle exception } catch { IsADirectoryError }_{ # handle exception } catch { # all other types of exceptions }; warn('Should reach here')

      Hence the OP was wrong and needs to be corrected.

      If anyone sees a way to avoid the trailing commas (or fat commas), please feel free to show ...

      Otherwise, this break in symmetry is just "meh" ...

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery

        Ah, valid point. Still slightly wary of . . . overloading the single _ with syntactic meaning but it does at least look cleaner than the properly comma'd code. Objection weakened at the least.

        The cake is a lie.
        The cake is a lie.
        The cake is a lie.

Re: conditional catch-blocks 'try {} catch(COND) { }'
by NERDVANA (Deacon) on Sep 20, 2021 at 18:59 UTC
    Here’s the try/catch I wish we had:
    sub foo { some_code(); catch { print “ignoring $@”; say “$_ would be the exact string thrown, without ‘at line…’”; } } sub bar { some_code(); catch ClassName::Xyz { … } catch /pattern/ { … } catch ($_ && $_ !~ /io/ && do { … }) { … } }

    pretty sure the only way that could ever happen is with a source filter. Or, if there was a way to intercept the construction of the OP tree and manipulate it…

Re: conditional catch-blocks 'try {} catch(COND) { }'
by Anonymous Monk on Sep 20, 2021 at 07:11 UTC

      > Sounds like you didn't ...

      ... read ...

      > > and I want to have an easy syntax:

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      Wikisyntax for the Monastery

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://11136862]
Approved by Corion
Front-paged by marto
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others studying the Monastery: (6)
As of 2024-04-19 16:25 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found