Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feature] Firstclass exception type representation #17951

Open
leonerd opened this issue Jul 7, 2020 · 15 comments
Open

[feature] Firstclass exception type representation #17951

leonerd opened this issue Jul 7, 2020 · 15 comments

Comments

@leonerd
Copy link
Contributor

leonerd commented Jul 7, 2020

As part of my ongoing thoughts about a core try/catch syntax, I have been thinking more about the plain string exceptions that core perl throws. It continues to feel weird when designing a nice try/catch with typed dispsatch, that all core exceptions are only plain strings.

It would be nice, for the purposes of typed catch, if we could somehow distinguish different types of core-thrown exception, so as not to have to have such ugly string matching as

  try { $maybeobj->do_thing }
  catch ($e) {
    return if $e =~ m/^Can't call method \"do_thing\" on an undefined value /;
  }

With some native type attached to these exceptions we could do other fun things like asking the file/line/package/etc.. of where they were thrown from too.

As usual with these design cases we can often start by looking at CPAN for inspiration, but as usual all of CPAN necessarily comes upon the limitation of what is currently possible in Perl. They all fall short here, because any of the exception type systems on CPAN by necessity have to be implemented as some sort of blessed object, and thus

  ref $@

becomes true; whereas this has always been false for a core-thrown stringy exception type. I feel that if we want to have better
information around exceptions in core perl we need to be careful not to break the vast amount of existing code which is expecting this condition to hold.

(this also posted to perl5-porters@ as https://www.nntp.perl.org/group/perl.perl5.porters/2020/07/msg257948.html)

@leonerd
Copy link
Contributor Author

leonerd commented Jul 7, 2020

As part of the 5.31 series we bumped the bit representation of SV types in order to allocate some more. We went from 15 possible with 14 used, up to 31 possible - so we now have plenty of free ones. I would therefore like to suggest we allocate a new one, for now codenamed EV, to contain a native "error value".

In terms of interface to interact with it, there are a lot of questions. A lot of what follows is now just some random top-of-my-head ideas; I'm quite sure they won't be the final form of whatever idea this turns into, but serves just as a starting point for further discussion.

  • Does it act like an object, with accessor methods?
    try { ... }
    catch ($e) {
      return if $e->type eq "invocant::undef";
      warn sprintf "Caught an exception of type %s in file %s line %d.\n",
        $e->type, $e->file, $e->line;
      warn $e->message;
    }

Any code not aware that these are objects with some methods, and instead just sees them as plain unblessed strings in the "traditional perl" sense of them, would continue to get plain message strings out of them.

    unless( eval { ...; 1 } ) {
      warn "Oopsie, it failed $@";   # normal stringification
    }
  • If it does so, there is much scope for one day trying to define some more specific fields to store more information about certain kinds of exception:
    try { ... }
    catch ($e) {
      return if $e->type eq "invocant::undef" and $e->methodname eq "do_thing";
      ...
    }
  • What do ref or reftype make of these new things? For the best combination of back-compat + usefulness I can first think that
      ref($e) eq undef;       # looks like a string
      reftype($e) eq "ERROR"; # but reftype knows different
  • What does the newly-added isa operator think of them?

As they are not blessed refs into some package, I would suggest they can never be isa anything.

  • What does my proposed catch TYPE syntax think of them?

I would suggest a third kind of catch syntax that can see them:

    try { ... }
    catch ($e errtype invocant::undef) {
      return if $e->methodname eq "do_thing";
      ...
    }
  • How can modules throw these things?

Currently most code that wants to throw a plain string exception does so either with die or croak. There's relatively little use of a toplevel function called throw, even around existing exception code, so it suggests we could add a new throw which can create one of these, perhaps annotated with other information.

    sub f {
      throw "It is not Tuesday today", type => "runtime::not_tuesday;
    }

@khwilliamson
Copy link
Contributor

khwilliamson commented Jul 7, 2020 via email

@yuki-kimoto
Copy link

As part of my ongoing thoughts about a core try/catch syntax, I have been thinking more about the plain string exceptions that core perl throws. It continues to feel weird when designing a nice try/catch with typed dispsatch, that all core exceptions are only plain strings.

Why not avoid using words like weird?

Some people feel good enough about eval {}, and I am one of them.

@tonycoz
Copy link
Contributor

tonycoz commented Jul 9, 2020

Instead of creating a new thing that behaves like an object, why not just use an object?

To deal with legacy code that expects string exceptions for perl thrown exceptions maybe we could add a new feature, say "stringify_exceptions" that like the indirect feature defaults on. When the following holds:

  • an exception is caught in code with the feature on and
  • an object is thrown and
  • that object has some specific method, say "__exception_string",

Perl calls that method for the value for $@, otherwise $@ is populated with the object.

If exceptions are rethrown from code with the feature enabled they'll lose their object-ness, but this allows us (and downstream) to at least incrementally update code to support object exceptions for perl generated exceptions.

Using a new method instead of just overloading "" means that existing code that throws object exceptions won't suddenly see their exceptions stringified.

The perl exception objects would likely also overload "".

Another option would be to just start throwing objects (that overload "") and let downstream deal with it.

@FGasper
Copy link
Contributor

FGasper commented Dec 17, 2020

This would be hugely useful; right now, for example, when a module fails to load we have to parse the string to determine if the failure is because of nonexistence of the module or invalidity of the parsed code. It would be much safer for us to have typed errors instead.

What is the advantage to designating a new SV type (EV) versus harnessing Perl’s existing object mechanism? I note that, while CPAN contains an “Exception.pm” module, it’s just a placeholder; maybe core could take that over and do something good with it?

that object has some specific method, say "__exception_string",

For consistency with the existing PROPAGATE method, perhaps STRINGIFY instead?

@dur-randir
Copy link
Member

dur-randir commented Dec 17, 2020

I would therefore like to suggest we allocate a new one, for now codenamed EV, to contain a native "error value".

Currently different SV types mean having a different memory layout. What are the requirements for different internal representation of your proposal, why can't it be satisfied with an existing SV type?

@jackdeguest
Copy link

Actually I have implemented a full try-catch block in a module on CPAN: Nice::Try using perl filters. It enables embedded try-catch blocks, return, variable assignments, no need for semicolon on the last brace and more. So you can do something like:

use Nice::Try;
try
{
    # do something
    die( "Argh....\n" );
}
catch( $wow )
{
    return( $self->error( "Caught an error: $wow" ) );
}
# optionally
finally
{
    # do some cleanup
}

Full disclosure: I have developed this module when TryCatch got broken due to the discontinuation of Devel::Declare.

@Grinnz
Copy link
Contributor

Grinnz commented Mar 24, 2021

Actually I have implemented a full try-catch block in a module on CPAN: Nice::Try using perl filters.

That's nice but I am not sure the relevance to this issue which is about exceptions. Also the initially referenced try/catch mechanism is now core and also implements those features.

@jackdeguest
Copy link

jackdeguest commented Mar 24, 2021

I brought this, because I thought it would be helpful in response to the earlier comment:

"As usual with these design cases we can often start by looking at CPAN for inspiration, but as usual all of CPAN necessarily comes upon the limitation of what is currently possible in Perl"

Nice::Try tries to implement closely the try-catch block as seen in other languages and this includes catching object exceptions like: die( Exception->new( "Oh my" ) );

Excellent news this is now handled at the core!

@FGasper
Copy link
Contributor

FGasper commented Mar 24, 2021

Excellent news this is now handled at the core!

Note that core's implementation lacks finally.

@leonerd
Copy link
Contributor Author

leonerd commented Mar 24, 2021

Fear not; that's what defer statements are for.

Currently CPAN expermental; a little too late to have got into 5.34, but hopefully 5.36.

https://metacpan.org/pod/Syntax::Keyword::Defer

@FGasper
Copy link
Contributor

FGasper commented Mar 24, 2021

@leonerd defer will no doubt be useful, but I still suspect many will miss finally and desire its inclusion as well. But we’ll see. Certainly the major win is already achieved. :)

@jackdeguest
Copy link

It is not clear from the announcement of addition to perl's core that the try-catch block can handle multiple catch blocks based on exception class. For example:

try
{
    # something bad
    die( MyException->new( "Argh..." ) );
}
catch( MyException $e )
{
    return( $self->error( "Caught a MyException: $e" ) );
}
catch( $e )
{
    return( $self->error( "Caught something else" ) );
}

@FGasper
Copy link
Contributor

FGasper commented Mar 24, 2021

@jackdeguest I think core try/catch isn’t class-aware yet.

@leonerd
Copy link
Contributor Author

leonerd commented Mar 24, 2021

@jackdeguest I think core try/catch isn’t class-aware yet.

Indeed. Given the time constraints to get something into 5.33 before the feature freeze ahead of 5.34's release, I opted to go with a minimum-viable product of just the single, untyped catch block, rather than risk getting a larger design halfway to completion and then have to stop with nothing to show for it.

There is definitely space to expand on that design once we open for feature development again in 5.35 - I definitely hope to bring more of the features from Syntax::Keyword::Try into core - such as the typed catch.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

9 participants