Local scope in for loops AGAIN

Hi,
New to Julia from MATLAB. I’ve been reading up on the local variable scope rules in Julia, but fail to understand the behaviour i see in the following example

Example code

u0 = 10     #The global scope variable

for k in 1:10
    local local_u
    
    #In the first iteration, local_u reads from the global variable
    if k == 1           
        local_u = copy(u0)
    end

    #My main function call goes here - returns uk which i want to use in next iteration
    uk = k*10*local_u

    #Reassigning local_u to the result from the current iteration
    local_u = copy(uk)
    println("At end of iteration ", k, " local_u is ", local_u)
end

I can see that it goes through 1 iteration, but then throws the error when trying to execute.
I get the error message -> UndefVarError: local_u not defined

So my Question is - Does the local variable (local_u) get destroyed after every iteration in the for loop ? Or am i misunderstanding something here.

Any help appreciated.

You’re recreating local local_u in every iteration of the loop, thus overwriting its previous value. The behaviour you experience is therefore to be expected.

Why not just wrap it in a function?

u = 10 # global scope variable

function foo(u)
    for k in 1:10
        u = k*10*u # could also write u *= k*10
        println("At the end of iteration $k local u is $u")
    end
end

foo(u)

This is much simpler and you don’t have to deal with the local keyword, reassigning, copying or anything else.

4 Likes

Sorry, i do not fully understand. Then removing this line from my code should have solved th issue right? Even when i remove the line “Local local_u”, i get the same behaviour and error. It should be able to access the variable local_u from iteration k=1 during iteratio k=2 in this case, but doesn’t look like it.

My original code is trying to solve an Optimal Control Problem and simulate the plant, which are already in their own functions. I just put in k*10*local_u as a dummy line here instead of my function call. I am using the for loop to step through time in the main file, and hence did not want to wrap in a seperate function again.

OP, here is an MWE

julia> for i in 1:10
       if i == 1
           a = 2
       end
       
       @show a
       end

This will throw the same error.

The way to think about this is that when you say a = 1, that assignment only exists for the first iteration of the loop. The other iterations of the loop don’t know that a variable a exists since it isn’t created in those other iterations.

If you want it to persist, you need to define it outside the loop. This is certainly a bit of a pain before 1.5 but now it works “as expected” when copied and pasted into the REPL.

3 Likes

My approach would remain the same. Just write u = bar(u) instead of u = k*10*u. I don’t see any problem with that.

Oh, that’s too bad. My mistake was that i misunderstood that the local variables were available until the “for loop” terminates. Thanks for the correction.

In this case, then I will use this approach then,

a = 1
for i in 1:10
    global a1

   a_temp = i*10*a #Get the next step of a. to be used in next iteration
   
    @show a_temp
    a = a_temp     #assigning a_temp to be used in next iteration
    
end

If i may ask a followup question.
The line a1 = a_temp seems to be working instead of me having to use a = copy(a_temp).

Am i right in my understanding then that
a = a_temp binds the variable names to the same underlying number, but when a_temp is destroyed at the end of iteration 1, the global variable a is able to retain the same value and does not end up cleared (even though the underlying local variable was cleared out)

yes that is correct.

1 Like

Probably one thing more to note, is that arrays work differently than scalars, and the copy function plays an important role there:

julia> a = 1 ; b = [ 1, 1 ] ;

julia>  for i in 1:2
           a2 = a
           b2 = b 
           a2 = 0
           b2[1] = 0
       end

julia> a
1

julia> b
2-element Array{Int64,1}:
 0
 1

Note that the value in the first position of the b vector changes, but the value of a does not, the variable a2 is local to the loop. Now using copy:

julia> a = 1 ; b = [ 1, 1 ] ;

julia>  for i in 1:2
           a2 = copy(a) # this is redudant for a scalar
           b2 = copy(b)
           a2 = 0
           b2[1] = 0
       end

julia> a
1

julia> b
2-element Array{Int64,1}:
 1
 1

julia> b2
ERROR: UndefVarError: b2 not defined



For the scalar, this is the same as before, but now b2 is a vector local to the loop, occupying a different space in memory, and altering its values do not alter the original b.

1 Like

That isn’t exactly true. Right now:

  • It isn’t required in Jupyter (and it never has been).
  • It (>= 1.5) isn’t required in the (normal) REPL or execute the code through any IDE that sends text to a normal REPL.
  • It is required if you use the REPL in vscode
  • It is required if you run it as a .jl file with include, etc.

My advice: stick to jupyter for this sort of thing where there are any top-level loops, and move to functions as soon as you no longer want to use jupyter

1 Like

Yes, I fixed that already. In that context it was never required.

What changed is that if you need to assign a new value to the global variable, in 1.5 or greater you do not need to add the global anymore when using the REPL.


julia> a = 0
0

julia> for i in 1:2
          global a # not required anymore in 1.5
          a = a + 1
       end

1 Like

I really think what you want is:

function calculatemything(a)
   for i in 1:10
      ...
   end
  return stuff
end

calculatemything(1)

I would strongly advice to put your loop into a function, as @fbanning suggested, unless you are looping over very few elements.
The reason for this is that global variables are not type-stable and therefore you are getting bad performance - see the very first performance tip in https://docs.julialang.org/en/v1/manual/performance-tips/

2 Likes