Loop Scope works differently for Int and Arrays. Why?

Loop Scope works differently for Int and Arrays. Why?

Here is an example. Array w is changed inside the loop, but variable w isn’t.

w=zeros(2)
println(w)
for i=1:2
w[i]=i
end
println(w)

q=0
println(q)
for i=1:2
q=i
end
println(q)

Long story, but if you are just getting started the easiest way to deal with this is to:

  • Use Jupyter for exploratory script-style code. It doesn’t have this issue
  • Wrap anything that uses for loops in a function otherwise, which also doesn’t have this issue.

If you follow that advice, you will never see this problem again. Hopefully at some point things will be a little more intuitive. If you want more context, there are a lot of threads on discourse to discuss.

No, this is not the scoping issue, this about assignment vs mutation. Or maybe it’s both?

There’s two things going on here. The first is that for the array case, you’re modifying the existing array, not rebinding w to point to a new array each loop iteration (which is what’s happening in the integer case). So there’s a different kind of operation going on between the two examples (modifying an object vs rebinding a variable to point at a new object).

The second thing is that for loops introduce a local scope, which means variables you bind in that scope are by default local variables, and don’t overwrite global variables such as q. To avoid this, in the loop you can declare global q in the loop to say that you want your use of q to refer to the global variable when you rebind it. Another option (because using global variables is slow) is to put the for loop in a function f and use q = f(q).

(Note also Jupyter uses a package to emulate top-level for loops being in global scope; if you don’t know that it can be confusing when you see different behavior between Jupyter and the repl).

Edit: I started writing this before seeing the other replies, but I think we’re all saying more-or-less the same thing

2 Likes

They are often connected in practice. Although are you saying that I am wrong, and that the behavior is the same in the REPL and Jupyter in this case?

The reason the array case is different than the integer case is assignment vs mutation; The reason that difference causes a different behavior here is the local scope of the for loops treats those two things differently (allows mutation of global variables, but uses local variables on assignment)

Thank you.
How Can I modify integer like array, not to rebind it

Integers are immutable, meaning they can’t be modified, but one option is to put the integer in a mutable container and use that. For example, q = Ref(1) stores 1 in a container, and the value can be accessed or changed by doing q[] or q[] = 2, etc. This is just like in the array case where you index to modify the array.

Edit: but often you don’t really need that, and it’s better to just use functions like I wrote in the last post.

It may depend on what you are trying to do. If you are just trying to code an algorithm up along the lines of what you have above, then follow my advice: use jupyter or put it in a function. You won’t need to worry about this issue.

If you “really” want to modify it in place, then you can use that fancy Ref stuff… but it is unlikelky that you need it.

My knee-jerk reaction was that you were wrong, but then I realized it was both things.

Thank you for good explanation.
One more, please.

So why inside the function there are other rules.
I hope function it’s like small code, or main code is like the main function.
An example result will be “2” no “10”.

q=0
println(q)
for i=1:2
q=i
end
println(q)
function foo(arg)
arg=10
for i=1:2
arg=i
end
return arg
end
println(foo(q))

An intuitive answer to this probably depends on what other programming language you are coming from, and your level of familiarity with scoping rules in general.

This is an issue that only hits you (> Julia 0.6 as it didn’t use to be an issue) if you write top level code with for loops outside of jupyter. If you don’t do that, then the Julia scoping rules are very intuitive.

Thank you for Ref()
I got is, I know it from C++. I would like to use Ref in future

This is almost never what you want to do. It would be like only modifying integers through pointers, which leaves out all sorts of optimizations and clutters your code.

Just put your code in a function.

Functions introduce local scopes just like for loops, and local scopes inherit all the variables of parent local scopes (see here: https://pkg.julialang.org/docs/julia/THl1k/1.1.1/manual/variables-and-scoping.html#Local-Scope-1). I think the manual does a pretty good job explaining it all, but there’s a lot of details.

Thank you.
I got it.

Although I prefer the local scope, don’t have the right to modify parent scope, since why do I need a scope inside the scope.

1 Like

Actually, you can modify the parent scope, unless the parent scope is module-global.
To ensure everything is wrapped into a local scope, use let:

julia> q=0
0

julia> println(q)
0

julia> for i=1:2
       q=i
       end

julia> println(q)
0

julia> let q=0
       println(q)
       for i=1:2 q=i end
       println(q)
       end
0
2
1 Like