[Closed] Scopes
I have a rather weird behaviour dealing with local variables. Take a look at this:
clearlistener()
-- explicit declaration of fooray and x, scope is global
global fooray = 23
-- implicit declaration of y, scope is global
y = 0
format "context level global: fooray= %
" fooray
for i=1 to 1 do
( -- top-level open parentheses - new scope is created
-- IF THE NEXT LINE IS COMMENTED OUT, fooray's global value will be correct in the end
--local baz = fooray + 1 -- uses the global fooray
format "context level 1: baz= %
" baz
-- declares 1st local fooray, hides global fooray
local fooray = y - 1
format "context level 1: fooray= %
" fooray
for i=1 to 1 do
(
-- a nested 2nd local fooray, hides 1st local
local fooray = 999
format " context level 2: fooray= %
" fooray
)
-- 1st local fooray
format "context level 1: fooray= %
" fooray
)
format "context level global: fooray= %
" fooray -- back to global fooray
Un-comment the line “local baz = fooray + 1” and try to explain why fooray gets wrong value in the end.
I have tried to study “Scope of variables” from MX reference, but this is something I just cant understand. :banghead:
I knew about this scope “behavior” and I think it’s just a bug and not a feature at all. The compiler has to throw a redefinition error in place:
-- declares 1st local fooray, hides global fooray
local fooray = y - 1
because this redefinition goes after the using of the global variable (fooray) in the same scope.
I can’t explain why this is happening but I can tell you that if add “::” before the variable it will force it to use the global scope and not mess it up:
local baz = ::fooray + 1
Have you tried adding a ( and ) englobing all your script? I always do that.
I found a similar situation a couple of years ago and asked Larry Minton about it.
His explanation kind of made sense, but he admitted that this could possibly be considered an error and should have been made to throw an error.
Here is a portion of his explanation:
You sort of have to separate the variable name from the variable storage to see how this makes sense.
When a variable name is first used in a scope, the name is placed in a hash table associated with the scope along with a Thunk that says how to get/set the variable. As we continue to parse the script, for each variable name we first look in this hash table to see if the name has already been used. If it has, we simply use the Thunk associated with it. If we don’t find it, we look in the hash tables for higher level scopes, until we reach the global scope. If we still don’t find it, we create a local variable slot that will hold the value of the variable, create a Thunk to get/set that local variable slot, and store the name and Thunk in the local scope’s hash table.
I guess in one sense for your example you could say we are creating a local variable that just points at the global one, but this isn’t a runtime local variable, rather it is a compile time local variable.
(The Bold emphasis is mine)
Long story short, the approach with prefixing the use of the global variable with :: is a good solution and if the local declaration was BEFORE the line where you accessed the global, you wouldn’t have any other choice anyway. So in the case of variable declarations, you shouldn’t think of the lines as executed in order (you cannot assume that at runtime the local fooray does not exist before the line that declares it because it has already been processed at compile time!). To make things more obvious, it is a much better practice to PRE-declare the variables in the beginning of the scope to remind yourself they will exist throughout the scope, like this:
clearlistener()
-- explicit declaration of fooray and x, scope is global
global fooray = 23
-- implicit declaration of y, scope is global
y = 0
format "context level global: fooray= %
" fooray
for i=1 to 1 do
( -- top-level open parentheses - new scope is created
local fooray
local baz = ::fooray + 1 -- uses the global fooray explicitly, no way around it, otherwise foorya would be local and undefined!
format "context level 1: baz= %
" baz
--uses the local fooray that was pre-declared:
fooray = y - 1
format "context level 1: fooray= %
" fooray
for i=1 to 1 do
(
-- a nested 2nd local fooray, hides 1st local
local fooray = 999
format " context level 2: fooray= %
" fooray
)
-- 1st local fooray
format "context level 1: fooray= %
" fooray
)
format "context level global: fooray= %
" fooray -- back to global fooray
I find this particular case quite confusing myself, so you are not alone.
Thank you all for your help, I knew this was tricky. The reason I asked about this was that next week or so I’ll be arranging a training session where variables and scopes are one topic. It would have been a bit embarrassing to talk about this without a faintest clue of what there really is in it.
Thank you Bobo for a really thorough explanation, I’ll archive this :applause:
Funny as I ran into this the first time in a class trying to explain scope, left me scratching my head and wondering if I really knew anything at all.