Is there a language with implicit variable declaration (like Julia) that works the way you suggest? I am curious. Haskell obviously does not count as it does not have variables, only constants.
Also, is your proposal basically making the effect of the existing keyword local the default behaviour (like every first assignment to a variable in a scope had local before it), and making the current default behaviour (i.e., considering that the variable is from a parent scope) only accessible by means of the ancest keyword you propose? This is, reversing what is the default behavior and what is the keyword-modified behaviour?
I don’t think you understand the scoping rules much. The hard/soft-ness of scopes has to do with whether a local scope would reuse an existing global variable in interactive contexts (REPL, notebooks) or make a new local variable instead in non-interactive contexts (files, eval). It’s unrelated to a local scope reusing an existing local variable in an outer local scope in both contexts, which you’re calling “this local scope leaks” in your examples. It’s not a bug, it’s documented right above the hard scope point:
Feel free to disagree with that design choice, other languages made different choices with closures. But it’s really useful here considering even for loops are local scopes, and you would want that to access an outer local variable by default for i in 1:3 x+=1 end.
I have no preference when it comes to harder or looser scope. I just want consistency. Without consistency, you can not build patterns, and you are more likely to produce buggy code, since predicting what the constructs would yield is complex.
That’s why I am fine with Haskell’s immutability (making scoping a non-issue), Rust’s resource acquisition, Python, JavaScript block’s closure (that finally fixed their Julia-problem), C, etc… No matter their choice of scoping, it is consistent and makes sense at first sight.
The only inconsistency I can find with Julia is that the REPL is handled as a special case. That’s a debatable decision, but it was made based on a lot of feedback, and it’s what the majority of people prefers. In any case, Julia’s scoping rules are not going to change, so if you find them unacceptable, you should probably not use Julia.
I am sorry, but it seems that you are the one who didn’t understand the problem.
JULIA’S HARD LOCAL SCOPE BEHAVE DIFFERENTLY IN A GLOBAL SCOPE AND A HARD LOCAL SCOPE.
CONSISTENCY is at the core of programming. That’s how we have been able to build all the patterns that we have!
I am not talking about ‘for’ loops. I was talking about functions declaring local variables that end up taking over some other variables in the function’s lexical scope (one of the scope above). There is nothing to understand about that. It’s simply bad design. And moreover, while having that odd behavior, the function doesn’t do it consistently - one way in the global scope, and another way in the scope of another function.
There are already good approaches to variable scopes. Julia didn’t need to come up with this poor approach.
I can explain Rust, Haskell, and C++ variable scopes to 5th-graders and they would understand it. Julia’s variable scope took almost a PhD dissertation to explain. Go read the documentation.
Note that Julia uses a sematic approach (instead of contextual approach as in, say, Python). In particular for the scope, local means everything inside a module-level function (so all local variables are shared with nested functions).
You wouldn’t get the same result if you’d define lc1, lc2, lc3 separately (instead of nested).
… and please don’t scream, this is detrimental to the discussion.
I am not forced to use Julia; the entire Data Sciences and AI community neither. That’s why they all stick with Python. The adoption of Julia is very weak. Python (coupled with C++) is the industry standard. Rust is making its way into the industry. So far, nobody is talking about Julia - despite a lot of great features that it has. I was wondering why Julia didn’t beat Python since. After dealing with this scope issue, I know why.
I am quite happy with Haskell (that can do everything Julia does and do it safer) and Rust (that can do everything Julia does and do it faster). So, I am not begging for anything here. I was just trying to help.
What exactly are you trying to achieve with this thread? Julia’s scoping rules are what they are, and they’re not going to change. I understand that you find the rules too complicated. I think everyone in this forum would be happy to clarify the details of Julia’s scoping rules. But coming in and yelling at everyone that the rules should be different isn’t going to get anything but a shrug at best, and majorly annoy people at worst.
That was a stress-test, that Julia failed miserably. I was just trying to show the inconsistency in Julia. However, it is a pattern that is used to solve certain problems, unless certain constructs replace it.
I have already found some workarounds to this problem:
Go functional as much as you can, with Julia;
Only use pure functions - these are the only ones you can trust in Julia;
Declare all your local variable using ‘local’;
From the beginning I was already disappointed by the fact that ‘variables’ were not immutable by default. That was a missed opportunity.
Well, Julia might have some uncommon choices and other defaults in its scoping rules. It is no less consistent than other languages though. It does differ between global scope, i.e., top-level definitions and local scope, i.e., anything within a hard scope – nested or not. Julia is not alone in this regard, e.g., also Common Lisp or Python handle global and local scope differently.
To be honest, I find Python’s rule more confusing. Can you guess the output of the following script?
x = 10
y = 10
def xyz1():
x = 12
y = 12
def i0():
# x += 1 # don't uncomment me!
print("x0", x)
def i1():
x = 2
print("x1", x)
def i2():
nonlocal x
x += 1
print("x2", x)
def i3():
global y
y+= 1
print("y3", y)
print(x, y)
i0()
i1()
i2()
i3()
print(x,y)
i0()
i1()
i2()
i3()
print(x,y)
print("global:", x,y)
xyz1()
print("global:", x,y)
Julia simply has a different default, i.e., think of it as using nonlocal by default and explicit local instead of Python choosing the other way around. In particular, idioms requiring mutable closures work nicely with Julia’s defaults:
julia> function adder()
n = 0
incr() = n += 1
decr() = n -= 1
(inc = incr, dec = decr)
end
adder (generic function with 1 method)
julia> foo = adder()
(inc = var"#incr#3"(Core.Box(0)), dec = var"#decr#4"(Core.Box(0)))
julia> foo.inc()
1
julia> foo.inc()
2
julia> foo.dec()
1
Okay, so you don’t like the language. That’s fine. Nobody is forcing you to use it.
I hope you realize that your issues with the language aren’t everyone’s issues. The idea that Julia is not finding adoption because of its scoping rules is preposterous. There’s lots of people using Julia very productively, and I think if you asked developers with a little Julia experience what their major pain points are, the scoping rules aren’t going to be very high on the list.
This isn’t how we do discussions here. We don’t name-call, we don’t denigrate, and we don’t shout. I sincerely do hope that you stick around with the language and community, @anon98050359 — and we can even continue talking about scope in other threads in a more productive manner. But we’re not going to continue like this.