Remove the soft scope altogether and make it global

I read the whole previous discussion and acknowledge the complexity of the issue, and also I find that my figure of the nested scope was not accurate.

To help those who have not previously followed the discussion (like me), I have made a chronicle about the whole story.

Note that the whole story goes way, way back. See

https://github.com/JuliaLang/julia/issues/423

3 Likes

Firstly the answer is not ambiguous. It is 1 whether x is global or local. Secondly people would rarely program like this unless they love bugs. This is a niche programming practice to begin with. It wouldn’t take a long time to wrap the first line in a function to fix this.

The real issue is, should Julia really break under:

k=0
for i=1:10
k = k+1
end

This is not even ambiguous. But results in an error in 1.7.0 !!
Just how many times do we do things like the above?
This is not even just about teaching beginners. It’s fundamental.

If this was just a warning and global scoping was allowed, it can potentially result in bugs in niche cases like you suggest. The warning alone is enough to annoy people to start using functions.

Errors will make people stop loving the language. It leads to frustration, needing to use functions even while prototyping, introduces difficulty in debugging, inability to copy code, ugly globals etc…,

Please consider changing the scoping rules in v2.0+.

2 Likes

Uhm… It doesn’t. This is my take on how we ended with the current rules, which are a fine balance between pros and cons: Scope of loops · JuliaNotes.jl

I don’t see this been one possible change in 2+, which in any case doesn’t seem to have a compelling reason to exist yet.

4 Likes

The article you cited doesn’t mention Jupyter notebook and Juno. I believe there is some work in this direction. Maybe you can add that.

Is there any difference there? I don’t use them, so don’t know what to say.

They are the same as the Julia REPL.

2 Likes

image

This is because the soft scope only works in the REPL and in stuff like Jupyter. Scripts were unaffected by the change. We couldn’t make the soft scope happen in scripts because it would be a breaking change.

I think it was a real mistake to have the scoping rules be different in the REPL vs a script.

2 Likes

It doesn’t work in the scripts. I works in the REPL and inside functions and let blocks.

Yes. Exactly! Thank you! I use scripts for most of my prototyping. It helps in debugging. Now I cannot copy code from scripts.

To be clear though, I’m saying that it should error in the REPL as well as the script. Not erroring in the script would be a breaking change to variable scope.

1 Like

So are you for having different scoping rules in scripts and REPL as opposed to functions and let blocks etc?
Why?

How important is it to be able to copy code from scripts/REPL into functions. We care about debugging more than some microseconds of performance in scripts. Think about all the time it would take to delete and add globals.

If you had such rules in the REPL, people might stop using Julia on the first day.

You can also use inline evaluation to get the soft-scope behaviour in VS Code:
image

I think it’s fair to find that an annoyance. But if you adapt your workflow to prototype inside functions, using Revise in particular (tracking the changes of your script), not only you can copy and paste to the REPL again, but you can just update and rerun your function without the copy/paste, which is even more convenient.

3 Likes

I agree that in your example, it seems obvious that a for-loop should access that global k because it would otherwise cause an UndefVarError. The thing is, letting local scopes automatically reassign existing global variables (like how they reassign outer local variables) would cause difficult bugs. This is a bigger problem for methods, which can run repeatedly, but I’ll show a for-loop example:

digit = "thumb"
get_finger() = digit

# get_finger()  would return "thumb"

k=0
for i=0:9
     global k          # invisible if automatic
     global digit      # invisible if automatic
  digit = rand(0:9)    # random digit from 0 to 9
  k = k + digit * 10^i # k gets new leftmost digit
end

# get_finger()  would be some random integer now

The clear intention is that get_finger() accesses a global digit describing a finger. However, the for-loop assigns a digit describing an integer from 0 to 9, clearly intended to be a local variable completely separate from the global digit (note that in your proposal, the global statements would be invisible).

Well, as you might point out, you have to fix name collisions in local scopes anyway e.g. rewriting as digit_finger vs digit_number. It’s not hard to look over a scope and figure it out, what’s the difference?

The difference is that any local scope has to be written in its entirety within a file, but a global scope can be written across multiple files and combined into one via include. The get_finger code and k code were substitutes for more complicated code that would be written in separate files:

include("finger.jl")
include("numbers.jl")
include("somethingelse.jl")
include("evenmorestuff.jl")

Now maybe you wrote all of these files separately, and they run just fine on their own, but the moment you tried to combine them into the same global scope, something like get_finger() is broken. Where is the source of the bug? It’s not in the file with all the includes. Maybe it’s in numbers.jl, maybe it’s somethingelse.jl, or maybe both. You have to fix a name collision among ALL the files so that a local variable won’t be mistakenly treated as a global one.

Assuming that globals are reassigned automatically as you propose, a way to prevent this problem would be to declare every local variable local, just to avoid colliding with some global variable in some other file. But there are way more local variables than global variables, so that would be an even bigger hassle.

Aside: you can’t reassign variables imported from other modules/global scopes for this reason, too. Easy to break things, hard to figure out where. At least when you add methods to an imported function, you can pin a method to its source module and file.

3 Likes

Thank you for giving such a nice explanation. I really thought about this.

I agree that it can potentially avoid such niche bugs across multiple script files.
But who uses multiple script files which all have global scope?
Isn’t that a practice that’s frowned upon?
At the cost of helping prevent such niche bugs for novice programmers, having to write locals and globals and potentially causing other bugs in the way is too big of a compromise.
Literally I am having to redeclare variables in every for loop. How bad can it really get?

It is a such a bad trade-off. I still stand by this. Thanks very much for your effort in trying to help me understand.

includeing multiple files into one file’s global scope is quite routine in Julia, actually, so the local-as-global bug wouldn’t be so niche. It’s very common for a project to grow to the point where organizing a global scope into multiple files makes more sense, but it wouldn’t make any sense to split the global scope into separate smaller global scopes. This isn’t so strange, many other languages have include.

Thank you Imiq. I looked at Revise.jl. It might be of some help indeed. Loading REPL is really slow in vs-code when using the inbuild debug mode.

Do you have a suggestion for a better workflow? I would ideally like to evaluate line by line from within functions for debugging.