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

[EXPERIMENT] try/catch control structure #18760

Open
rjbs opened this issue Apr 30, 2021 · 24 comments
Open

[EXPERIMENT] try/catch control structure #18760

rjbs opened this issue Apr 30, 2021 · 24 comments
Labels
experiment ticket tracking an active experiment

Comments

@rjbs
Copy link
Member

rjbs commented Apr 30, 2021

try{}catch{} was first released in perl v5.34.0 as an experimental feature. This issue tracks its progress toward the end of its experimental phase.

@rjbs rjbs added the experiment ticket tracking an active experiment label Apr 30, 2021
@rwp0
Copy link
Contributor

rwp0 commented Apr 30, 2021

try{}catch{} was first released in perl v5.34.0 as an experimental feature. This issue tracks its progress toward the end of its experimental phase.

addressing/identifying the features by name in contents eg. try as in use feature 'try' could be more informative for users/developers to recognize what feature it is in code

@leonerd
Copy link
Contributor

leonerd commented May 1, 2021

The current feature is fairly minimal in its ability. In 5.35 I hope to get around to importing more abilities from Syntax::Keyword::Try.

  • Typed dispatch - catch($e isa Some::X) and catch($e =~ m/^A message/)
  • finally {} blocks - although perhaps those would be covered instead by defer {}

@choroba
Copy link
Contributor

choroba commented May 25, 2021

Also note that the feature is missing in documentation of experimental, although it is supported by it.

@Leont
Copy link
Contributor

Leont commented May 25, 2021

Also note that the feature is missing in documentation of experimental, although it is supported by it.

Good point, I guess I should double-check the rest too.

@boftx
Copy link

boftx commented May 25, 2021

I hope this is the right place to offer a comment on the feature.

I have concerns that the new feature is using a syntax/style that breaks from the existing pattern for "eval" and Try::Tiny (and probably other as well) that allows "return" to only leave the block. I think the new practice of having it exit the surrounding method will be confusing given how well entrenched the existing behavior is. The new behavior requires using what I think has long been considered a bad practice, that of simply "falling off the bottom" in order to return a value. Granted, that is a common practice in some case, especially in Moo(se) "has" clauses for clarity, but is discouraged overall.

Also, I am curious why it was decided to base this on a module that is only used by 37 packages in CPAN (according to the reverse dependencies) as opposed to Try::Tiny which has over 1300 listed.

On a touchier note, I have the impression that the change in behavior is being made to make Perl look/feel more like other languages instead of adapting a long accepted solution to the underlying problem solved by Try::Tiny" and similar modules.

Thank you for your consideration.

@Grinnz
Copy link
Contributor

Grinnz commented May 25, 2021

For your consideration, Try::Tiny only was written not to do these things because it's not possible to do them in pure-perl; and it only has as many dependencies as it does because it was the only reasonable low-magic solution for a long time (and still is if you want to support Perl 5.10). These historical facts skew the usage in CPAN codebases, and of course should not be ignored but also should not hold hostage a feature to be less consistent with the rest of the language and user expectations (for example, if and foreach blocks which appear conceptually similar).

@boftx
Copy link

boftx commented May 25, 2021

... should not hold hostage a feature to be less consistent with the rest of the language and user expectations (for example, if and foreach blocks which appear conceptually similar).

I view try/catch to be a more exact form of "eval" and so would think that the behavior of Try::Tiny is more correct. I also noticed in the original Syntax::Feature::Try that if the context variable is not supplied then the exception was available in $@, which makes me wonder if that module (and the new feature) still have the same problem that Try::Tiny resolved for the most part.

@Grinnz
Copy link
Contributor

Grinnz commented May 25, 2021

I view try/catch to be a more exact form of "eval" and so would think that the behavior of Try::Tiny is more correct.

As I said, Try::Tiny only had this behavior because there is no other option in pure-perl. It is not a form of "eval" syntax-wise because "eval" is an expression, not a statement (though it obviously shares some semantics).

SKT's usage of $@ does not cause the problems Try::Tiny is guarding against, because exceptions are indicated by running the "catch" block - Try::Tiny would frankly probably work fine using $@ itself, but that can't be changed now. $@ is more appropriate to use for a core feature in this case, though ideally most usage will assign it to a lexical variable within the "catch" block and not touch any global variables.

@boftx
Copy link

boftx commented May 25, 2021

As I said, Try::Tiny only had this behavior because there is no other option in pure-perl.

FYI, I was informed by SSCAFFIDI that he wrote Try::Harder to specifically explore if Syntax::Feature::Try could be done in pure Perl and he says it was surprisingly easy to do so. So I would think that Try::Tiny, and others, kept the native behavior of "eval" because that was what was expected.

@Grinnz
Copy link
Contributor

Grinnz commented May 25, 2021

As I said, Try::Tiny only had this behavior because there is no other option in pure-perl.

FYI, I was informed by SSCAFFIDI that he wrote Try::Harder to specifically explore if Syntax::Feature::Try could be done in pure Perl and he says it was surprisingly easy to do so. So I would think that Try::Tiny, and others, kept the native behavior of "eval" because that was what was expected.

Try::Harder is a source filter, thus while easy to do, is not suitable for production code.

@hercynium
Copy link

hercynium commented May 25, 2021

Just to be clear - I didn’t say it was easy, just that it worked surprisingly well. In reality it was devilishly hard to get everything working just right: I actually copied the test suite from Syntax::Keyword::Try at the time and hacked on it until it passed everything but one test. IIRC, that was the “return a value” semantics, which I don’t care much for anyway. ☺

oh, and I agree my source-filter approach is way, way less than ideal, but it really was just an experiment that turned out to work well enough I think it can still be useful to provide some sort of bridge for older versions of perl.

@boftx
Copy link

boftx commented May 25, 2021

Just to be clear - I didn’t say it was easy, just that it worked surprisingly well. In reality it was devilishly hard ...

Thanks for the clarification! I should have just quoted you directly on that. :)

@nrdvana
Copy link

nrdvana commented Sep 21, 2021

I happened to have this little brainstorm on a Perlmonks post, and it got some upvotes, so I'm sharing it here.

This is the try/catch (or rather, catch) that I wish we had in Perl:

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 (ref && ref->can("info")) { ... }

  catch my $e { say $e }
  catch my $e ($e isa 'Pkg') { ... }
  catch my $e (ref($e) && ref($e)->can("info")) { ... }

  my $val= do { might_fail(); catch { 42 } };
  $val= do { might_fail(); catch { undef } } // 42;
  $val= do { might_fail(); catch } // 42;

  catch (die) { ... )  # exception within exception-test generates a warning and counts as 'false'
}

My idea of how that would play out in C-land is that as soon as the parser saw a catch, it would immediately wrap the current block's optree-in-progress with a try-context, ... and then continue as normal, attaching each consecutive catch block as a handler of that try context. I also think it should localize $_ for both the catch logical test and the catch body so that users can take advantage of all the implied "topic" operations on the exception. A lexical could be offered with the same syntax as for my $x. I think that the parentheses should be optional for the two most common cases of a isa-test and a regex. A catch could of course have no test at all and just run a block. And as a final special case, a catch could have no test and no block; and if it was the last statement it would have the effect of returning an empty list.

I'd also like it if the error string did not have the "at FILE line X" attached to it when caught, but I'm guessing that's impossible since a __DIE__ handler would be called first and it would expect to see that. I just thought I'd add it to the wishlist in case anyone thought of a way to make it happen.

@nrdvana
Copy link

nrdvana commented Sep 21, 2021

Oh, and it neatly sidesteps the arguments about compatibility with expectations from Try::Tiny because

Do or do not. There is no try

^ that line needs to be in perldoc, right? That should seal the argument.

@Grinnz
Copy link
Contributor

Grinnz commented Sep 21, 2021

This would be more difficult to implement in the parser, differs from the expected control structure other languages have, and has no benefits that I can think of and more complicated semantics. The ability for typed dispatch of catch is planned for the future.

@nrdvana
Copy link

nrdvana commented Sep 22, 2021

No benefits?

  • Saves one level of indentation
  • do { ... catch } is nearly as convenient as Try::Tiny's rvalue semantics
  • Arbitrary expressions in the catch condition allow duck-typing and Type::Tiny
  • Syntax construct from foreach is more familiar to perl than catch($e) from javascript

Edit: I guess I didn't make the case for the value of having a complex conditional. If you catch and re-throw an exception, it triggers the __DIE__ handler a second time and may lose stack trace information, or just bombard the log output with useless implementation details. If you can avoid catching it in the first place, the exception reporting will be more accurate and reliable.

@Grinnz
Copy link
Contributor

Grinnz commented Sep 22, 2021

That ability has nothing to do with your proposed syntax and is planned anyway. Thus the only benefit is saving a level of indentation, which does not outweigh the added implementation and semantic complexity.

@nrdvana
Copy link

nrdvana commented Sep 22, 2021

It's unclear to me how the current syntax of Syntax::Keyword::Try would both allow for the declaration of the lexical variable name and also allow free-form boolean expressions. It appears that catch ($var isa Class) and catch ($var =~ /pattern/) are both special parsed syntaxes. In a general expression, would it just assume that the first undeclared variable reference was the one the user wanted to create?

My proposal above could be thought of as 6 separate patches, the first of which is probably easy and unobtrusive, and the rest which would be unlikely unless a lot of people just really liked it.

  • Make try optional
  • Declare the catch variable with my instead of ($e), using $_ by default
  • Use the parens for an arbitrary expression
  • Two special case syntaxes for ClassName and /pattern/ that skip the parenthesees
  • Store the original exception string separate from $@ so the user can have the original in $_ or $e
  • Allow catch without a block, as a way to just throw away the error

If I wrote a patch for the first and it turned out to be simple and didn't break any unit tests, would there be interest in including that?

@haarg
Copy link
Contributor

haarg commented Sep 22, 2021

Making try optional definitely not acceptable.

@Grinnz
Copy link
Contributor

Grinnz commented Sep 22, 2021

I apologize for my previous curt responses but this feature is already being trialed and is not in need of total redesigns right now without a clear benefit. And as I said, most of the other features you propose are not intrinsically related and are already planned in the scope of the current design, so tying them together is unnecessary.

@nrdvana
Copy link

nrdvana commented Sep 22, 2021

I assume the purpose of the trial (and this ticket) is to get community feedback, right? So my raw feedback was that I really like being able to return from the middle of a try block, but was dismayed that the convenient $x= try { migh_fail() }; has expanded into the awkward $x= do { try { might_fail() } catch ($e) {} }. I read a bit on it and understand there are technical reasons why it needs to be a block and not a statement, and then was trying to think of constructive ideas on how to reduce boilerplate. An optional try seemed like a novel perl-ish reduction. I'm maybe surprised at the negative reaction, but not offended. I recognize it's really late into the decision process.

@shadowcat-mst
Copy link
Contributor

@nrdvana @boftx we've been thrashing this out on IRC and cpan for-fscking-ever and basically - I totally appreciate where both of you are coming from in theory, but in practice it just ain't going to work. Using the do block trick is way more consistent, way more sensible, and also if either of you want to look into the compiler code you'd need to do things otherwise, I'd recommend laying it at least a full handle of bourbon beforehand.

Also, for the record, I've been writing code using Feature::Compat::Try for a bit now and while it took me a minute to adapt to needing the do block, I've actually really enjoyed the consistency code wise (and remember I was around on #moose when Try::Tiny was invented, I'm not unfamiliar with this stuff).

Fundamentally, "when mst says this is a nice idea in theory but he spent weeks trying to figure a way to do it but concluded that even for him that would be way too much crack in practice, that's really quite a lot of crack" applies here, and while that's kind of an argument from authority (argument from insanity?) I hope that's sufficient on top of the more directly technical arguments delivered already to make you both comfortable that this is the right way to go.

Much love (but only because my liquor cabinet is currently well stocked), mst :D

@leonerd
Copy link
Contributor

leonerd commented Jun 28, 2022

Another year, another release. Perl v5.36 retains the experimental status of this feature.

Newly added is the ability to do try ... catch ... finally { ... } blocks.

Still missing is

  • Typed dispatch (by comparing maybe object classes or regexp matches)
  • Consideration of whether core perl exceptions ought to become objects in the catch variable, and thus have more typing information or other abilities

@leonerd
Copy link
Contributor

leonerd commented Apr 5, 2024

We've now considered that basic try/catch syntax can be made non-experimental. Things like typed or conditional catch can be added as a separate feature and/or experiment. Any changes to how exceptions are handled should probably also apply to the $@ variable via eval, so again should be its own feature/experiment.

Since each keyword is implemented individually, we can leave the warning on the optional finally blocks, because those have questions around double-exception control flow for the same reason that defer remains experimental.

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

No branches or pull requests