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

break-with-value and for/else implementation #23260

Closed
wants to merge 23 commits into from

Conversation

tkluck
Copy link
Contributor

@tkluck tkluck commented Aug 14, 2017

This commit enables a combination of two language features that dovetail nicely together (as remarked by @StefanKarpinski in [1]): for/else and break-with-value, which together allow something like:

name = for person in people
    if person.id == id
        break person.name
    end
else
    generate_name()
end

The parsing patch is pretty straightforward. As for the code lowering part, I opted to make a second version of 'break-block, called 'break-block-with-value, which passes a target variable for the return value onto the break-labels stack. That's where the 'break operation finds it.

An alternative approach would be something more similar to the existing replace-return function: Instead of having an intermediate node representing 'break-block-with-value,we could traverse the expression tree and replace break-with-value by an assignment followed by a break.

I have no opinion either way; the current commit seemed like the obvious implementation to me, but that was before I saw replace-return's prior art.

This patch still has a few places marked TODO, where scoping issues for the else block need to be resolved. I'll tackle that after awaiting feedback. It also doesn't parse while/else yet, only for/else. That would be a trivial addition.

[1] #22891

edit: re-flow paragraphs.

tkluck added 2 commits August 14, 2017 22:26
This commit enables a combination of two language features that dovetail
nicely together (as remarked by @StefanKarpinski in [1]): for/else and
break-with-value, which together allow something like:

    name = for person in people
        if person.id == id
            break person.name
        end
    else
        generate_name()
    end

The parsing patch is pretty straightforward. As for the code lowering
part, I opted to make a second version of `'break-block`, called
`'break-block-with-value`, which passes a target variable for the return
value onto the `break-labels` stack. That's where the `'break` operation
finds it.

An alternative approach would be something more similar to the existing
`replace-return` function: Instead of having an intermediate node
representing `'break-block-with-value`,we could traverse the expression
tree and replace `break-with-value` by an assignment followed by a
break.

I have no opinion either way; the current commit seemed like the obvious
implementation to me, but that was before I saw `replace-return`'s prior
art.

This patch still has a few places marked `TODO`, where scoping issues
for the `else` block need to be resolved. I'll tackle that after
awaiting feedback.

[1] JuliaLang#22891
Because a new language feature should probably be used in
the standard library, to encourage cargo-culting.
@ararslan ararslan added design Design of APIs or of the language itself needs docs Documentation for this change is required needs news A NEWS entry is required for this change needs tests Unit tests are required for this change labels Aug 14, 2017
@ararslan
Copy link
Member

Wow, very nice first contribution! I'm against this change but I want to commend your work here.

@tkluck
Copy link
Contributor Author

tkluck commented Aug 14, 2017

Thanks for your remark @ararslan ! I thought it is a nice language feature but I certainly see how opinions can be different. If it turns out to have just been a little learning project for me, that still makes me happy :)

@JeffBezanson
Copy link
Member

Thanks, @tkluck, this is great work! I haven't reviewed it in detail yet but it seems basically good. I imagine the main issue here will be whether people want this feature :)

@JeffBezanson
Copy link
Member

Noting that this relates to #22659. Interestingly, this feature doesn't provide a way to get the last value of the iteration variable, so that might mean we should add both this and for outer i = ... as orthogonal features.

base/array.jl Outdated
@@ -1251,10 +1251,11 @@ julia> findnext(A,3)
function findnext(A, start::Integer)
for i = start:length(A)
if A[i] != 0
return i
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not related to the feature itself, but I find explicit returns easier to reason about.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are, but sometimes you don't want to factor a single loop into a function just so that you can use a return to get a value out. This basically lets you have that without a function.

Copy link
Contributor

@yuyichao yuyichao Aug 14, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do like the for else feature but isn't v = for ...; break i; else j end always equivalent to for ...; v = i; break; else v = j end (with the correct scope of v)?
The latter seems much easier to understand than the former. It is similar to the return value of if else but in that case at least the return value is easier to find.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with @KristofferC that the findnext example isn't the best use-case for this feature because a return suffices; I just included it for the sake of show-casing the working patch.

@yuyichao , yes that is indeed equivalent, and it's probably a matter of taste which is easier to understand.

IMHO, in a language where there is no distinction between statements and expressions, it is a bit wasteful and arguably unexpected (e.g. when compared to your if/else example) not to be able to specify a value for a loop expression. That's why I personally like this feature.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Afterthought: it's also a bit more 'functional' because it's easier to reason about whether the resulting variable v is mutable or not.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's why I do like the for else and my example above includes the else block.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, I misread that. Got it.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do agree, however, that these cases are clearer with return; but they do serve as an in-situ test of the new syntax, which is useful for a WIP pull request.

Copy link
Member

@rfourquet rfourquet Aug 30, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A nice example is intersect, which can be improved into:

function intersect(s::Set, sets...)
    i = similar(s)
    for x in s
        for t in sets
            !in(x, t) && break false
        else true
        end && push!(i, x)
    end
    return i
end

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great example @rfourquet ! It could also be

for x in s
    for t in sets
        !in(x, t) && break
    else
        push!(i, x)
    end
end

which doesn't use break-with-value, but does use the else feature.

I personally like options with all or with comprehensions best, but that's a matter of taste and may not give the same performance (yet).

@StefanKarpinski
Copy link
Member

I can't build this branch – am I missing something here or is this work in progress?

essentials.jl
ctypes.jl
generator.jl
reflection.jl
options.jl
promotion.jl
tuple.jl
pair.jl
traits.jl
range.jl
expr.jl
error.jl
bool.jl
number.jl
int.jl
operators.jl
pointer.jl
indices.jl
array.jl
error during bootstrap:
ErrorException("syntax: break or continue outside loop")
rec_backtrace at /Users/stefan/projects/julia/src/stackwalk.c:86
record_backtrace at /Users/stefan/projects/julia/src/task.c:246 [inlined]
jl_throw at /Users/stefan/projects/julia/src/task.c:568
jl_errorf at /Users/stefan/projects/julia/src/rtutils.c:77
eval at /Users/stefan/projects/julia/src/interpreter.c:495
jl_interpret_toplevel_expr_in at /Users/stefan/projects/julia/src/interpreter.c:50
jl_parse_eval_all at /Users/stefan/projects/julia/src/ast.c:907
jl_load at /Users/stefan/projects/julia/src/toplevel.c:646 [inlined]
jl_load_ at /Users/stefan/projects/julia/src/toplevel.c:653
unknown function (ip: 0x10b3a1a01)
unknown function (ip: 0x10b3a1951)
do_call at /Users/stefan/projects/julia/src/interpreter.c:70
eval at /Users/stefan/projects/julia/src/interpreter.c:262
jl_interpret_toplevel_expr_in at /Users/stefan/projects/julia/src/interpreter.c:50
jl_toplevel_eval_flex at /Users/stefan/projects/julia/src/toplevel.c:608
jl_eval_module_expr at /Users/stefan/projects/julia/src/toplevel.c:204
jl_toplevel_eval_flex at /Users/stefan/projects/julia/src/toplevel.c:485
jl_toplevel_eval_in at /Users/stefan/projects/julia/src/builtins.c:505
unknown function (ip: 0x10b3a0f21)
do_call at /Users/stefan/projects/julia/src/interpreter.c:70
eval at /Users/stefan/projects/julia/src/interpreter.c:262
jl_interpret_toplevel_expr_in at /Users/stefan/projects/julia/src/interpreter.c:50
jl_toplevel_eval_flex at /Users/stefan/projects/julia/src/toplevel.c:608
jl_parse_eval_all at /Users/stefan/projects/julia/src/ast.c:913
jl_load at /Users/stefan/projects/julia/src/toplevel.c:646
exec_program at /Users/stefan/projects/julia/usr/bin/julia (unknown line)
true_main at /Users/stefan/projects/julia/usr/bin/julia (unknown line)
main at /Users/stefan/projects/julia/usr/bin/julia (unknown line)

make[1]: *** [/Users/stefan/projects/julia/usr/lib/julia/inference.ji] Error 1
make: *** [julia-inference] Error 2

@tkluck
Copy link
Contributor Author

tkluck commented Aug 14, 2017

@StefanKarpinski that's odd; I tried a clean build before pushing. Might still be a leftover build artifact somewhere; I'll look into it tomorrow (am in CEST).

Thanks for trying it out!

@tkluck
Copy link
Contributor Author

tkluck commented Aug 15, 2017

I understand the build issue now; the change to find_next works when run normally, but not when building the sysimg. I also understand the root cause but the proper fix isn't 100% obvious. I'm sure I'll come up with something.

@StefanKarpinski
Copy link
Member

Bootstrapping is tricky. The system is continually building itself and things you're used to being able to do are at various points not defined yet. So using @eval Base ... to develop is a good idea, but then you have to try bootstrapping as well at the end to make sure you didn't use anything too early.

Before 976ff84, a parsed `break` expression `'(break)` was being
augmented to `'(break <label>)` by the function `expand-forms`. Since
the latter is supposed to be idempotent [1], it used to check

    (pair? e)

before deciding on replacing it. In 976ff84 however, the parsed
break can have a value expression, so `(pair? e)` can be true for that
reason as well. Thinking I was being smart, I replaced the check by

    (symbol? (cadr e))

not realizing that a lone symbol can be a valid expression too!

This commit fixes that by *always* compiling the break expression to a
*triple*

    '(break <expression> <label>)

where <expression> may be `'(null)`. This should have no big
implications for the code being generated, because `'(null)` generates
no code when, in the function `compile`, `(and value tail)` is false,
which is likely the typical case.

[1] At least, that's what I'm guessing is the reason.
@tkluck
Copy link
Contributor Author

tkluck commented Aug 15, 2017

Just pushed a fix for the issue I found, and confirmed that make clean && make gives a working julia executable. Hope I didn't overlook anything this time!

I think it's possible to get rid of 'break-block-with-value, instead just adding a (possibly '(null)) else expression to every break-block. That would make it all slightly easier to maintain and I don't think it actually makes a difference for code generation.

As for scoping, I'm tempted to keep the else block in outer scope, as I don't see much value in hiding any of its variables from the outer scope. However, for least surprise, it's probably better to follow the behaviour of the finally block in try/finally, which does introduce a scope:

julia> try finally j=4 end; j
ERROR: UndefVarError: j not defined

So unless anyone objects, that's what I'll implement.

…ck and break-block-with-value

Instead, we give every `break-block` an `else`-block, potentially equal
to `'(null)`. This gives us one less atom to worry about.

This commit also makes a small modification to `compile`, which now
passes the value of `tail` for a `break` onto the `break-labels` stack.
That means a `break` can be compiled to a `return` if the `break-block`
is in the last position of a function.
@StefanKarpinski
Copy link
Member

I get a lot of "invalid AST" now, e.g.:

julia> f(n) = for i = 1:n
           isprime(i) && break i
ERROR: syntax: invalid AST

julia> f(n) = for i = 1:n
           isprime(i) && break(i)
ERROR: syntax: invalid AST

julia> f(n) = for i = 1:n
           isprime(i) && break
ERROR: syntax: invalid AST

julia> for i = 1:n
           isprime(i) && break
ERROR: syntax: invalid AST

julia> for i = 1:n
           isprime(i) && break i
ERROR: syntax: invalid AST

julia>
[ stefan ] ./julia                                                              KARPINSKI-MAC:~/projects/julia
               _
   _       _ _(_)_     |  A fresh approach to technical computing
  (_)     | (_) (_)    |  Documentation: https://docs.julialang.org
   _ _   _| |_  __ _   |  Type "?help" for help.
  | | | | | | |/ _` |  |
  | | |_| | | | (_| |  |  Version 0.7.0-DEV.1331 (2017-08-15 22:22 UTC)
 _/ |\__'_|_|_|\__'_|  |  tkluck-break-with-value/3abd4d9a8d (fork: 4 commits, 5 days)
|__/                   |  x86_64-apple-darwin16.1.0

julia> for i = 1:n
           isprime(i) && break i
ERROR: syntax: invalid AST

julia> for i = 1:n
           isprime(i) && break
ERROR: syntax: invalid AST

julia> 1 + 2
3

julia> for i = 1:10
           println(i)
ERROR: syntax: invalid AST

tkluck added 4 commits August 16, 2017 21:22
The following issue:

    Test Failed
      Expression: Base.incomplete_tag(parse(str, raise=false)) == tag
       Evaluated: none == block

was due to returning a different error message string in case of parsing
the incomplete string "for i=1;". This commit fixes it by delegating
error handling to `expect-end`.
This was triggered by

    Error in testset inference:
    Test Failed
      Expression: all(isleaftype, (ast12474[1])[1].slottypes)

which had, as a root cause, the unused new-mutable-var being of type
Any.

Now, that isn't really what this doctest is trying to avoid, but I'm
guessing it's worthwhile avoiding an unneeded mutable var nonetheless.
This is an artifact from when I wasn't sure whether anything else would
write to the break-labels stack apart from the code point that I had
updated. I'm now confident that doesn't happen, so no need for special
cases.
@tkluck
Copy link
Contributor Author

tkluck commented Aug 16, 2017

@StefanKarpinski ai, thanks for finding that!

I can't reproduce that anymore with what I just pushed. It's possible that this commit fixed it. My hypothesis would be that the REPL relies on the incompleteness error from the parser to decide whether to give a new prompt or wait for more instructions. That would also explain why I didn't see it before, because I was testing with code in a source file.

If that sounds believable to you, would you mind trying the latest update? Thanks for your patience!

@fredrikekre fredrikekre removed the needs docs Documentation for this change is required label Aug 16, 2017
@StefanKarpinski
Copy link
Member

My hypothesis would be that the REPL relies on the incompleteness error from the parser to decide whether to give a new prompt or wait for more instructions.

That is correct. I'll give it another try. It's always amazing how different people's workflows are an how that can lead to triggering bugs or completely missing them.

@tkluck
Copy link
Contributor Author

tkluck commented Aug 17, 2017

Thinking about scoping:

The current patch initiates a scope-block for the loop body and the else body together. That kind of bothers me, but then I realized that that is because this already bothers me:

julia> for i=1:2; println(@isdefined(j)); j=1; end
false
true
julia> map(1:2) do i; println(@isdefined(j)); j=1; end;
false
false

My scoping expectation would be that every iteration starts a new scope. This is particularly relevant because julia seems to want to move freely between the two constructions I use above, e.g. with the @parallel macro wrapping a for-loop.

This comment here lists a few similar scoping tickets.

Any thoughts on how it should relate to what we're doing here?

(BTW, in addition to this, this pull request still has a few TODO places for collecting variable info from the else body.)

tkluck added 3 commits August 17, 2017 23:06
I postponed this mostly because the names of the methods sounded scary
and I thought I'd need to think about it; turns out it's just a tree
traversal.
(Use `git blame -w` to see the most recent functional changes to this code.)
@tkluck
Copy link
Contributor Author

tkluck commented Dec 24, 2018

CI failure seems unrelated, and a quick inspection of https://github.com/JuliaLang/julia/commits/8eca27df422089a8c21301764c6485fb86d0fee8 suggests that is was introduced by #30442 and merged into this pull request when merging master.

@tkluck
Copy link
Contributor Author

tkluck commented Jan 18, 2019

Some new conflicts with master branch have just appeared as a result of #30656.

Is now maybe a good time to make a decision on this feature? If we put it in master now it will have maximal time in the 1.2.0-DEV window to be battle tested before general release.

I'll gladly and swiftly fix the merge conflict in case of a positive decision.

@StefanKarpinski
Copy link
Member

I think this is @JeffBezanson's call. He is Julia's Syntax Czar.

@tkluck
Copy link
Contributor Author

tkluck commented Jan 21, 2019

Hey @JeffBezanson -- anything I can do to help you review this?

@mbauman
Copy link
Member

mbauman commented Jan 22, 2019

For what it's worth, reading the documentation changes here made me like this feature much more — I had been on the fence before. It's introduced quite well and sensibly. Perhaps we should discuss on the next triage call?

@mbauman mbauman added the triage This should be discussed on a triage call label Jan 22, 2019
@StefanKarpinski
Copy link
Member

Agree. The documentation is really lovely and motivates this feature quite nicely.

@JeffBezanson
Copy link
Member

Triage has mixed reactions to this. Personally I'd say I'm weakly in favor of it; it's a useful pattern and I can't think of a really strong reason not to have it.

Points for:

  • It's good for expressions to have (useful) values whenever possible.
  • It clarifies the purpose of a loop.
  • else is great when computing the default value is expensive.

Points against:

  • It's a bit weird. The meaning of else doesn't seem entirely natural.
  • It's weird that for loops don't normally have values, but using break grants them the ability to yield useful values, when it seems like it's just control flow.
  • Putting the default value at the end is not always clearer.

It was also suggested that we look up python's experience with this and see if they regret it.

@StefanKarpinski
Copy link
Member

It was also suggested that we look up python's experience with this and see if they regret it.

At this point Rust has had break with values for long enough that we might be able to learn from their experience with that part of this feature.

@tkluck
Copy link
Contributor Author

tkluck commented Feb 18, 2019

Test failure logs give me very little to go on. Should I assume these are just noisy test failures like we've seen a few times in this PR?

@tkluck
Copy link
Contributor Author

tkluck commented Mar 3, 2019

I'm going to close this pull request and give myself some mental closure. If anyone is willing to make the effort to shepherd this through, feel free to go ahead.

@tkluck tkluck closed this Mar 3, 2019
@StefanKarpinski
Copy link
Member

StefanKarpinski commented Mar 3, 2019

Sorry about that @tkluck. It was excellent work and I'm sorry that it wasn't used. I hope you don't feel badly about it. Part of the experimental design process is that sometimes one does a lot of work on PRs only to decide not to use them. It is always still here if we decide to go ahead with it.

@tkluck
Copy link
Contributor Author

tkluck commented Mar 3, 2019

Thanks for the kind words @StefanKarpinski. Of course I do feel bad about it, but better to cut my losses.

I wouldn't even mind chasing master and dealing with CI false positives if some ending were in sight, but upon reflection, I just don't see this making further progress after the relatively unactionable outcome from the triage meeting.

@StefanKarpinski
Copy link
Member

Yes, sorry, it's a bit unsatisfying. We did discuss for a long time and just could not collectively or even individually come to a conclusion.

@ararslan
Copy link
Member

ararslan commented Mar 3, 2019

Regardless of the outcome, I do hope you can feel proud of the quality of the PR though. It's incredibly impressive work, and we all really appreciate you going through the effort. Major features like this can be the source of a tremendous amount of discussion and deliberation, but every time someone proposes something new, we all learn from it, and Julia gets better as a result, even if the feature doesn't land. So again, I hope you can feel proud of this work!

@fredrikekre
Copy link
Member

fredrikekre commented Aug 21, 2019

Since the main argument against this seems to be the use of else (?), may I suggest something like this instead (example from op):

name = for person in people
    if person.id == id
        break person.name
    end
end
if name === nothing
    name = generate_name()
end

i.e. just let the loop return nothing (as it already does) when there is no break value. This pattern (checking for nothing and doing something when it is) is already used alot in e.g. all the find* functions, so should feel pretty natural. Thoughts?

@rfourquet
Copy link
Member

just let the loop return nothing (as it already does) when there is no break value

Looks like a great idea. It's one half of this PR which looks indeed quite less controversial, although there are still mixed feelings about it: quoting #23260 (comment):

  • It's good for expressions to have (useful) values whenever possible.
  • It's weird that for loops don't normally have values, but using break grants them the ability to yield useful values, when it seems like it's just control flow.

I really like this "break with value" feature, which makes loops more "expression-like", like most things in the language. Paraphrasing: they allow to have the return feature without having to refactor a loop into a function.

Note that even though the else feature seems to be implementable separately, it would still be useful, e.g. the example in #23260 (comment) becomes a bit more annoying to write, as it removes the possibility to specify the "default value":

function intersect(s, sets...)
    i = similar(s)
    for x in s
        intersects = for t in sets
            !in(x, t) && break false
        end
        intersects === nothing && push!(i, x)
    end
    return i
end 

@tkf
Copy link
Member

tkf commented Aug 21, 2019

It's kind of already implied by @StefanKarpinski's remark #22891 but you can actually add "else clause" yourself once you have break:

ifnothing(f) = x -> ifnothing(f, x)
ifnothing(f, x) = x
ifnothing(f, ::Nothing) = f()

name = for person in people
    if person.id == id
        break person.name
    end
end |> ifnothing() do
    generate_name()
end

(of course, in this case, it's probably better to write |> ifnothing(generate_name))

So supporting only break clause sounds like a nice first step.

@StefanKarpinski
Copy link
Member

StefanKarpinski commented Aug 21, 2019

If we're going to have the break with value part, I'd be inclined to just do the whole thing—it's weirder and more awkward to not be able to do the else clause to give a default value and it's not like we're going to add the else clause to loops with any other meaning since then it would super confusingly disagree with what this means in Python. I agree that else meaning this on loops is not the most intuitive possible thing but: (a) it is very useful and (b) it's the only meaning with precedent.

I've thought of a way to look at this that reconciles what I consider to have been the most significant objection, which was this point made by @JeffBezanson:

It's weird that for loops don't normally have values, but using break grants them the ability to yield useful values, when it seems like it's just control flow.

Namely, that we just consider the natural exit of a loop to implicitly do break nothing—or just break for short (like how return is short for return nothing). It is, when you think about it, somewhat artificial to consider the expression value of a loop to be the last value that the body evaluates to since the actual last thing evaluated is always a condition: are we done yet—which always evaluates to a boolean (yes, we're done). For a while loop, the reasoning is something like this: a while loop is actually lowered to something like this:

while cond()
    # body
end
loop # loop construct that loops forever
    cond() || break # this is the return value of the loop
    # body
end

From this perspective, it is always a break statement that gives a loop its value, it's just that for implicit breaks (i.e. natural loop termination) the default break value is nothing. Only with an explicit break call can you return from a loop with a different value. If we had a loop ... end construct in the language, this would be a literal explanation since while and for would be defined in terms of it.

@Keno Keno removed the triage This should be discussed on a triage call label Feb 27, 2020
@jakobjpeters
Copy link
Contributor

jakobjpeters commented Apr 4, 2021

In case this ever gains traction again:

continue could be used to return an array. This would be a natural extension to break and quite useful.

Swapping else for default may be a good idea. It's descriptive, familiar terminology (switch statements), and may reduce some of the objections to the feature.

Points against:

  • It's a bit weird. The meaning of else doesn't seem entirely natural.


Using return within a default statement would allow users to do other things before explicitly returning a value.

Optional return types could be used to guarantee type stability. If the return value evaluates to an incompatible type, then it would trigger the else clause. If the else clause is incompatible, throw an exception. If someone doesn't care about type stability or wants to avoid the exception, just leave off the return type.

All together now:

values = rand(1:100, 10)

evens = for i in values::Array{Int64} # optional return type
    if iseven(i)
        continue i # append to returned array
    else if i == 13
        continue "not Int64" # throw an exception
    end
default # previously `else`
    newvalues = values * 2 # do stuff
    return newvalues # explicit return
end


I'd love to hear what you think about these ideas!

@tkluck
Copy link
Contributor Author

tkluck commented Apr 6, 2021

Hi @jakobjpeters ! Thanks for engaging with the topic. If you'd want your ideas to fit within julia 1.x you have to find a way to stay backwards compatible:

  • default is not a keyword, and currently this code:
for i in 1:10
    println(i)
    default
    println(i)
end

is valid (even though default is not doing anything useful there). That cannot be broken in 1.x.

  • Similarly, for i in values::Array{Int64} already has a meaning: it's parsed as for i in (values::Array{Int64}), so it asserts the type of values. This also cannot be broken in 1.x.

Please open a separate bug for your continue idea so it can get the discussion it deserves.

@jakobjpeters
Copy link
Contributor

jakobjpeters commented Apr 6, 2021

Thank you for the educational response! May I ask a couple clarifying questions to streamline the new issue?

  • Good point on default, that can be dropped.
  • For typed return, would the following already be built into Julia or have another major issue I have not thought of?
evens::Array{Int64} = for i in values
  • Should I include typed return handling, continue (in addition to break), and return in the new issue (EDIT: and else)? These appear to be closely related to the same feature set, although I may be mistaken on the best way to start this discussion. Obviously I also don't want to include ideas that would not work (such as default).

@tkluck
Copy link
Contributor Author

tkluck commented Apr 6, 2021

@jakobjpeters happy to answer those questions in their own issue. There's no need to streamline it before opening it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
design Design of APIs or of the language itself needs news A NEWS entry is required for this change needs tests Unit tests are required for this change
Projects
None yet
Development

Successfully merging this pull request may close these issues.