Are the scope rules applied correctly by Julia?

Hello guys! In test1, array A has a local scope, shouldn’t it be global? In test2, array A has a global scope (OK). That seems wrong to me. Both scopes must be global. The difference in the two situations is very subtle! Please help me to understand correctly.

A = [1,2,3]

function test1()
	println("test1")
	A = [4,5,6] # can't take global ref
end

test1()
println(A) # = [1, 2, 3]

function test2()
	println("test2")
	A[1:end] = [4,5,6] # catch global ref, global A = [4,5,6] # It also works
end

test2()
println(A) # = [4, 5, 6]

Julia Version 1.6.0
Commit f9720dc2eb (2021-03-24 12:55 UTC)
Platform Info:
OS: Linux (x86_64-pc-linux-gnu)
CPU: Intel(R) Core™ i3-7020U CPU @ 2.30GHz
WORD_SIZE: 64
LIBM: libopenlibm
LLVM: libLLVM-11.0.1 (ORCJIT, skylake)

Hi. The rules are applied correctly: In the first example you assign a new object to the variable A the first time it is seen, and that makes it a local variable. In the second example you modify the variable A the first time it is seen; that’s partly a “read” operation, because you access its contents to select what part of the array must be replaced, so it is assumed to be a global.

5 Likes

Note that this behaviour is exactly the same as e.g. in Python.

2 Likes

I really recommend reading Manual > Scope of Variables > Local Scope, or even the whole Scope of variables section. Also, note there may be small differences on how scope behave based on if you are executing the code in the REPL or from a file.

1 Like

So is it always good to use global A, B, … within functions? For example:

A = [1,2,3]
function test()
	global A
	println("test1")
	A = [4,5,6]
end
test()
println(A) # [4,5,6]

No! rs…

If you want to use a global variable, you have to do that. But you should not do that almost never, because that hurts performance a lot. See the performance tips, the first one!

You should pass the variables as arguments to your function, i. e.

A = [1,2,3]
function test(A)
  println(A)
end

julia> test(A)
[1, 2, 3]


4 Likes

In addition to the advice given by @lmiq, if what you want is to update the values of A, while keeping the type of its elements and also its size, you can proceed as in your second example, but a nicer way of doing it – without indexing – is:

A = [1,2,3]

function test()
	A .= [4,5,6] # note that it is a "dotted assignment"
end

The “dotted assignment” means “replace the elements of A by the elements of the right hand side”, in contrast to = that means “replace the whole object referred to by A by the object on the right hand side”. That doesn’t have the downside of modifying globals mentioned before.

And not just that. Global variables are bad for all sorts of reasons, and you should almost never use them.

2 Likes

Global variables are good essentially to represent effectively constant values, like the speed of light. In which case declare them as constant globals:

const c = 299792458

This you can use inside a function without performance problems, but you cannot modify it* (you can, but that is another story, normally you should not and the standard behaviour and errors reflects that).

1 Like

And using constant in a global scope is right?

I liked the “dotted assignment” !

“right” may mean many things. What I meant is that you can use a global constant variable in good code, and that will perform well and also be easy to understand and predictable.

One other reason for not using non-constant global variables is that their values can change and so the result of a function can be unpredictable. For example:

function f(x)
   return x + a
end

what does this function return? That depends on what a is. If a is a trully constant value defined in your code, like the speed of light, declared as const a = ..., then someone will be able to predict the result of the function (and the compiler will know everything that it need about the function to optimize it). If a is not a constant, it may change depending on the definition of a provided at runtime. Then the result of f is unpredictable, and the code is hard to understand and to debug, and also the compiler cannot do optimizations.

2 Likes

What if all code is in the global scope? Is this good or bad? I do not do this.

bad.

You will have all the problems mentioned.

3 Likes

That’s right, I have. I use include(“program.jl”) because of PyPlot, because the first run of the plot is very long and I have to test the code several times. Then, when there are constants in the global scope, Julia keeps giving me warnings, so I change constants to variables! Another thing that I think is bad is passing too many parameters in functions in several functions.

Too many parameters for functions is not bad in principle. The thing is to organize them, most likely in a struct.

You do not need to restart the REPL every time. Take a look at these tips: Development workflow · JuliaNotes.jl and Modules and Revise · JuliaNotes.jl

But, if if you are plotting stuff only, you can do everyting in global scope just fine. The peformance tips are important for code that needs performance. Otherwise one can use the flexibility of the dynamic typing as one wants.

4 Likes

The test version of the code runs 1 or 2 days, the official version runs 7 or more, so I need everything about performance. Thanks a lot for the tips.

1 Like

Thanks to everyone.