So hypothetical logic for this feature request would have to be a bit more subtle: if the variable doesn’t already exist (as a local or do we also allow this to work with globals?) then you’d want to implicitly declare the variable in the immediately enclosing scope. Also, what about the case where the loop doesn’t execute? Then the existence of the untaken loop causes an undefined outer variable to be implicitly created. It seems clearer to require the user to explicitly declare the variable, then there can’t be any confusion about what scope the variable should appear in or what value it should have if the loop isn’t taken.
That’s right, variables must be defined at compile-time unequivocally.
However, I think an outer to implicitly define the local sounds good. Although, there is a confusion with an outer referring to a global, I think it is better to always define an implicit local. And to require an explicit global to access the global.
Even the keyword name outer seems to suggest the enclosing scope and the current default requires more effort in more cases (it isn’t the right “branch prediction”).
Additionally, we should be wary of the simple do-nothing bias, as this is a rarer example of a not-often-used construct which could still be improved with not much cost (or maybe I’m mistaken about this. To answer this question, a source-wide grep is needed).
rfourquet already demonstrated that an implicit local statement can shadow an already existing outer local variable, so best case is doing it only when there isn’t any in the outer local scopes.
When there are no outer locals to shadow, putting one right outside the for-loop makes sense. However, as StefanKarpinski said, variable isn’t guaranteed to be initialized because the for-loop isn’t guaranteed to run e.g. for outer i in iter where iter is empty. There’s no reasonable implicit default value to fix that. Relying on an iterable to be non-empty to initialize a variable is very likely a bug, and I prefer it to be caught by a syntax error at definition before an undefined error at some of the calls:
julia> function f(n)
local i # implicit proposal
for outer i = 1:n end
ERROR: UndefVarError: i not defined
@ Main ./REPL:4
 top-level scope
julia> function f(n)
for outer i = 1:n end
ERROR: syntax: no outer local variable declaration exists for "for outer"
 top-level scope
So, the consequences of the implicit local i proposal is actually substantial.
Okay… I relent… the status quo isn’t so bad.
But still, that extra definition is quite annoying.
But there is something awkward in the way for loops communicate to the enclosing scope. Not knowing if the loop exited through break or through iterator end. Not knowing final value. Feels like these values are available to the code and to the mental model of programmer, yet not easily enough in the source code.
I invite anyone who shares this feeling to suggest an improved syntax (in this thread or somewhere else).
You’re not alone in this opinion. The structured program theorem that moved programming languages away from GOTOs and toward block patterns does not need multiple exit points like break and early return, and some stricter interpretations enforce single exit points e.g. Pascal. However, this would require some extraneous variables and code duplication in many programs, and Shapiro found that students tend to run into bugs in Pascal that don’t happen at all if they were allowed to return early. Kosaraju ended up proving that you can only avoid extraneous variables entirely by allowing multi-level breaks from a loop.
I mean, it’s impossible to know that at the source code in general because it’d depend on runtime values and calculations that we probably can’t do on our own.
Of course, knowing the final value at loop exit at run-time. But currently it isn’t so accessible, unless you shadow it with a local or through the outer construct. A tad too much work, for something the programmer is quite sure the machine has access to (and similarly the point of loop exit - return or iterator-end - which can be somehow “labeled”). This use appears in so many algo pseudo code that it is a shame it’s not more syntactically simple.
lastval = 0
for i in 20:30
lastval = i # shadowing
# here `lastval` is 0 if no iteration
# or last iteration before break
# (*) still can't figure if iterator-ended or broke in last iteration.
This kind of decision depending on loop ending condition is very common in algorithms. outer makes this a little simpler, but there is point (*) also.
I’m not suggesting any better syntax yet. But feel there should be one. And we should be couragous enough to not immediately dismiss improving this.
So you mean that the value of for i in isn’t accessible from outside the for-loop unless you assign the value to another local variable from the outside? That’s normal for local variables when its origin scope ends; in this case the i is local to each iteration, so lastval = i really is necessary as an outer variable that can persist across iterations. A manual lastval = 0 is necessary to properly initialize in case the for-loop runs 0 iterations. no changes to for outer i in can possibly get around that.
You can if you store some information in extraneous outer local variables, break just doesn’t do it for you because it’s often unnecessary work.
Worth mentioning that this is extremely rarely used, and when someone misquoted Guido van Rossum as wanting to rename the else to a more intuitive name like nobreak, he clarified that he wouldn’t include this feature at all if he could go back in time. The flexibility and clarity of per-break flags and if-statements after the loop just outweighs it.
Then that wouldn’t need to be elided or optimized away, it just wouldn’t be there in the first place.
It’ll change the return values of functions whose last expression is a for-loop, so it’s not actually backwards compatible. Adding syntax isn’t always backwards compatible, it depends on implementation.
This is infeasible to prove. Sampling for-loops in source code isn’t enough because the source code does not say how often each for-loop is used, and that’ll change with the package ecosystem. A useless package can generate enough methods ending in for-loops to dwarf every other for-loop in existence, it would be meaningless.
Hard disagree, Julia is changing fairly quickly. The key difference is those features are highly demanded and developed collaboratively, not done on unvetted whims.
Nope, even if implementing the same algorithm, foreach-do involves a closure and would introduce type inferrability issues for reassigned captured variables equivalent to outer local variables accessed by a for-loop.