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.