Dear all:
I wonder why is the UndefVarError, and, is the Suggestion accurate?
julia> function test(C)
count = 1
v = Function[]
while count < C
local item
f = function()
println("I'm ", item[])
end
push!(v, f)
if count == 1
item = [count]
else
item[] += 1
end
map(x -> x(), v)
count += 1
end
end
julia> test(2)
I'm 1
julia> test(3)
I'm 1
ERROR: UndefVarError: `item` not defined in local scope
Suggestion: check for an assignment to a local variable that shadows a global of the same name.
Stacktrace:
[1] test(C::Int64)
@ Main .\REPL[1]:13
[2] top-level scope
@ REPL[3]:1
How to analyze the item, is it a local variable in one pass, or is it a local variable in all C-1 passes.
(by “pass” I mean one go from top to bottom within the while).
It always helps to form a minimal reproducible example. Get rid of all unnecessary lines so you can focus on the underlying issue.
Here’s one example:
julia> function test()
for i in 1:2
local item
if i == 1
item = [i]
else
item[] += 1
end
end
end
test (generic function with 2 methods)
julia> test()
ERROR: UndefVarError: `item` not defined in local scope
Suggestion: check for an assignment to a local variable that shadows a global of the same name.
Stacktrace:
One the first iteration, you declare local item and initialise it.
On the second iteration, you declare a new local item, and then attempt item[] += 1, which tries to read item[]. But because of local item, the variable does not persist between iterations and so it doesn’t exist in iteration 2.
Change where you put the local:
julia> function test()
local item
for i in 1:2
if i == 1
item = [i]
else
item[] += 1
end
end
return item
end
test (generic function with 2 methods)
julia> test()
1-element Vector{Int64}:
2
There is no ambiguity in this case—the three a’s are the same name. This case is not confusing to users.
This is not the case for while/for, people needs to understand that if you have 10 iterations, then the local (user-specified) names in the loop body are not identical.
(My thoughts are also updated in the Github link above)
Note that the local scope of a for loop body is no different from the local scope of an inner function. This means that we could rewrite this example so that the loop body is implemented as a call to an inner helper function and it behaves the same way:
This may be theoretically equivalent. But would anyone write this conversion in practice? e.g. due to performance issues? Is the plain loop faster? I’m not very clear about this.
Anyway, I think the stress is on learning a skill to producing the predictable final result.
To be honest I’m having difficulty understanding some parts of that manual, e.g.
begin
local c
c += 1
end
It says begin block will not introduce a scope. But why is here a local?
Anyway, I have no chance using begin, so I would skip it.
how many different entities nameditem exist in the following code
function test()
item = 0
for i in 1:2
local item
if i == 1
item = [i]
else
item[] += 1
end
end
end
test()
It is possible for him to answer: Oh, the function test introduce a local scope, there is one outer item being associated to 0. Oh, I also see the for loop introduce an inner local scope and there is also an local item inside. So if there are no global name item elsewhere, the conclusion is:
there are 2 different entities named item here, both are non-global.
(Yes, surely—I asked ChatGPT, who answered 2.)
But in reality, the proper answer is 3, given Oscar’s answer in #2 post.
Anyway, I think my comprehension still needs to be advanced—therefore I give up the Github actions, giving way to people who are more experienced.
Edit: I took a closer look at ChatGPT’s answer, and find it’s even worse
An if you call the function twice, it’s … 6? Or do you want repeated executions of the scope in the loop body to count multiple times, but not repeated execution of the scope in the function? What about repeated execution of let? Are there 10 scopes with b in this function?
function f()
i = 1
a = 0
@label L
let b = i^2
a += b
end
i += 1
if i < 10
@goto L
end
return a
end
Perhaps in the sense of dynamic scopes, but not the lexical scopes which are used in julia.
The preceding description is pretty much all there is to it:
If a top-level expression contains a variable declaration with keyword local , then that variable is not accessible outside that expression.
So while a begin block doesn’t make a local scope, a local declaration makes a variable only last in that scope, similar to how uncaptured local variables only last in their home local scope. The Manual currently does not describe this as a local variable, whether directly or indirectly. However, it does currently behave similarly to a local variable, even if the capture and boxing are implemented differently for global functions.
julia> begin
x = 1 # normal global
f() = x
print(f(), ", ")
let # rules say this would reassign outer locals
x = 10
end
local y = 2
g() = y
println(g())
let
y = 20
end
nothing
end
1, 2
julia> f(), g() # local y got reassigned
(1, 20)
I have always found scopes in Julia confusing as a non-programmer and a noob, but saying that a for/while loop introduces a new scope on each iteration cannot be true, or did I completely misunderstand your PR?
…while defines a distinct [local scope](@ref scope-of-variables) for each of its iteration…
In your original code you are just redefining ‘item’ with local item at each iteration. I don’t see how this has anything to do with scopes, it is an error of the programmer.
It is like writing
for i in 1:n
x = 0
x = x+1
end
and then wondering why x at the end of the loop is always 1 instead of n.
As far as I understand, writing local var does not define a new (uninitialised) variable var, it just declares that all appearances of var in the current scope refer to a different variable than any var in the outer scope. In particular, you can have
julia> function f()
local i
i = 2
local i # Does not create a new variable i; i below remains at 2
println(i)
end;
julia> f()
2
and
julia> function g()
i = 1
for j = 1:1
i = -1 # Not the same i as above
local i # even though we only declare it here
end
return i # Still 1
end;
julia> g()
1
The concept “a scope being local” and “a variable being local” are different. I regret opening that.
No this situation is not alike in any way. There is no confusion in your case because you are re-associating x to 0 every iteration and clearly the x in x = x+1 is identical to the x in x = 0, regardless of whether x being local or global.
In retrospect, I think julia’s style choice is reasonable. And I now accept it.
I don’t mind at all typing some local keyword when it’s necessary, because functions itself should be short—otherwise it’s the user writing cumbersome code.
With that just said, I’ve just spend 3 hours debugging a weird ERROR:
julia> f()
ERROR: BoundsError: attempt to access Float64 at index [2]
Stacktrace:
[1] indexed_iterate(I::Float64, i::Int64, state::Nothing)
@ Base .\tuple.jl:168
finally finding that the problematic line is here:
function f()
...
local c = s.a, b = 4.0
end
Oh no. I guess I won’t write = in conjunction with local any longer (which was let’s syntax). It’s a bad habit.