Variable scope in functions

I don’t know what to make of this…

In the follow snippet

const user_mappings, profile_mappings = Dict{Int,Int}(), Dict{Int,Int}()
const user_counter, profile_counter = 0, 0

function data_accessor() :: DataAccessor
  training_data = CSV.read(training_file)

  for row in eachrow(training_data)
    user_id, profile_id, rating = row[:UserID], row[:ProfileID], row[:Rating]
    @show user_counter
    haskey(user_mappings, user_id) || (user_mappings[user_id] = (user_counter += 1))
  end
end

a. the line
haskey(user_mappings, user_id) || (user_mappings[user_id] = (user_counter += 1))
causes an
ERROR: LoadError: UndefVarError: user_counter not defined
in the previous line, the one saying
@show user_counter.

b. if I comment out the line
haskey(user_mappings, user_id) || (user_mappings[user_id] = (user_counter += 1))
the loop works

c. if I remove the line
@show user_counter
but leave the line
haskey(user_mappings, user_id) || (user_mappings[user_id] = (user_counter += 1))
I still get the UndefVarError but at the line
haskey(user_mappings, user_id) || (user_mappings[user_id] = (user_counter += 1))

I have no idea why this happens :scream:

Julia Version 0.6.2
Commit d386e40c17 (2017-12-13 18:08 UTC)
Platform Info:
  OS: macOS (x86_64-apple-darwin14.5.0)
  CPU: Intel(R) Core(TM) i7-6820HQ CPU @ 2.70GHz
  WORD_SIZE: 64
  BLAS: libopenblas (USE64BITINT DYNAMIC_ARCH NO_AFFINITY Haswell)
  LAPACK: libopenblas64_
  LIBM: libopenlibm
  LLVM: libLLVM-3.9.1 (ORCJIT, skylake)

I think you need a global user_counter inside your function.

Yes, that’s what I did and it worked: global user_counter, profile_counter

I was now re-reading the scope rules in the manual but I still don’t quite get it… Maybe I need to re-re-read…

The statement user_counter += 1 assigns a value to user_counter thus it makes it a local variable. But then it doesn’t find user_counter anymore as it is not defined yet when evaluating user_counter + 1.

Is this what this paragraph explains?

for loops and Comprehensions have the following behavior: any new variables introduced in their body scopes are freshly allocated for each loop iteration. This is in contrast to while loops which reuse the variables for all iterations. Therefore these constructs are similar to while loops with let blocks inside

So before I attempt to update my variable, when I execute @show user_counter it’s actually not showing the user_counter variable from the outer scope, but rather a newly defined local variable? Which is set to 0?

I can’t quite find a clear explanation of the behavior…

This due to the scoping of functions, see https://docs.julialang.org/en/stable/manual/variables-and-scoping/#Hard-Local-Scope-1. The first example shows this. Also note that x += 1 is equivalent to writing x = x+1.

1 Like

Oh yes, you’re right - nice, now I understand. Thank you, very very useful!

Cool! Mark it as solved.

Note that the scoping rules may change in 0.7. In fact, I posted an example with an undefined-variable error in 0.6.1 similar to this one just a few days ago in discourse, and then I found that my example ran without an error in 0.7-nightly. The 0.7 documentation may not have caught up with this language change yet.