perl5
perl5 copied to clipboard
[EXPERIMENT] try/catch control structure
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.
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
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)
andcatch($e =~ m/^A message/)
-
finally {}
blocks - although perhaps those would be covered instead bydefer {}
Also note that the feature is missing in documentation of experimental
, although it is supported by it.
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.
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.
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).
... 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.
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.
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.
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.
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.
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. :)
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.
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.
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.
No benefits?
- Saves one level of indentation
-
do { ... catch }
is nearly as convenient asTry::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.
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.
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?
Making try optional definitely not acceptable.
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.
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.
@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
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
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.
We use Try::Tiny a lot at work, and I decided to try and use the try/catch experimental feature for a personal project. I'm a little bit bummed by the fact that a return
statement in the try/catch/finally returns from the function. I think Try::Tiny is now easier to use than the experimental feature because of this behavior.
For example, I want to perform some action in a particular state of the catch
and want to short-circuit the code. With Try::Tiny, you just return; with this feature, you must be explicit with if/else statements in the catch block. We need to write more code to do the exact same thing.
That is intentional, so that it functions like every other syntax construct built into the language like if/else, rather than introducing invisible subroutine scopes. I find the best way to introduce "breakpoints" in an if/else or now a try/catch is to abstract the functionality into a subroutine which can be returned from at any point.
That said, nothing's stopping you from continuing to use Try::Tiny if you prefer its quirks, but I wouldn't expect this behavior to change.
I know I can continue to use Try::Tiny. This feature is an experiment, and I'm giving my feedback on it.