are there examples of for or let blocks evaluated in a module scope where we definitely would want the variables in those blocks to be local?
Yes, this happens frequently when you copy/paste method bodies to the REPL and wrap them in a let. For example, in a function I can use first as a variable without conflicting with the function named first; if first is local to the let, then there is also no conflict and my copy/paste debugging works. If in contrast first is global, I have to change the name of that variable before I can debug in this fashion.
(@StefanKarpinski, Iâve not yet had time to really sit down and think about this carefully; I will try to do so soon, though I suspect youâll get all the feedback you need from others. No matter what, kudos for coming up with devious solutions!)
All scope-introducing constructs that occur outside of a function body.
only for the REPL? or all module / baremodule ?
Both. One of the goals is not to have different behaviors in different places. Since that inevitably leads to a series of questions about âwhy does this work in the REPL but not in my script/module.â In particular, since âscriptsâ (aka programs) operate in Main, which set of rules should apply to them? Are they interactive or non-interactive?
Ok, yeah so I did miss somethingâŚapologies! In my defense, itâs been a bit difficult as a bystander to try and follow all the proposed solutions and counter-examples. Glad we have some individuals focusing so much of their attention on this issue. From the multiple issues/threads, itâs obviously something thatâs hit a nerve and should be carefully consideredâŚso thanks!
So, this has me wondering how much of the trouble around this issue and proposed solutions has to do with scoping, and how much has to do with shadowing? I wonder if itâs worth considering these as separate issues? Maybe we turn Julia into a LISP-2 in v2.0? (âŚok, that last oneâs a joke )
This transformation must operate very early on AST, before lowering. For technical reasons, it has to since it has to come before closure conversion. But more significantly, it would also be very broken if the behavior of such a feature depended on details of lowering; lowering is an implementation detail, not a feature of the language.
How are these different? If a local name shadows a global one, doesnât that mean that it has local scope? Note that talking about this issue being about scope is a bit misleading: no scopes are changed on any of the proposals. The only question is about automatic inference of local and global annotations when x = is seen in various contexts.
(Also, Jeff and I both found the Lisp-2 joke very amusing )
Only allowing the introduction of new variables with either a let (more or less equivalent to cl:let) or a global (= cl:defparameter) would also solve this problem, both technically and then socially (everyone would then beg the core devs to return to the 1.0 semantics, and no sane person would bring up the issue again for fear of causing something similar).
If the question is: âWhy not allow variables in for loops to always have global scope?â, it seems like the answers fall into one of two categories:
Because we would have to be more careful with the names we use, since a re-definition of a global variable within a for loop would last for the duration of the script/REPL-session (shadowing).
Because this would generate a lot of values and objects that might consume considerable memory and never go out of scope to be collected (scoping).
Obviously, an elegant solution that manages to address both categories would be desirable, but if it proves difficult to âkill two birds bugs with one stone patchâ, perhaps addressing these concerns separately could yield better results? An example I providedâŚsomewhere in one of these threadsâŚwas how Ruby managed to address the issue of shadowing in lambdas, while still having many unresolved problems around scoping.
In my humble opinion of a discussion that excludes me due my lack of experience in the intricacies, i could imagine a rather laymanâs solution:
JULIA already has a: default REPL mode, a Shell mode, a package mode, ⌠? Help-mode ⌠, why not having the best of both worlds in Julia with a default REPL mode â julia> plus a quick prototype mode that enables copy and paste from forums, copy-paste module code etc⌠Even a copy-paste from a julia> mode would not collide with the proto> mode in case ist contains declarations of globals within for loops etc. cause in proto> mode the declaration of a global within code would be redundant, but the proto> mode code wouldnât run in the default Julia> Repl without throwing an error.
ButâŚ, i have no clue wether this âbest of both worldsâ a Julia 1.0 mode next to a old-school proto> 0.6 mode within the REPL is doable, or even disruptive.
Thatâs certainly possible. The main issue is that people will then be confused about why their code works one way in one place and differently in another.
Itâs worth emphasizing that the Julia 0.6 behavior also didnât seamlessly allow copy-paste of code from local scope to global scopes, either. See Timâs first example above.
FWIW, I fear that the cure proposed here may be worse than the disease. It seems to me that fundamentally, the confusion out of which all the discourse/stackoverflow questions arise stems from the fact that the meaning of âx=1â depends on the context in which it appears (at top level scope or in a loop). The solution proposed here (whether the original one or its modifications) makes this context dependence even more subtle; while it addresses the most common cases in a DWIM way, these subtleties may well lead to even more confusion in more complex/corner cases down the road.
In the spirit of explicit is better than implicit, maybe the following, perhaps radical, solution is a way to address the fundamental issue: KEEP the current (1.0) behavior, except for one change: declaring or modifying, but not reading, a global variable ALWAYS requires a global annotation; hence x=1 at top level scope is an ERROR (with an exception for functions perhaps). x=1 in local scope ALWAYS creates/modifies a local.
The advantage of this is its simplicity, which leaves very little room for confusion. Beginners could just be told that this is how variables are declared in Julia. If they try the likes of
x=1
for i=1:10
x += 1
end
they still get an error, but the error now appears at âx=1â, with an explicit hint to use global. This is a lot less confusing than the current x not defined. Similarly, code pasted from a function will still not work when pasted to the REPL, but at least you get a very clear error message. A possible knock-on effect is that this might reduce the number of issues where people benchmark in global scope.
Taking away the right to x = 1, with no global, sounds like a step down the usability ladder to me. If you come from another scripting language, this will be really annoying.
In my view Julia has evolved beyond a scripting language. And globals are special; see the numerous performance questions from people benchmarking in global scope. Syntactically singling them out as such is probably worthwhile.
Note also that in Matlab, IIRC accessing a global from local scope (a function) also requires the global keyword. Matlab just has fewer local scope introducing constructs. This doesnât seem to have caused many issues.