REPL and for loops (scope behavior change)

In the REPL, to test some code, I often do things like

julia> a = 0
0

julia> for i=1:3
           a+=i
       end

But under Julia 1.0 this will now throw an error

ERROR: UndefVarError: a not defined
Stacktrace:
 [1] top-level scope at ./REPL[2]:2 [inlined]
 [2] top-level scope at ./none:0

The fix is to define

julia> global a=0

which is quite annoying. Is there a way to implicitly make every variable declaration in the REPL global?

7 Likes

In the REPL, variable definitions are global - however, for introduces a new scope. See scoping.

To access the global variable, use global at the first use of the variable in the loop.

Thank you, I knew about the scoping, but not that you could use global in the loop!
But still this somehow feels broken, I cannot simply copy code out of a function in the REPL to debug it :frowning: (In the lack of a proper debugger)

3 Likes

It is annoyingā€¦ a workaround for the copy paste is to put everything inside a let block:

julia> let
       a = 0

       for i=1:3
           a+=i
       end
       a
       end
6
5 Likes

This really worries me. When we teach Julia, students typically start to code in global scope (in Jupyter) and at some point I tell them, ā€œand if you now put everything in a function it might be even fasterā€. However now, I have to tell them about scoping and ā€˜globalā€™ very early on, as they have to put 'globalā€™s in the first place and remove them in the function, which I find suboptimal. Although I think I see the technical benefit, it feels to me that this is maybe trading too much of the userā€™s convenience (I donā€™t know any interactive language where you would have to do something like this). After all Julia has not just been great for performance but also simple, naive usage. Just my two cents.

16 Likes

Iā€™ll be an instructor for Engineering freshmen this fall and Iā€™ll be teaching Julia. I have been thinking about this problem.

Since it is very ā€œJulianā€ to write small functions I think the first thing Iā€™m going to do is teach them how to write functions. Like tiny functions.
From that Iā€™ll explain scoping rules, and how to refer to a variable defined outside the function.
Then I think of introducing for loops, if statements etc using small functions that need those.
Only after that Iā€™d explain them that everything is a global in the REPL, so they have to be careful with this gotcha when tinkering on the REPL.

The point is they really should be writing small functions since the beginning.

8 Likes

Hi Guys, iā€™m having the same problem in Jupyter Notebook. Iā€™m creating my own Julia tutorial following the youtube ā€œIntro to Juliaā€ video.

In a code cell I made it:

n=0
while n < 10
    println(n)
   n+=1
end

And the result was:

UndefVarError: n not defined

Stacktrace:
[1] top-level scope at ./In[18]:3 [inlined]
[2] top-level scope at ./none:0

Iā€™ve tried to put the local status in any place but the result still goes wrong, the solution came following @saschatimme instructions (or at least how I interpreted that), like below:

n=0
while n < 10
    println(n)
   global n+=1
end

Iā€™m newbie in programming.

This problem is a kind of bug?

Am I doing something wrong?

Thanks since now guys!

2 Likes

It is not a bug.
It worked in Julia 0.6, in Julia 0.7 it gave a deprecation warning that you must add global, and in 1.0 it throws an error.

I really like favbaā€™s idea of starting by introducing functions and teaching them to write tiny functions. Sounds like thatā€™d also end up encouraging good coding habits.

2 Likes

This was a tough decision that we deliberated over for a very long time and had many conversations about. The old behavior was carefully designed to make the behavior of loops and other scope-introducing constructs the same in global scope or in the body of a function. However, the down side was that to accomplish this, whether an assignment inside of a loop or other non-function scope assigned to a global variable or created a new local variable depended on whether a global binding for that name already existed or notā€”which is not, in general, a statically predictable property. This also created a distinction between the kind of scope which a top-level loop or other non-function scope-introducing construct created and the kind which functions created. This behavior was widely misunderstood and often complained about when people were trying to wrap their heads around the scoping behavior.

Now, in 0.7/1.0 there is only one kind of scope: functions and loops and other scope constructs are all the same. So thatā€™s much simpler and now whether a variable is local or global is always statically predictable. But the down side is that the same code in a function or on global scope do not behave the same anymore. This trade off is unavoidable given the way local variable are implicitly introduced in Juliaā€”we really explored all the possible options for this. Languages that require you to declare local variables donā€™t have this problem but then again declaring the occasional global is a lot less difficult than declaring every local.

There is one possible way to recover the old ability to paste code from a function body into the REPL and have it behave the same, which is to automatically wrap the code in a let block to make the behavior like that in a function body. Weā€™ll probably experiment with this in the future and see how well it works. It feels a bit weird to special case the REPL like this but it may be better.

13 Likes

Thanks Chris, really appreciate your atention.

Another question, the way I did is the right way? Cuz I tried to use ā€œlocalā€ but didnā€™t work. Using global will not cause any trouble with other variables with same name in another loops or function?

Thank very much, again!

And it begins: for loop - Scope of variables in Julia - Stack Overflow :sweat_smile:

Iā€™m hosting a hands-on introduction to Julia for PhD-students in energy systems modeling. The whole mini-course will have three 2.5-hour sessions, and I just came back from the first session an hour ago. Four out of 12 students were stumped by this issue completely independently of each other. All four worked interactively in the REPL to make their code work before saving it to a file. None of these four had enough programming experience to know what scoping meant, but all are capable of hacking together Matlab scripts.

The bottom line is this. Programming newbies who try everything out in the global scope of the REPL find this scoping behavior completely counter-intuitive. This makes the language seem unfriendly, bordering on broken in this particular case.

As for me, I think scoping rules are an intermediate level topic and shouldnā€™t be necessary to bring up in an introductory-level course. If you just tell students to wrap all code in functions, even in the REPL, then suddenly the language seems quite a bit less interactive and harder to work with.

I understand why the scoping changes were made in 1.0, but I hope that automatic let-block-wrapping idea that Stefan mentioned gets implemented and released ASAP. It may even be worth making a 1.01 bug fix release just for that. Because Iā€™m pretty sure I lost a third of my potential new Julia recruits just because of this.

12 Likes

You really cannot win. We used to get so much flack for the scope rules being slightly complicated specifically to make this case work in an intuitive way. So we fix that and now we have this. :pensive: In any case, I think that the current behavior, though less convenient, is the right one since it is the one that is statically resolvable. We should experiment with the automatic let wrapping in the REPL, however.

3 Likes

Can that (let wrapping) be done in 1.0.1, instead of waiting for 1.1?
It is only the REPL :wink:

I am a big fan of the new behavior, the old ā€œspooky action at a distanceā€ scoping rules was one of the biggest warts on a beautiful language, thanks for taking the hard decision to fix this in v1.0.0ā€¦

2 Likes

Yes, this does sound very interesting. It could help with closures from the REPL too.

1 Like

Here is a Github issue if youā€™d like to follow along there.
https://github.com/JuliaLang/julia/issues/28789

5 Likes

Wondering if there is any update on this.

1- there were some very long discussions here and on github. Does anybody know if any conclusion has been reached?

2 - I was wondering if the global declaration and the explicit reference creates any (significant) overhead considering that itā€™s looped over at every iteration?

3 - if a solution is still open for discussion, maybe something inspired by PHPā€™s lambdas syntax would work? In julia it could look like:

somevar, someothervar = 10, 100

for i in 1:1_000 accessing (somevar, someothervar)
 # do stuff with somevar and someothervar
end

The accessing block would be optional. It could start as syntactic sugar for the ugly global as itā€™s more readable, in my opinion. Plus, as a less imperative approach, later on, the underlying implementation could be optimised at compiler level.

Just more 2c.

3 Likes

Dear Stefan:

I can no longer even make the most basic iterations work switching from Julia 0.x to 1.x - probably because I donā€™t understand the computer science behind the ā€œtweak.ā€

You say that this is a mere " inconvenience." To the beginner, this is inconvenient in the same way as a broken condom is inconvenient in regards to preventing pregnancy.

I am shattered. With this " tweak", my love affair with Julia (0.x) has suddenly turned into a nightmare. I am back to trying to turn me into a computer scientist, with Julia (1.x)? Itā€™s not going to happen.

I was doing straightforward scientific programming Julia 0.x with no issues, ever. Now, it seems that Iā€™ve run into a concrete wall with this Julia 1.x. " issue."

Please tell me that I can get over this in a day or two by pointing me to THE helpful resource that will help make me whole again. Save me from the truly inconvenient jaws of Mathematica.

3 Likes

What seems to be the problem?