RFC: syntax: Implement for/while-then & labeled, multi-level break#60367
RFC: syntax: Implement for/while-then & labeled, multi-level break#60367
Conversation
|
I sent this to keno but I think it's a useful reference for anyone discussing this https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2025/p3568r0.html. This is the C++ proposal and it takes a look at what other languages do and what makes sense or not. For my personal opinion I think that labels should be required for this feature, this maybe means we should introduce proper label syntax in Julia (which I'm not against). But forcing the use of a label means that code that was written like for cond1
for cond2
...
break break
end
endif over time the two loop statements get moved away from each other and then a new loop nesting is added, it continues to be clear and avoids the bug of jumping to the wrong place. Also by always needing labels you can naturally extend to being able to break out of any kind of block, which may remove the need for a then. |
This implements multi-level and labeled break as contemplated in #5334. In addition this adds support for break-with-value (#22891) as well as for-then (aka for-else #1289). Also while-then of course. All three features are syntax gated to 1.14 syntax. The syntax for multi-level break is as follows: ``` for i = 1:10 for j = 1:10 break break (i, j) end end # Loop evaluate to `(1,1) ``` The break value can be `continue` in which case the next innermost loop is continued: ``` julia> for i = 1:3 for j = 1:3 i > 1 && j == 2 && break continue @show (i,j) end @show i end (i, j) = (1, 1) (i, j) = (1, 2) (i, j) = (1, 3) i = 1 (i, j) = (2, 1) (i, j) = (3, 1) ``` For more deeply nested loops, the loop can be annotated with a `@label` and the the break or continue can be targeted using `@goto`: ``` julia> @Label outer for a = 1:2 for b = 1:3 for c = 1:4 b > 1 && c == 2 && @goto outer break end @show b end @show a end b = 1 ``` Naturally `continue` is supported here as well. The syntax and semantics for `for-then` are as proposed in the issue: ``` function has5(iter) return for x in iter x == 5 && break true then false end end ``` Any `break` (including multi-level and labeled) skips the corresponding loop's `then` block, which ordinarily would run at loop completion.
88eedc4 to
0df79d3
Compare
|
Yeah, as I said on slack, I think that's a reasonable way to go as long as we have proper syntax for labeled blocks. Rust's syntax of |
This is a redo of #60367 taking into account various feedback on that PR. In particular, it seemed like people strongly disliked the `break break` syntax for two primary reasons: 1. It scales poorly to multiple loops `break break break break` 2. It introduced footguns if extra loops are introduced between the loop and the break. Instead, the consensus seemed to be that labeled break should be the only facility available. Thus, this implements a proper labeled break facility that looks as follows: ``` @Label :name for i = 1:10 for j = 1:10 break :name (i, j) end end # evaluate to `(1,1) ``` The idea is to re-use the `@label` macro for now, but possibly promote this to syntax in a future version if it gains widespread use. Note that parser changes are still required, since the `break` syntax is currently an error. However, compat.jl could provide `@goto break name val`, which parses fine. `continue` is extended with label support as well: ``` @Label :outer while true for i = 1:10 a[i] && continue :outer end [...] end ``` However, the feature is not restricted to loops. A particular use case is to replace cleanup blocks written like ``` cond1 && @goto error cond2 && @goto error return true @Label error error("foo") ``` by a labeled begin/end block: ``` @Label :error begin cond1 && break :error cond2 && break :error return true end error("foo") ``` This doesn't save much typing work, but it makes this kind of pattern much more structured, e.g. for code-folding in IDEs. A similar pattern replaces the `for-then` construction originally proposed: ``` result = @Label :result begin for x in arr pred(x) && break :result x end default end ``` For convenience, the label may be ommitted, in which case it defaults to `_`, i.e. the above can be written as: ``` result = @Label begin for x in arr pred(x) && break _ x end default end ``` I've taken the liberty of converting some base code for testing and to give an idea of what the syntax looks like in practice, but didn't go through particularly comprehensively. These changes should be considered extended usage examples. Largely written by Claude, and I haven't looked at the implementation particularly carefully yet - for now I'm not interest in dicussion of the syntax.
|
Superseded by #60481 |
This is a redo of #60367 taking into account various feedback on that PR. In particular, it seemed like people strongly disliked the `break break` syntax for two primary reasons: 1. It scales poorly to multiple loops `break break break break` 2. It introduced footguns if extra loops are introduced between the loop and the break. Instead, the consensus seemed to be that labeled break should be the only facility available. Thus, this implements a proper labeled break facility that looks as follows: ``` @Label :name for i = 1:10 for j = 1:10 break :name (i, j) end end # evaluate to `(1,1) ``` The idea is to re-use the `@label` macro for now, but possibly promote this to syntax in a future version if it gains widespread use. Note that parser changes are still required, since the `break` syntax is currently an error. However, compat.jl could provide `@goto break name val`, which parses fine. `continue` is extended with label support as well: ``` @Label :outer while true for i = 1:10 a[i] && continue :outer end [...] end ``` However, the feature is not restricted to loops. A particular use case is to replace cleanup blocks written like ``` cond1 && @goto error cond2 && @goto error return true @Label error error("foo") ``` by a labeled begin/end block: ``` @Label :error begin cond1 && break :error cond2 && break :error return true end error("foo") ``` This doesn't save much typing work, but it makes this kind of pattern much more structured, e.g. for code-folding in IDEs. A similar pattern replaces the `for-then` construction originally proposed: ``` result = @Label :result begin for x in arr pred(x) && break :result x end default end ``` For convenience, the label may be ommitted, in which case it defaults to `_`, i.e. the above can be written as: ``` result = @Label begin for x in arr pred(x) && break _ x end default end ``` I've taken the liberty of converting some base code for testing and to give an idea of what the syntax looks like in practice, but didn't go through particularly comprehensively. These changes should be considered extended usage examples. Largely written by Claude, and I haven't looked at the implementation particularly carefully yet - for now I'm not interest in dicussion of the syntax.
This is a redo of #60367 taking into account various feedback on that PR. In particular, it seemed like people strongly disliked the `break break` syntax for two primary reasons: 1. It scales poorly to multiple loops `break break break break` 2. It introduced footguns if extra loops are introduced between the loop and the break. Instead, the consensus seemed to be that labeled break should be the only facility available. Thus, this implements a proper labeled break facility that looks as follows: ``` @Label :name for i = 1:10 for j = 1:10 break :name (i, j) end end # evaluate to `(1,1) ``` The idea is to re-use the `@label` macro for now, but possibly promote this to syntax in a future version if it gains widespread use. Note that parser changes are still required, since the `break` syntax is currently an error. However, compat.jl could provide `@goto break name val`, which parses fine. `continue` is extended with label support as well: ``` @Label :outer while true for i = 1:10 a[i] && continue :outer end [...] end ``` However, the feature is not restricted to loops. A particular use case is to replace cleanup blocks written like ``` cond1 && @goto error cond2 && @goto error return true @Label error error("foo") ``` by a labeled begin/end block: ``` @Label :error begin cond1 && break :error cond2 && break :error return true end error("foo") ``` This doesn't save much typing work, but it makes this kind of pattern much more structured, e.g. for code-folding in IDEs. A similar pattern replaces the `for-then` construction originally proposed: ``` result = @Label :result begin for x in arr pred(x) && break :result x end default end ``` For convenience, the label may be ommitted, in which case it defaults to `_`, i.e. the above can be written as: ``` result = @Label begin for x in arr pred(x) && break _ x end default end ``` I've taken the liberty of converting some base code for testing and to give an idea of what the syntax looks like in practice, but didn't go through particularly comprehensively. These changes should be considered extended usage examples. Largely written by Claude, and I haven't looked at the implementation particularly carefully yet - for now I'm not interest in dicussion of the syntax.
This is a redo of #60367 taking into account various feedback on that PR. In particular, it seemed like people strongly disliked the `break break` syntax for two primary reasons: 1. It scales poorly to multiple loops `break break break break` 2. It introduced footguns if extra loops are introduced between the loop and the break. Instead, the consensus seemed to be that labeled break should be the only facility available. Thus, this implements a proper labeled break facility that looks as follows: ``` @Label :name for i = 1:10 for j = 1:10 break :name (i, j) end end # evaluate to `(1,1) ``` The idea is to re-use the `@label` macro for now, but possibly promote this to syntax in a future version if it gains widespread use. Note that parser changes are still required, since the `break` syntax is currently an error. However, compat.jl could provide `@goto break name val`, which parses fine. `continue` is extended with label support as well: ``` @Label :outer while true for i = 1:10 a[i] && continue :outer end [...] end ``` However, the feature is not restricted to loops. A particular use case is to replace cleanup blocks written like ``` cond1 && @goto error cond2 && @goto error return true @Label error error("foo") ``` by a labeled begin/end block: ``` @Label :error begin cond1 && break :error cond2 && break :error return true end error("foo") ``` This doesn't save much typing work, but it makes this kind of pattern much more structured, e.g. for code-folding in IDEs. A similar pattern replaces the `for-then` construction originally proposed: ``` result = @Label :result begin for x in arr pred(x) && break :result x end default end ``` For convenience, the label may be ommitted, in which case it defaults to `_`, i.e. the above can be written as: ``` result = @Label begin for x in arr pred(x) && break _ x end default end ``` I've taken the liberty of converting some base code for testing and to give an idea of what the syntax looks like in practice, but didn't go through particularly comprehensively. These changes should be considered extended usage examples. Largely written by Claude, and I haven't looked at the implementation particularly carefully yet - for now I'm not interest in dicussion of the syntax.
This is a redo of #60367 taking into account various feedback on that PR. In particular, it seemed like people strongly disliked the `break break` syntax for two primary reasons: 1. It scales poorly to multiple loops `break break break break` 2. It introduced footguns if extra loops are introduced between the loop and the break. Instead, the consensus seemed to be that labeled break should be the only facility available. Thus, this implements a proper labeled break facility that looks as follows: ``` @Label :name for i = 1:10 for j = 1:10 break :name (i, j) end end # evaluate to `(1,1) ``` The idea is to re-use the `@label` macro for now, but possibly promote this to syntax in a future version if it gains widespread use. Note that parser changes are still required, since the `break` syntax is currently an error. However, compat.jl could provide `@goto break name val`, which parses fine. `continue` is extended with label support as well: ``` @Label :outer while true for i = 1:10 a[i] && continue :outer end [...] end ``` However, the feature is not restricted to loops. A particular use case is to replace cleanup blocks written like ``` cond1 && @goto error cond2 && @goto error return true @Label error error("foo") ``` by a labeled begin/end block: ``` @Label :error begin cond1 && break :error cond2 && break :error return true end error("foo") ``` This doesn't save much typing work, but it makes this kind of pattern much more structured, e.g. for code-folding in IDEs. A similar pattern replaces the `for-then` construction originally proposed: ``` result = @Label :result begin for x in arr pred(x) && break :result x end default end ``` For convenience, the label may be ommitted, in which case it defaults to `_`, i.e. the above can be written as: ``` result = @Label begin for x in arr pred(x) && break _ x end default end ``` I've taken the liberty of converting some base code for testing and to give an idea of what the syntax looks like in practice, but didn't go through particularly comprehensively. These changes should be considered extended usage examples. Largely written by Claude, and I haven't looked at the implementation particularly carefully yet - for now I'm not interest in dicussion of the syntax.
This is a redo of #60367 taking into account various feedback on that PR. In particular, it seemed like people strongly disliked the `break break` syntax for two primary reasons: 1. It scales poorly to multiple loops `break break break break` 2. It introduced footguns if extra loops are introduced between the loop and the break. Instead, the consensus seemed to be that labeled break should be the only facility available. Thus, this implements a proper labeled break facility that looks as follows: ``` @Label :name for i = 1:10 for j = 1:10 break :name (i, j) end end # evaluate to `(1,1) ``` The idea is to re-use the `@label` macro for now, but possibly promote this to syntax in a future version if it gains widespread use. Note that parser changes are still required, since the `break` syntax is currently an error. However, compat.jl could provide `@goto break name val`, which parses fine. `continue` is extended with label support as well: ``` @Label :outer while true for i = 1:10 a[i] && continue :outer end [...] end ``` However, the feature is not restricted to loops. A particular use case is to replace cleanup blocks written like ``` cond1 && @goto error cond2 && @goto error return true @Label error error("foo") ``` by a labeled begin/end block: ``` @Label :error begin cond1 && break :error cond2 && break :error return true end error("foo") ``` This doesn't save much typing work, but it makes this kind of pattern much more structured, e.g. for code-folding in IDEs. A similar pattern replaces the `for-then` construction originally proposed: ``` result = @Label :result begin for x in arr pred(x) && break :result x end default end ``` For anonymous blocks, use `@label _ begin ... end` with `break _`: ``` result = @Label _ begin for x in arr pred(x) && break _ x end default end ``` I've taken the liberty of converting some base code for testing and to give an idea of what the syntax looks like in practice, but didn't go through particularly comprehensively. These changes should be considered extended usage examples. Largely written by Claude, and I haven't looked at the implementation particularly carefully yet - for now I'm just interested in dicussion of the syntax.
This is a redo of #60367 taking into account various feedback on that PR. In particular, it seemed like people strongly disliked the `break break` syntax for two primary reasons: 1. It scales poorly to multiple loops `break break break break` 2. It introduced footguns if extra loops are introduced between the loop and the break. Instead, the consensus seemed to be that labeled break should be the only facility available. Thus, this implements a proper labeled break facility that looks as follows: ``` @Label :name for i = 1:10 for j = 1:10 break :name (i, j) end end # evaluate to `(1,1) ``` The idea is to re-use the `@label` macro for now, but possibly promote this to syntax in a future version if it gains widespread use. Note that parser changes are still required, since the `break` syntax is currently an error. However, compat.jl could provide `@goto break name val`, which parses fine. `continue` is extended with label support as well: ``` @Label :outer while true for i = 1:10 a[i] && continue :outer end [...] end ``` However, the feature is not restricted to loops. A particular use case is to replace cleanup blocks written like ``` cond1 && @goto error cond2 && @goto error return true @Label error error("foo") ``` by a labeled begin/end block: ``` @Label :error begin cond1 && break :error cond2 && break :error return true end error("foo") ``` This doesn't save much typing work, but it makes this kind of pattern much more structured, e.g. for code-folding in IDEs. A similar pattern replaces the `for-then` construction originally proposed: ``` result = @Label :result begin for x in arr pred(x) && break :result x end default end ``` For anonymous blocks, use `@label _ begin ... end` with `break _`: ``` result = @Label _ begin for x in arr pred(x) && break _ x end default end ``` I've taken the liberty of converting some base code for testing and to give an idea of what the syntax looks like in practice, but didn't go through particularly comprehensively. These changes should be considered extended usage examples. Largely written by Claude, and I haven't looked at the implementation particularly carefully yet - for now I'm just interested in dicussion of the syntax.
This is a redo of #60367 taking into account various feedback on that PR. In particular, it seemed like people strongly disliked the `break break` syntax for two primary reasons: 1. It scales poorly to multiple loops `break break break break` 2. It introduced footguns if extra loops are introduced between the loop and the break. Instead, the consensus seemed to be that labeled break should be the only facility available. Thus, this implements a proper labeled break facility that looks as follows: ``` @Label name for i = 1:10 for j = 1:10 break name (i, j) end end # evaluate to `(1,1) ``` The idea is to re-use the `@label` macro for now, but possibly promote this to syntax in a future version if it gains widespread use. Note that parser changes are still required, since the `break` syntax is currently an error. However, compat.jl could provide `@goto break name val`, which parses fine. `continue` is extended with label support as well: ``` @Label outer while true for i = 1:10 a[i] && continue outer end [...] end ``` However, the feature is not restricted to loops. A particular use case is to replace cleanup blocks written like ``` cond1 && @goto error cond2 && @goto error return true @Label error error("foo") ``` by a labeled begin/end block: ``` @Label error begin cond1 && break error cond2 && break error return true end error("foo") ``` This doesn't save much typing work, but it makes this kind of pattern much more structured, e.g. for code-folding in IDEs. A similar pattern replaces the `for-then` construction originally proposed: ``` result = @Label result begin for x in arr pred(x) && break result x end default end ``` For anonymous blocks, use `@label _ begin ... end` with `break _`: ``` result = @Label _ begin for x in arr pred(x) && break _ x end default end ``` I've taken the liberty of converting some base code for testing and to give an idea of what the syntax looks like in practice, but didn't go through particularly comprehensively. These changes should be considered extended usage examples. Largely written by Claude.
This is a redo of #60367 taking into account various feedback on that PR. In particular, it seemed like people strongly disliked the `break break` syntax for two primary reasons: 1. It scales poorly to multiple loops `break break break break` 2. It introduced footguns if extra loops are introduced between the loop and the break. Instead, the consensus seemed to be that labeled break should be the only facility available. Thus, this implements a proper labeled break facility that looks as follows: ``` @Label name for i = 1:10 for j = 1:10 break name (i, j) end end # evaluate to `(1,1) ``` The idea is to re-use the `@label` macro for now, but possibly promote this to syntax in a future version if it gains widespread use. Note that parser changes are still required, since the `break` syntax is currently an error. However, compat.jl could provide `@goto break name val`, which parses fine. `continue` is extended with label support as well: ``` @Label outer while true for i = 1:10 a[i] && continue outer end [...] end ``` However, the feature is not restricted to loops. A particular use case is to replace cleanup blocks written like ``` cond1 && @goto error cond2 && @goto error return true @Label error error("foo") ``` by a labeled begin/end block: ``` @Label error begin cond1 && break error cond2 && break error return true end error("foo") ``` This doesn't save much typing work, but it makes this kind of pattern much more structured, e.g. for code-folding in IDEs. A similar pattern replaces the `for-then` construction originally proposed: ``` result = @Label result begin for x in arr pred(x) && break result x end default end ``` For anonymous blocks, use `@label _ begin ... end` with `break _`: ``` result = @Label _ begin for x in arr pred(x) && break _ x end default end ``` I've taken the liberty of converting some base code for testing and to give an idea of what the syntax looks like in practice, but didn't go through particularly comprehensively. These changes should be considered extended usage examples. Largely written by Claude.
This is a redo of #60367 taking into account various feedback on that PR. In particular, it seemed like people strongly disliked the `break break` syntax for two primary reasons: 1. It scales poorly to multiple loops `break break break break` 2. It introduced footguns if extra loops are introduced between the loop and the break. Instead, the consensus seemed to be that labeled break should be the only facility available. Thus, this implements a proper labeled break facility that looks as follows: ``` @Label name for i = 1:10 for j = 1:10 break name (i, j) end end # evaluate to `(1,1) ``` The idea is to re-use the `@label` macro for now, but possibly promote this to syntax in a future version if it gains widespread use. Note that parser changes are still required, since the `break` syntax is currently an error. However, compat.jl could provide `@goto break name val`, which parses fine. `continue` is extended with label support as well: ``` @Label outer while true for i = 1:10 a[i] && continue outer end [...] end ``` However, the feature is not restricted to loops. A particular use case is to replace cleanup blocks written like ``` cond1 && @goto error cond2 && @goto error return true @Label error error("foo") ``` by a labeled begin/end block: ``` @Label error begin cond1 && break error cond2 && break error return true end error("foo") ``` This doesn't save much typing work, but it makes this kind of pattern much more structured, e.g. for code-folding in IDEs. A similar pattern replaces the `for-then` construction originally proposed: ``` result = @Label result begin for x in arr pred(x) && break result x end default end ``` For anonymous blocks, use `@label _ begin ... end` with `break _`: ``` result = @Label _ begin for x in arr pred(x) && break _ x end default end ``` I've taken the liberty of converting some base code for testing and to give an idea of what the syntax looks like in practice, but didn't go through particularly comprehensively. These changes should be considered extended usage examples. Largely written by Claude.
This is a redo of #60367 taking into account various feedback on that PR. In particular, it seemed like people strongly disliked the `break break` syntax for two primary reasons: 1. It scales poorly to multiple loops `break break break break` 2. It introduced footguns if extra loops are introduced between the loop and the break. Instead, the consensus seemed to be that labeled break should be the only facility available. Thus, this implements a proper labeled break facility that looks as follows: ``` @Label name for i = 1:10 for j = 1:10 break name (i, j) end end # evaluate to `(1,1) ``` The idea is to re-use the `@label` macro for now, but possibly promote this to syntax in a future version if it gains widespread use. Note that parser changes are still required, since the `break` syntax is currently an error. However, compat.jl could provide `@goto break name val`, which parses fine. `continue` is extended with label support as well: ``` @Label outer while true for i = 1:10 a[i] && continue outer end [...] end ``` However, the feature is not restricted to loops. A particular use case is to replace cleanup blocks written like ``` cond1 && @goto error cond2 && @goto error return true @Label error error("foo") ``` by a labeled begin/end block: ``` @Label error begin cond1 && break error cond2 && break error return true end error("foo") ``` This doesn't save much typing work, but it makes this kind of pattern much more structured, e.g. for code-folding in IDEs. A similar pattern replaces the `for-then` construction originally proposed: ``` result = @Label result begin for x in arr pred(x) && break result x end default end ``` For anonymous blocks, use `@label _ begin ... end` with `break _`: ``` result = @Label _ begin for x in arr pred(x) && break _ x end default end ``` I've taken the liberty of converting some base code for testing and to give an idea of what the syntax looks like in practice, but didn't go through particularly comprehensively. These changes should be considered extended usage examples. Largely written by Claude.
This is a redo of #60367 taking into account various feedback on that PR. In particular, it seemed like people strongly disliked the `break break` syntax for two primary reasons: 1. It scales poorly to multiple loops `break break break break` 2. It introduced footguns if extra loops are introduced between the loop and the break. Instead, the consensus seemed to be that labeled break should be the only facility available. Thus, this implements a proper labeled break facility that looks as follows: ``` @Label :name for i = 1:10 for j = 1:10 break :name (i, j) end end # evaluate to `(1,1) ``` The idea is to re-use the `@label` macro for now, but possibly promote this to syntax in a future version if it gains widespread use. Note that parser changes are still required, since the `break` syntax is currently an error. However, compat.jl could provide `@goto break name val`, which parses fine. `continue` is extended with label support as well: ``` @Label :outer while true for i = 1:10 a[i] && continue :outer end [...] end ``` However, the feature is not restricted to loops. A particular use case is to replace cleanup blocks written like ``` cond1 && @goto error cond2 && @goto error return true @Label error error("foo") ``` by a labeled begin/end block: ``` @Label :error begin cond1 && break :error cond2 && break :error return true end error("foo") ``` This doesn't save much typing work, but it makes this kind of pattern much more structured, e.g. for code-folding in IDEs. A similar pattern replaces the `for-then` construction originally proposed: ``` result = @Label :result begin for x in arr pred(x) && break :result x end default end ``` For convenience, the label may be ommitted, in which case it defaults to `_`, i.e. the above can be written as: ``` result = @Label begin for x in arr pred(x) && break _ x end default end ``` I've taken the liberty of converting some base code for testing and to give an idea of what the syntax looks like in practice, but didn't go through particularly comprehensively. These changes should be considered extended usage examples. Largely written by Claude, and I haven't looked at the implementation particularly carefully yet - for now I'm just interested in discussion of the syntax. --------- Co-authored-by: Keno Fischer <Keno@users.noreply.github.com>
This implements multi-level and labeled break as contemplated in #5334. In addition this adds support for break-with-value (#22891) as well as for-then (aka for-else #1289). Also while-then of course. All three features are syntax gated to 1.14 syntax.
The syntax for multi-level break is as follows:
The break value can be
continuein which case the next innermost loop is continued:For more deeply nested loops, the loop can be annotated with a
@labeland the the break or continue can be targeted using@goto:Naturally
continueis supported here as well.The syntax and semantics for
for-thenare as proposed in the issue:Any
break(including multi-level and labeled) skips the corresponding loop'sthenblock, which ordinarily would run at loop completion.I think the
thenkeyword deserves some bikeshedding. I think theelsekeyword is seen as a bit of a mistake in languages that have it. Common lisp usesfinallybut I wanted to avoid it here since the semantics are substantially different fromfinallyin a try-catch block (as mentioned in the original issue. Perl has a similar feature with acontinueblock (but it runs every time). Claude points out that django templates have anemptyclause in for loops that runs if and only if the collection is empty (but doesn't have break in any case).Another keyword options I thought about is
normally. Kind of likefinally, but for normal termination.An additional motivation here is to make multi-level
breakavailable as syntax for the potential future addition ofmatch(#60344).Written by Claude. Some remaining cleanup and todos, but appears to basically be working.