in reply to Re: conditional catch-blocks 'try {} catch(COND) { }'
in thread conditional catch-blocks 'try {} catch(COND) { }'

{ 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". ;-)

Replies are listed 'Best First'.
Re^3: conditional catch-blocks 'try {} catch(COND) { }'
by choroba (Cardinal) on Sep 20, 2021 at 19:50 UTC
    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]

      A little bit more code and indenting than expected, but yes, that should work.

      Try::Tiny implements catch as a prototyped sub (sub catch (&;@)), so it should be possible to implement something like sub catchif ($&;@) that accepts an additional parameter, and to extend the logic in sub try to basically implement your handle_error() function.

      That would allow the following syntax that at least looks similar to try-catch in other languages:

      try { # ... } catchif TypeError => { # ... } catchif RangeError => { # ... } catch { # ... } finally { # ... }

      I think it would not need many changes:

      • sub catchif is very similar to sub catch, it "just" needs to also embed its first argument in the returned object
      • try needs to build a hash of catchif cases, extracting the first argument from catchif objects

      • try has to examin the hash before executing the generic catch case.

      Alexander

      --
      Today I will gladly share my knowledge and experience, for there are no sweeter words than "I told you so". ;-)
        > That would allow the following syntax that at least looks similar to try-catch in other languages:
        try { # ... } catchif TypeError => { # ... } catchif RangeError => { # ... } catch { ...

        May I see a POC with prototypes for that?

        Because this

        > sub catchif ($&;@)

        will require a sub before and a comma after the block.

        try { # ... } catchif TypeError => sub { # ... }, catchif RangeError => sub { # ... }, catch { ...

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

      >  handle_error($_, {  TypeError => sub {

      I wanted to suggest

      • dropping the $_ because it's redundant
      • dropping the default case ' => sub { die $_[0] },' because it's redundant too
      • elevating handle_error or rather handle to the same level like catch
      • like this a simple catch could be used if the default isn't wanted.

      But I think Lukas already did this in Try::Tiny::ByClass

      use Try::Tiny::ByClass; try { die $exception_object; } catch_case [ 'Some::Class' => sub { # handle Some::Class exceptions }, 'Exception::DivByZero' => sub { # handle Exception::DivByZero exceptions }, ], finally { # always do this };

      Tho I sense a bit of confusion between finally and a "naked" catch

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

Re^3: conditional catch-blocks 'try {} catch(COND) { }'
by kcott (Archbishop) on Sep 20, 2021 at 14:42 UTC

    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