Yes, this is the trade off that Python makes. It’s not a terrible one. Recall, however, how much trouble Python has had with the scope of variables in comprehensions as a result. If you choose the Python design then you either have lots of unexpected leaking of comprehension variables or loops and comprehensions behave very differently. So it’s no panacea either. In Julia, comprehensions were a very early feature and scope behavior of for loops and comprehensions are identical.
Well, no, considering there isn’t a isprime
out of the box and I hadn’t had a chance to build Jeff’s branch yet…however now that I have, I can give an actual example:
julia> contains_even = false
false
julia> start = 1
1
julia> finish = 1
1
julia> for i in start:finish
if iseven(i)
contains_even = true
end
end
julia> contains_even
false
julia> finish = 10
10
julia> for i in start:finish
if iseven(i)
contains_even = true
end
end
julia> contains_even
false
julia> for i in start:finish
if iseven(i)
contains_even |= true
end
end
julia> contains_even
true
julia> versioninfo()
Julia Version 1.2.0-DEV.219
Commit be8e29559d (2019-01-26 07:30 UTC)
Platform Info:
OS: macOS (x86_64-apple-darwin18.2.0)
CPU: Intel(R) Core(TM) i5-5287U CPU @ 2.90GHz
WORD_SIZE: 64
LIBM: libopenlibm
LLVM: libLLVM-6.0.1 (ORCJIT, broadwell)
In this case, I’d prefer at least a warning that contains_even
was being shadowed in the first version of the for
loop.
Are you referring to this behavior in Python 2?
$ python2
Python 2.7.15 (default, Jun 27 2018, 13:05:28)
[GCC 8.1.1 20180531] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> [x for x in range(3)]
[0, 1, 2]
>>> x
2
It actually got fixed in Python 3 (and also was not the case in the generator comprehension in Python 2 as well). OK, but I think you could say that Python 3 behavior is inconsistent in that the comprehension introduces a scope while the loop doesn’t, even though both of them use the keyword for
. So, Python 3 is also sacrificing a part of consistency to get a tighter scope rule.
Yes, precisely.
@jballanc I’m interested in this so I dug up the history a little bit. It looks like a rule to make your example seem to be considered once (see the “write-only” case)
but it was rejected since simple insertion of @show
etc. could change the behavior.
So the final version seems to be this one:
But it seems in your second case (finish = 10
with contains_even = true
in the loop body) have to be caught by the third rule (i.e., has to be an error). Is this a bug in the PR?
Yes, I think that is actually a bug in the PR with respect to the specification I wrote.
Edit: I’ve commented as much on the PR.
Doesn’t the revised proposal have a similar issue? For instance
for i = 1:10
t = i
@assert t>0
end
There t
is local, but if you remove assertions then you get an error.
Hmm…, you are right. Also, isn’t this problematic as well?:
t = "some important data"
for i = 1:10
+ @assert t>0
# many lines of code here
t = i
# many lines of code here
end
code_using_t(t)
i.e., by adding a read in the beginning of the for
loop, you can modify the scope of t
(which potentially break the following code). If you forget that t
is a local variable (which may happen if the loop is long enough), I think this is a possible scenario.
It is exactly my point above.
One could also assume possible future of @assert (which I tried to demonstrate there with macro @check
).
Linking back to the same post doesn’t help clarify anything.
I think it is inevitable that people coming to Julia will run into difficulties when applying concepts/habits learned in other programming languages. It is always nice to have fewer of these, but IMO this should not be a primary design consideration.
It is my impression that people stumble into this once, grok it (because of #1—it is simple), and move on. A one-time cost is paid in exchange for a simpler set of rules for the rest of their Julia experience.
I appreciate the effort to come up with solutions for this problem, but frankly, all the proposed solutions have their own complications and we may be just trading one set of questions for another. If we stick with 1.0 scoping, I expect that eventually all the tutorials and texts on Julia will just contain a simple and polished explanation of it, making this a primarily pedagogical issue.
I am sorry discourse is not ideal platform. I was writing to @tkf . Means that he is trying to point same thing.
@assert
(or @check
in my example) could change meaning due to global state.
I would be surrprised, but in case you really miss it - my code “return” 16
in case ENV["debug"]=="1"
and “return” 4
otherwise.
I was thinking it is clear counterexample to “the meaning of code no longer depends on any global state—if you evaluate the same sequence of expressions in the REPL multiple times it means the same thing every time.”
In case you knew it then sorry I don’t understand your question.
Instead of changing the rules, would it be possible to introduce a macro
@behaveAsInFunction for t=1:10
...
?
Simple and transparent. Possible?
Really agree here. Especially for everyone worrying about teaching - this is just way simpler. You can always tell students “just add @global if it’s not in a function, I’ll explain why later,” or introduce the concept of scope () earlier, but this doesn’t add any weird gotchas.
I totally sympathize with Stefan not wanting to explain this the rest of his life but (a) I really don’t think it will last forever - this is part of the growing pain from 0.6 to 1 (and python to Julia) and (b) the fact you’re designing a programming language you’re planning to use for the rest of your life means you should do it right, not to satisfy the grumblers
It will last forever since there will always be newcomers to the language. And regarding the “just write this and we discuss this later”: I absolutely do not like doing things like that in my programming courses. Julias strength is that it can hide a lot of the complexity from the introductory user.
Matlab is used worldwide is the goto language for teaching numerics (I know there are exceptions, but that are, well, exceptions). In that context, one usually will not talk about scope and the global
requirement will scare people. I even don’t know if people teaching Matlab in numerics have heard about the term scope…
Judging from some of the Matlab code I have seen, the majority of Matlab programmers haven’t
I think there is a more fundamental question implicit in these discussions: if
- being easy to write simple programs for people new to programing, and
- being a powerful, consistent, and well-designed programming language for complex projects
are conflicting goals to some extent, do we resolve this in favor of (2) or (1)?
Eg scope is a fundamental concept that people run inevitably run into for anything nontrivial. How much do we sacrifice of the language design just to avoid talking about it initially?
Exactly. And they are perfectly happy that way. This is not just intro users, but people using matlab for 2 decades… They never learned about scope because matlab has very intuitive scoping rules for scripts (and awful scoping rules for big projects).
I am willing to bet that the overlap between people arguing to maintain the 1.0 behavior, and those actually affected by it is close to zero. Are you making a philosophical point, or talking about your own usage?
If you write code for a complex project or tonnes of code and packages, you could write a hundred thousand lines of code and never use the soft scope behavior, because it is only used by those hacking away at scripts and not advanced programmers.
Julia can fulfill both groups. The proposed changes to the 1.0 behavior has no impact on the “serious” scoping rules used in packages or big projects…where you would only design with a global after careful consideration and planning.
It could be cool. But could not macro bring complications for (future at least first versions of) debugger?
I am affected by it in the sense that I was very happy with the simplification of the rules in 1.0, and would be affected (in possibly minor ways, if I understand the recent proposals correctly, but I am still digesting the latest one) by a more complex rule.
I hack away at scripts quite a bit (especially for data analysis). Writing complex packages and then working interactively with scripts are not exclusive activities; for most people the former are just for making the latter easier.
I would like to suggest yet another option: loops introduce a new scope except when in global scope, i.e., giving up requirement #4 in top level. (Comprehensions always introduce a new scope.)
This does introduce a new difference between top-level behavior and behavior inside, which may seem to go against #3, but I think in practice the behavior would become more similar. For example, this currently works (and there seems to be a near-consensus that it should keep working):
function foo(r)
found = false
for x in r
if istheone(x)
found = true # writes to outer scope variable
end
end
end
This and similar patterns where a loop modifies variables from an enclosing scope would work similarly in global scope. So I think such behavior would improve the state of #3 from the perspective of prototyping use – writing block in top level and then moving them to functions would work in the common cases. This also removes problems as in the examples above where adding a seemingly innocuous read (such as in a print
or @assert
) changes the scope. So to me it seems to improve both #1 and #3 at the expense of #4 in top level.
The main difference that remains between top level and functions is that new variables introduced inside a loop in global scope would remain visible. This means that code which introduces new variables inside a loop and then reads them outside the loop would break when moved to a function. I think this is much less common, and therefore less harmful for the usual “prototyping” use case, but I might be missing some use case.
I believe the main “production” use of loops in top level is for adding methods to functions inside loops, so presumably this use would not be affected.