Variable scope concept

Hello,
Why is the error related to the variable scope given in the following code?

num = 0
for line in eachline("file.txt")
    num +=1
end
print(num)

The error is:

┌ Warning: Assignment to `num` in soft scope is ambiguous because a global variable by the same name exists: `num` will be treated as a new local. Disambiguate by using `local num` to suppress this warning or `global num` to assign to the existing global variable.
└ @ ~/Downloads/Julia/File.jl:169
ERROR: LoadError: UndefVarError: `num` not defined

Thank you.

There’s a manual section on scope, but I think the most helpful suggestion is actually to put code like this within a function.

I will again note that this could be re-written as a one-liner :stuck_out_tongue:

julia> count(Returns(true), eachline("file.txt"))
293

Hello,
Thank you so much for your advice.
I am a newbie and I want to know where my problem is and to better understand the concept of Julia programming language.

This is my explanation of that: Scope of loops · JuliaNotes.jl

It is likely superficial and imprecise, but I think easier to understand than what’s in the manual.

1 Like

As you probably have tried, it works in the interactive REPL and notebooks, but not in files or in Julia expressions. Yes this is a very weird discrepancy with a weird history, it’s intuitive to nobody.

Minimal example in the REPL
julia> x = 0
0

julia> for i in 1:10
         x += i
       end

julia> x
55

julia> @eval begin
       x = 0
       for i in 1:10
         x += i
       end
       x
       end
┌ Warning: Assignment to `x` in soft scope is ambiguous because a global variable by the same name exists: `x` will be treated as a new local. Disambiguate by using `local x` to suppress this warning or `global x` to assign to the existing global variable.
└ @ REPL[4]:4
ERROR: UndefVarError: `x` not defined in local scope

First the principles. You need to declare global variables, like global num in another (even subsequent!) line or global num += 1 in the same line, in local scopes. All blocks except begin and if introduce a local scope for convenient variable lifetimes and capturing, so you’ll have to write global often if you work with global variables. Alternatively, you could put the whole thing in a let block so it’s all local variables.

The reason for a local scope freely borrowing from outer local scopes but not the global scope is because only the global scope can be scattered across several include-d files, so this disambiguation protects you from accidentally reassigning a global variable from 15 files ago when you meant to write a local variable. The soft scope concept relaxes this for for, while, try, and struct blocks for convenient use in the REPL and notebooks, especially pasting local scope code. (I’m actually not sure why struct was included because struct blocks don’t run code outside its inner constructors in practice.)

Described fixes for the minimal example earlier
julia> @eval begin
       x = 0
       for i in 1:10
         global x += i
       end
       x
       end
55

julia> @eval let
       x = 0
       for i in 1:10
         x += i
       end
       x
       end
55

Now the history. The relaxed behavior was around first, but people complained it was hard to remember and tell which blocks would make an assignment affect a global variable, especially from another file. So, the stricter behavior was done for all local scope blocks in v1.

Then people complained they needed to write global everywhere and remove it for pasting into local scopes. While some new users from other languages were just complaining this isn’t what they’re used to, there was an annoying factor that affected anyone: executing code by line is routinely done for debugging and development, and running lines in the globally scoped REPL had to be done when the debuggers that could step through local scopes weren’t around yet. IJulia (for Jupyter) independently reverted to the pre-v1 behavior, so v1.5 introduced the soft scope concept as a compromise to divide the relaxed and strict behaviors for where they were demanded the most.

I personally dislike this discrepancy and would prefer the strict v1 behavior for safety, but since v1 had to exist first for debuggers to even start development, I don’t know how it should have turned out differently. I also definitely wouldn’t want more blocks to not introduce local scopes; that’s a few more major gripes in other languages I’m glad to not to deal with.

1 Like

Hello,
Thank you so much.
Your example is similar to what I have done:

s = 0
for i in 1:3
    s = s + i
end

It works for you, but not for me. I use VSCodium. I ran the same code using the julia command and it worked!
Why?

Hello,
Thanks.
I tested two types of code:

global num = 0
for line in eachline("file.txt")
   num +=1
end
print(num)

And:

num = 0
for line in eachline("file.txt")
   global num +=1
end
print(num)

Why does the first code not work?

The global declaration only applies to the scope it’s directly inside. The one outside the for loop thus doesn’t affect the for loop, so the variables in the for-loop will be treated as a local variable, not a global variable.

I second this, but add that a let block also puts the code in a local scope and saves an extra function call. If the code doesn’t need to be run in a global scope for persistent variables, then a local scope would work just as well and doesn’t need to deal with rules concerning the global scope (again, because a local scope begins and ends in the same file).

:thinking:
Not really. Both codes work in julia command, but only the second one works in VSCodium.
Why?

I can say that both versions should work in the REPL or a notebook’s relaxed rules, otherwise the 1st version would error because of the stricter rules. I’m not sure what you mean by “julia command” so I can’t say anything about that now, but your experience is consistent with and is likely explained by the soft scope discrepancy. Like my earlier example pointed out, the stricter rules also show up when evaluating Julia expressions (this also happens for include, include_string, eval, @eval) in the REPL because the relaxed rules can’t interfere that deeply into Julia execution.

2 Likes

Hi,
I mean writing julia in Linux Terminal:

Codes work in this environment, but not in VSCodium. As far as I know VSCodium uses Julia installed on Linux!
Where is the problem?

The problem is that one explained in the previous answers. The scoping rules are different depending if you are on the REPL or executing the code by including a file.

And it’s wise to put code into functions. This both makes for a more rational scoping, and ensures the code is compiled when used.

Hello,
Well, that’s too bad. You need an IDE like VSCodium to write a project. If the REPL behavior is different from the IDE, then how do you know that the code you wrote is really wrong? What is the solution?

Put the code into functions.

There’s some tips for workflows here:

https://modernjuliaworkflows.org/

2 Likes

How are you running the code exactly?

For instance, if you have some code written in a file (in VSCode(ium)) and execute it by selecting and Control-Enter, it is executed in the REPL and it works with the soft-scope rules. That is, this code:

a = 0
for i in 1:3
    a = a + 1
end
a

can be copied/pasted to the REPL or executed with Control-Enter from VSC and it will work.

Yet, another alternative (to which one converges when the codes becomes slightly more complicated, is to put that into a function):

function test()
    a = 0
    for i in 1:3
        a = a + 1
    end
    a
end

The file where that function is written is read using

julia> using Revise # put this into your startup.jl file

julia> includet("./file.jl")

and then the function can be executed with test(). After modifying the content of the function, you can just run test() again that the new version of the function will be executed.

See: Development workflow · JuliaNotes.jl

2 Likes

Hi,
Thanks.
Why does this error only occur in the IDE and not when I use the julia command in the terminal?

Hi,
I use VSCodium.

“In the IDE” depends on the implementation of the IDE. It’s not a separate idea. Typically, when you run something from vs code, what it’s doing is (the equivalent of) copying code from a file and pasting it into the REPL. When you do this, you get soft scope behavior, as explained.

If you do julia my_file.jl from the command line, it’s not interactive, and you get the hard scope behavior.

1 Like

Hello,
Thank you so much for your reply.
I’m a newbie and it’s really hard for me to understand what you said. In my opinion, such behavior is not acceptable. To write big projects, you need an IDE, and such behavior makes the programmer wrong.