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

add try/catch/else #42211

Merged
merged 5 commits into from
Oct 18, 2021
Merged

add try/catch/else #42211

merged 5 commits into from
Oct 18, 2021

Conversation

simeonschaub
Copy link
Member

@simeonschaub simeonschaub commented Sep 10, 2021

This PR allows the use of else inside try-blocks, which is only taken
if no exception was caught inside try. It is still combinable with
finally as well.

If an error is thrown inside the else block, the current semantics are
that the error does not get caught and is thrown like normal, but the
finally block is still run afterwards. This seemed like the most
sensible option to me.

I am not very confident about the implementation of linearization for
trycatchelse here, so I would appreciate it if @JeffBezanson could
give this a thorough review, so that I don't miss any edge cases.

I thought we had an issue for this already, but I couldn't find
anything.
else might also not be the best keyword here, so maybe we
can come up with something clearer. But it of course has the advantage
that it is already a Julia keyword, so we don't need to add a new one.

Closes #21130

@simeonschaub simeonschaub added parser Language parsing and surface syntax compiler:lowering Syntax lowering (compiler front end, 2nd stage) needs news A NEWS entry is required for this change labels Sep 10, 2021
@DilumAluthge
Copy link
Member

DilumAluthge commented Sep 11, 2021

else might also not be the best keyword here, so maybe we
can come up with something clearer.

Maybe something like nocatch or no catch something like that? Personally, else seems a little confusing to me.

@alyst
Copy link
Contributor

alyst commented Sep 11, 2021

Maybe something like nocatch or no catch something like that? Personally, else seems a little confusing to me.

nothrow?

@simeonschaub
Copy link
Member Author

I didn't really look into what Python does when I wrote it (not sure if there are any other languages as well with this construct), but that might give some useful precedent here. They also called it else and arrived at pretty much the same semantics. One thing to note is that they disallow else without except (catch). I decided not to disallow that, since I could see it still being useful, but of course just try ... else ... end does read a little odd. The names proposed here seem fine as well, but I'd probably be in favor of just following Python's precedence here.

@DilumAluthge
Copy link
Member

I guess when I see try ... else ... end, for example, it's not clear to me when the else actually gets run.

E.g. in if ... else ... end, either the if is run or the else is run, but not both.

But in try ... else ... end, if the try doesn't throw anything, then both the try and else will get run.

I guess I'm very used to thinking of else in terms of if blocks, so I'd just prefer we use something else instead of else.

So I would prefer something like no throw (as suggested above) or no catch, because it makes it explicit to the reader when that block is going to be run.

@simeonschaub simeonschaub added the status:triage This should be discussed on a triage call label Sep 11, 2021
@DilumAluthge
Copy link
Member

DilumAluthge commented Sep 11, 2021

EDIT: scrap this suggestion.

We could do something like if no throw:

try
    ...
if no throw
    ...
end
try
    ...
catch
    ...
if no throw
    ...
end

This makes it more clear (in my opinion) that the block in question is only run if no exception is thrown.

@simeonschaub
Copy link
Member Author

simeonschaub commented Sep 11, 2021

I still like else but could maybe be convinced of nothrow or nocatch. Probably best to triage this.

try
    ...
catch
    ...
if no throw
    ...
end

This seems too close to

try
    ...
catch
    ...
if ...
    ...
end
end

which has a completely different meaning. It also very likely leads to parsing ambiguities or would at least significantly complicate the rules for parsing if statements, since if can now have two different roles inside try blocks.

@DilumAluthge
Copy link
Member

try
    ...
catch
    ...
if no throw
    ...
end

This seems too close to

try
    ...
catch
    ...
if ...
    ...
end
end

which has a completely different meaning. It also very likely leads to parsing ambiguities or would at least significantly complicate the rules for parsing if statements, since if can now have two different roles inside try blocks.

Oh, good point. Let's scrap the if no throw idea.

@DilumAluthge
Copy link
Member

I would also suggest that we disallow try ... else ... end.

@DilumAluthge
Copy link
Member

DilumAluthge commented Sep 11, 2021

If we disallow try ... else ... end, then I think that I'm fine with try ... catch ... else ... end, because the else block always immediately follows the catch block.

@tkf
Copy link
Member

tkf commented Sep 11, 2021

(not sure if there are any other languages as well with this construct)

There's a similar concept in scope(success) of D and std::experimental::scope_success of C++ (aka scope guards).

But success is a valid identifier (and also is a public API function) so I don't think we can use it here.

FWIW I think else is pretty good, but I learned programming in Python.

@vchuravy
Copy link
Sponsor Member

I think else is fine as well. It would be interesting to think about multiple catch statements as well. Using catch e <: ErrorException to only match one kind of exception. For me that would make the usage of else more symmetric to if .... Elseif ...else.

@simeonschaub
Copy link
Member Author

simeonschaub commented Sep 12, 2021

I think else is fine as well. It would be interesting to think about multiple catch statements as well. Using catch e <: ErrorException to only match one kind of exception. For me that would make the usage of else more symmetric to if .... Elseif ...else.

That's definitely an interesting idea. We might want it to work just like function dispatch though, which would again be asymmetric to how elseif works. Dynamic dispatch would likely also have an overhead, but I think that's probably a much larger discussion and out of scope here.

@quinnj
Copy link
Member

quinnj commented Sep 12, 2021

I also like else as the name for this; we had discussed doing while ... else as well at some point, which I find myself wanting from time to time too.

@tkf
Copy link
Member

tkf commented Sep 12, 2021

but I think that's probably a much larger discussion and out of scope here.

Yaeh, I think focusing on else here makes it easier to get merged. Exception handling has other discussions like #7026

BTW, it looks like try-catch-else was discussed before: #21130. According to @StefanKarpinski

There doesn't seem to be any real objection to this feature, someone just has to implement it.

--- #21130 (comment)

@bvdmitri
Copy link
Contributor

What about?

try
    ...
if catch
    ...
else 
    ...
end

Its not a breaking change since try ... catch ... end block will work as usual

@StefanKarpinski
Copy link
Sponsor Member

StefanKarpinski commented Sep 13, 2021

It seems like the minimally controversial option is to allow what Python allows: else clause on try blocks but only after catch clauses. That way the objection about "when does else get run?" is clear: with an else clause exactly one of the catch or else clauses executes (without an else clause at most one of the catch clauses executes). I suspect this is why Python doesn't allow else without a catch clause. Logically, you can reason that the else clause evaluates no exception is thrown, but I think it would generally be clearer to have a catch clause that just rethrows. I don't think that deviating from Python's else keyword here makes sense.

@simeonschaub
Copy link
Member Author

What about?

try
    ...
if catch
    ...
else 
    ...
end

Its not a breaking change since try ... catch ... end block will work as usual

It has the same issues I pointed out in #42211 (comment).

It seems like the minimally controversial option is to allow what Python allows: else clause on try blocks but only after catch

Should we care about the order as well? Python also disallows finally before except, which we currently allow.

@DilumAluthge
Copy link
Member

DilumAluthge commented Sep 13, 2021

Should we care about the order as well? Python also disallows finally before except, which we currently allow.

I would find it easiest to read if we:

  1. Require catch to precede else
  2. If finally is provided, require else to precede finally.

@simeonschaub
Copy link
Member Author

Do you mean else? except in Python is just what we call catch. I am not sure explicitly disallowing certain orders is really worth it though.

@DilumAluthge
Copy link
Member

DilumAluthge commented Sep 13, 2021

Do you mean else? except in Python is just what we call catch. I am not sure explicitly disallowing certain orders is really worth it though.

Ooof. Yes I definitely meant else 😂.

@StefanKarpinski
Copy link
Sponsor Member

The argument for requiring catch to precede else is that it's analogous to if and else. The argument for requiring finally to come last is a bit different—it's because it seems sensible for the code to appear in the order that it's evaluated in. If you put a finally block before other blocks, the code gets evaluated in a non-linear order.

@JeffBezanson
Copy link
Sponsor Member

Triage says 👍 and we like requiring else to come right after catch. The ideal order is try-catch-else-finally, but we currently allow finally in any position so we have to keep that for compatibility (except in between catch and else).

@JeffBezanson JeffBezanson removed the status:triage This should be discussed on a triage call label Sep 16, 2021
This PR allows the use of `else` inside try-blocks, which is only taken
if no exception was caught inside `try`. It is still combinable with
`finally` as well.

If an error is thrown inside the `else` block, the current semantics are
that the error does not get caught and is thrown like normal, but the
`finally` block is still run afterwards. This seemed like the most
sensible option to me.

I am not very confident about the implementation of linearization for
`trycatchelse` here, so I would appreciate it if @JeffBezanson could
give this a thorough review, so that I don't miss any edge cases.

I thought we had an issue for this already, but I couldn't find
anything. `else` might also not be the best keyword here, so maybe we
can come up with something clearer. But it of course has the advantage
that it is already a Julia keyword, so we don't need to add a new one.
src/julia-parser.scm Outdated Show resolved Hide resolved
src/julia-parser.scm Outdated Show resolved Hide resolved
@simeonschaub simeonschaub added status:merge me PR is reviewed. Merge when all tests are passing and removed needs news A NEWS entry is required for this change labels Oct 18, 2021
@simeonschaub simeonschaub merged commit 0357b2a into master Oct 18, 2021
@simeonschaub simeonschaub deleted the sds/try_else branch October 18, 2021 13:35
@DilumAluthge DilumAluthge removed the status:merge me PR is reviewed. Merge when all tests are passing label Oct 19, 2021
@mauro3
Copy link
Contributor

mauro3 commented Oct 21, 2021

Needs docs, or are they elsewhere already?

@fredrikekre fredrikekre added needs compat annotation Add !!! compat "Julia x.y" to the docstring needs docs Documentation for this change is required labels Oct 21, 2021
LilithHafner pushed a commit to LilithHafner/julia that referenced this pull request Feb 22, 2022
This PR allows the use of `else` inside try-blocks, which is only taken
if no exception was caught inside `try`. It is still combinable with
`finally` as well.

If an error is thrown inside the `else` block, the current semantics are
that the error does not get caught and is thrown like normal, but the
`finally` block is still run afterwards. This seemed like the most
sensible option to me.

I am not very confident about the implementation of linearization for
`trycatchelse` here, so I would appreciate it if @JeffBezanson could
give this a thorough review, so that I don't miss any edge cases.

I thought we had an issue for this already, but I couldn't find
anything. `else` might also not be the best keyword here, so maybe we
can come up with something clearer. But it of course has the advantage
that it is already a Julia keyword, so we don't need to add a new one.

Co-authored-by: Jeff Bezanson <[email protected]>
@stanvanlier
Copy link

stanvanlier commented Mar 4, 2022

I just noticed this change in the release notes of v1.8. Since it is not stable yet I hope its OK if I propose a different syntax. I really think the control flow and the scopes will be unintuitive with the usage of else.

For example, when you have some code that should only run when the try is successful, a simple solution is to write it like:

try
    x = can_go_wrong()
    use(x)      # while this actually should never go wrong
catch e
    handle_error(e)         # (x is not defined here)
end

Writing this the new way, the new block should have the same scope as try, and if this block is after catch there is a block in between which does not have that scope.

I propose to use the word then which reads natural when it is put after try

try
    x = can_go_wrong()
then
    use(x)         # same scope as try
catch e
    handle_error(e)         # (x is not defined here)
end

as opposed to

try
    x = can_go_wrong()
catch e
    handle_error(e)         # (x is not defined here)
else
    use(x)         # same scope as try
end

And I argue that it is easier to read:

'normal flow' -> 'normal flow' -> 'error case' -> 'always'

instead of having to jump when reading:

'normal flow' -> 'error case' -> 'normal flow' -> 'always'

It also reads natural to use then without catch:

try
    x = can_go_wrong()
then
    use(x)
end

Note that the new block (also when using else) does actually not correspond to catch but to try. To name it as if it means 'no catch' is messy. Also there is no reason why is should be disallowed without the use of a catch block other then that it reads weird.

How Python did it is I think an example of confusing semantics:

try:
    raise ZeroDivisionError()
except ValueError as e:
    print("This block is not run because it does not match the exception")
else:
    print("This is also not run because try did not succeed")

You could say that this is less of a problem in Julia since there is always just one 'catch-all' block, so if that block did not run it always means that the try did succeed. But it will get the same ugliness if multiple typed catch blocks will ever be added (as discussed in #7026).

@simeonschaub
Copy link
Member Author

I believe this is largely subjective and I don't feel like one is significantly more intuitive than the other. I think not having to introduce a new keyword and not breaking with Python are more objective arguments for the current syntax.

LilithHafner pushed a commit to LilithHafner/julia that referenced this pull request Mar 8, 2022
This PR allows the use of `else` inside try-blocks, which is only taken
if no exception was caught inside `try`. It is still combinable with
`finally` as well.

If an error is thrown inside the `else` block, the current semantics are
that the error does not get caught and is thrown like normal, but the
`finally` block is still run afterwards. This seemed like the most
sensible option to me.

I am not very confident about the implementation of linearization for
`trycatchelse` here, so I would appreciate it if @JeffBezanson could
give this a thorough review, so that I don't miss any edge cases.

I thought we had an issue for this already, but I couldn't find
anything. `else` might also not be the best keyword here, so maybe we
can come up with something clearer. But it of course has the advantage
that it is already a Julia keyword, so we don't need to add a new one.

Co-authored-by: Jeff Bezanson <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
compiler:lowering Syntax lowering (compiler front end, 2nd stage) needs compat annotation Add !!! compat "Julia x.y" to the docstring needs docs Documentation for this change is required parser Language parsing and surface syntax
Projects
None yet
Development

Successfully merging this pull request may close these issues.

try-catch-else