If you are interested really interested in the history, here are some links:
Way back in 2012, when the language was in its infancy, there was a discussion about scoping that eventually motivated “soft” and “hard” scopes, which Julia had until 0.6:
opened 11:06PM - 20 Feb 12 UTC
closed 09:34PM - 08 Jan 13 UTC
speculative
decision
breaking
Hello Julia developers,
congratulations with your great new language. I was sin… ging 'O Julia' out loud all day yesterday.
But now my bug report. Notwithstanding the `let`, `global` and `local` keywords, it seems there is not always a clear distinction in Julia between variable declaration and assignment. This breaks encapsulation and can lead to hard to find bugs.
```
function some_scope()
...
... many lines of code
...
f = () -> x = 42 # Not clear if this declares a local variable or assigns to one from the enclosing scope.
end
```
Matlab's scoping rules we don't even need to discuss [1], Coffeescript is a disaster [2], please don't copy Ruby on this one [3], Perl and Javascript are ok as long as you don't forget your `my`'s and `var`'s (or use strict), Python fixed it in version 3 [4], and one could say Scheme got it right the first time in 1970 [5].
What is the reasoning behind Julia's intricate scoping rules?
[1] http://www.mathworks.nl/help/techdoc/matlab_prog/f4-39683.html#f4-73993
[2] https://github.com/jashkenas/coffee-script/issues/712
[3] http://www.rubyist.net/~matz/slides/rc2003/mgp00010.html
[4] http://www.python.org/dev/peps/pep-3104/
[5] http://news.ycombinator.com/item?id=3379962
This was by and large intuitive, but had some corner cases. Scope simplified conceptually for 1.0:
JuliaLang:master
← JuliaLang:jn/toplevel-scope
opened 09:53PM - 14 Nov 16 UTC
This PR examines the impact of deprecating (much of?) the distinction between ha… rd/soft scope. Instead it simply distinguishes between global and local scope. This means that all scope-blocks introduce the same type of scope (local), rather than distinguishing that toplevel functions have special, hard scope rules. I've updated the manual to try to show how this change would impact the user. The main change is that there would no longer be the concept of implicit globals computed from examining the module bindings. Instead the global/local computation would be purely syntactic. For example, take the following code snippet:
```julia
global x = 0
for x = 1:10 end
@show x
```
Under the current, this shows `10`, because `x` had a value before the for-loop.
Under the new rules, this shows `0`, since the for-loop introduce a new local scope.
Making this code work as before would requiring declaring `x` to be a global inside the for-loop scope block:
```julia
for x = 1:10; global x; end
```
Another option that this PR still permits is to make an assignment to x inside a begin/end block:
```julia
begin
x = 0
for x = 1:10 end
end
@show x
```
The impact to base is small (two corrections to code that will be broken anyways when #265 is fixed).
The impact to tests is larger, as it takes a large number of unintentionally-global variables and causes them to emit a deprecation warning. I think the resulting changes are arguably beneficial, even if we don't decide to change the scoping rules, since they reduce the number of objects being kept around in global variables.
but some people still found it unintuitive:
opened 03:36AM - 21 Aug 18 UTC
closed 08:33PM - 28 Jan 20 UTC
REPL
minor change
### Example 1
This came up with a student who upgraded from 0.6 to 1.0 direct… ly, so never even got a chance to see a deprecation warning, let alone find an explanation for new behavior:
```julia
julia> beforefor = true
true
julia> for i in 1:2
beforefor = false
end
julia> beforefor # this is surprising bit
true
julia> beforeif = true
true
julia> if 1 == 1
beforeif = false
end
false
julia> beforeif # Another surprise!
false
julia> function foo()
infunc = true
for i in 1:10
infunc = false
end
@show infunc
end
foo (generic function with 1 method)
julia> foo() # "I don't get this"
infunc = false
```
### Example 2
```julia
julia> total_lines = 0
0
julia> list_of_files = ["a", "b", "c"]
3-element Array{String,1}:
"a"
"b"
"c"
julia> for file in list_of_files
# fake read file
lines_in_file = 5
total_lines += lines_in_file
end
ERROR: UndefVarError: total_lines not defined
Stacktrace:
[1] top-level scope at ./REPL[3]:4 [inlined]
[2] top-level scope at ./none:0
julia> total_lines # This crushs the students willingness to learn
0
```
I "get" why this happens in the sense that I think I can explain, with sufficient reference to the arcana in the manual about what introduces scopes and what doesn't, but I think that this is problematic for interactive use.
In example one, you get a silent failure. In example two, you get an error message that is very there-is-no-spoon. Thats roughly comparable to some Python code I wrote in a notebook at work today.
I'm not sure what the rules are in Python, but I do know that generally you can't assign to things at the global scope without invoking global. But at the REPL it does work, presumably because at the REPL the rules are different or the same logic as if they were all are in the scope of function is applied.
I can't language-lawyer the rules enough to propose the concrete change I would like, and based on Slack this isn't even necessarily perceived as an issue by some people, so I don't know where to go with this except to flag it.
Cross-refs:
#19324
https://discourse.julialang.org/t/repl-and-for-loops-scope-behavior-change/13514
https://stackoverflow.com/questions/51930537/scope-of-variables-in-julia
so
JuliaLang:master
← JuliaLang:jb/softscope
opened 08:59PM - 15 Nov 19 UTC
After thinking about it off and on for quite a while, @StefanKarpinski and I mor… e-or-less decided that the best way to change the REPL scope situation (if at all) is just to bring back what v0.6 did. That's what SoftGlobalScope and IJulia already do (albeit somewhat approximately; implementing it internally it's much easier to get it 100%), and seems less disruptive than introducing a *third* behavior.
This needs to be finished up but is ready to try. Please give it a whirl.
I'm not sure what the best interface to it is; it seemed easiest just to drop a special expression in the AST itself.
fixes #28789
was introduced to make a particular use case easier.
You will notice that a lot of thought went into scoping rules. If you want a deeper understanding, I recommend working through the code examples people posted in various discussions above (and some others you will find from there); I found it really instructive.
Generally it is not easy to define scoping rules that are intuitive (“do what I mean”), yet easy to reason about (including corner cases), especially in languages that don’t have different syntax for assignment and introducing new variables.
IMO the important thing is not whether scope behaves in a way that users from some other language will find intuitive, since Julia users come from variety of languages with different solutions to scope; but whether scoping is easy to understand and apply in practice after reading the relevant chapter in the manual. Personally, I think Julia’s current approach is a rather nice practical solution.
8 Likes