Unexpected behavior of global variables depending on function read/write behavior

Hi,

I have been experimenting with the behavior of global variables in Julia.

I (perhaps incorrectly) believed the behavior was quite similar, if not exactly the same, as in Python.

Here’s some test code:

global x="x"
y="y"

function printtype()
    println("typeof(x)=$(typeof(x))")
    println("typeof(y)=$(typeof(y))")
end

function printxy()
    println("printxy: x=$(x)")
    println("printxy: y=$(y)")
end

function printxyglobal()
    global x
    global y
    println("printxyglobal: x=$(x)")
    println("printxyglobal: y=$(y)")
end

function changexy()
    println("changexy: x=$(x)") # added later
    if !@isdefined(x) # added later
        println("x is not defined") # added later
        println("x= $(x)") # added later
        println(x) # added later
    end # added later
    x = x == "x" ? "xx" : "x"
    y = y == "y" ? "yy" : "y"
    println("end of changexy") # added later
end

# FYI
function changexy_original()
    x = x == "x" ? "xx" : "x"
    y = y == "y" ? "yy" : "y"
end

function changexyglobal()
    global x
    global y
    x = x == "x" ? "xx" : "x"
    y = y == "y" ? "yy" : "y"
end

function changexytypeglobal()
    global x
    global y
    x = x == "x" ? 1 : "x"
    y = y == "y" ? 2 : "y"
end

println("printtype:")
printtype()

println("printxy:")
printxy()
println("printxyglobal:")
printxyglobal()

println("changexy:")
changexy()

println("printxy:")
printxy()
println("printxyglobal:")
printxyglobal()

println("changexyglobal:")
changexyglobal()

println("printxy:")
printxy()
println("printxyglobal:")
printxyglobal()

println("changexytypeglobal:")
changexytypeglobal()

println("printxy:")
printxy()
println("printxyglobal:")
printxyglobal()

println("done")

Here is the output:

$ julia global.jl
printtype:
typeof(x)=String
typeof(y)=String
printxy:
printxy: x=x
printxy: y=y
printxyglobal:
printxyglobal: x=x
printxyglobal: y=y
changexy:
ERROR: LoadError: UndefVarError: `x` not defined in local scope

If it wasn’t already obvious from running this code, what I find surprising is this:

  • printxy is able to read x and y without global (this is what I expect)
  • printxyglobal behaves the same as printxy (again, exactly as I expect)
  • changexy cannot read x! This appears to be in conflict with the behavior seen in printxy

The final bullet point is really the surprise. The first two are expected behaviors.

Is anyone able to help me understand why this happens?

I’m assuming I haven’t made a dumb mistake her or otherwise doing something obviously wrong?

It’s because you assign to x in changexy, without declaring it as a global. The compiler sees the assignment and determines it is a local variable. However, at the point where it is read, it has not been defined yet. It’s just a local name which is not referring to anything yet. It’s like

julia> f() = (local x; println(x))
f (generic function with 1 method)

julia> f()
ERROR: UndefVarError: `x` not defined in local scope

or, even

julia> f() = (println(x); local x)
4 Likes

The assignment in the function body treats the variable as a local variable by default. You need a global statement to override it. Python has the same rule:

>>> x = 1
>>> def foo():
...     print("1: ", x)
...     x = 2
...     print("2: ", x)
...
>>> foo()
Traceback (most recent call last):
  File "<python-input-2>", line 1, in <module>
    foo()
    ~~~^^
  File "<python-input-1>", line 2, in foo
    print("1: ", x)
                 ^
UnboundLocalError: cannot access local variable 'x' where it is not associated with a value
>>> def foo():
...     global x
...     print("1: ", x)
...     x = 2
...     print("2: ", x)
...
>>> foo()
1:  1
2:  2
>>> x
2

…though it’s worth pointing out there are other differences in rules. One difference is Python errors if the statement isn’t ahead of all the working code. Julia’s global/local statements apply to the entire scope it’s written in, regardless of which line except the final returning line where global without assignment errors. It’s better to not expect Julia to work like Python despite the relative similarities among other languages.

4 Likes

I suspected it was something like that. Great! Thanks for confirming.

For anyone reading this in future, the issue appears to be that the following line does not make much sense.

x = x == "x" ? "xx" : "x"

First, the value of x is compared against "x". We have to ask the question, which x, or “what is x?” Since x has not been assigned to in the function, the answer is obvious. This refers to the global x, which exists outside of the function.

Next, having evaluated x == "x" ? "xx" : "x", we can assign the result to x.

Again, we ask the question, to which x does this refer?

Since we are writing to a variable x and this variable has not been declared as global x at the beginning of our function, this x must be a new local variable x.

However, this statement conflicts with the previous statement where we concluded x was the global variable x.

Hence, this code is actually nonsensical.

1 Like

Just for fun, note that you can write

julia> global x = "x"
"x"

julia> function f()
           let x = x
               x = x == "x" ? "xx" : "x"
               println(x)
           end
       end
f (generic function with 1 method)

julia> f()
xx

julia> x
"x"

global doesn’t have to be at the beginning.

julia> x=4
4

julia> function bar()
         x = 7
         global x
         return x
       end
bar (generic function with 1 method)

julia> bar(); @show x;
x = 7

True, but it should be at the beginning of your function, to make it easier to understand.

2 Likes

Of course. I just wanted to make it clear “For anyone reading this in future”.

3 Likes

Your comment doesn’t make it clear at all. Quite the opposite. You’re telling people to do something that makes their code less clear.

For context, I commented something elsewhere a short time ago where I clarified something and started off my post with

For anyone reading this in future

Hence why he’s quoted that.

Is there really any need for this kind of behavior?

Perhaps I’m being too terse. You posted a summary regarding scoping that included a small inaccuracy. There have been threads here before because of confusion from the position- independent nature of global. My intention was not to encourage moving global later but rather to warn that a later global can introduce unexpected behavior, thus the quote. I did not mean any offense and I’m sorry you took it that way.

1 Like

Seems pretty obvious that you were intending to mock me with your previous comment. Not sure how else someone would be expected to interpret that.

I quoted you to indicate my intention of the previous post was to provide more information and hopefully dispel confusion for your future readers (intentionally not quoting you again), and not that I was suggesting a late global as you had interpreted it. I’ve already offered you an apology.

3 Likes

I don’t think this was meant as a quote of @world-peace from another thread at all. This is a pretty standard expression that lots of people use on internet forums.

If @world-peace has the same thread in mind as I do, then their wording was actually

For the benefit of anyone reading this

which is not the same sentence that @Jeff_Emanuel used.

On this Discourse, please assume good intentions from the people you interact with, unless they have explicitly proved you wrong. I can’t recall observing mockery on this site, and I have no doubt @Jeff_Emanuel was genuinely trying to help.

2 Likes

I would assume it does refer to @world-peace 's

from this thread.

I don’t think it’s too far-fetched to interpret @Jeff_Emanuel 's

as slightly mocking, but equally it could be interpreted as tongue-in-cheek. The medium of text just doesn’t lend itself to these kind of nuances (unless you add emoticons or stuff like /s.).

Anyway, @Jeff_Emanuel clarified that there was no ill-intent and apologised, so let’s just leave it at that and move on.

5 Likes

@eldee perhaps you’re right. I might have over-interpreted the “elsewhere” here, thinking that it referred to another thread.
In any case, that’s exactly the problem: we should all spend less time interpreting what everyone’s intentions are, and more time solving each other’s problems :slight_smile:

5 Likes

I think it’s reasonable that I should be unhappy about this.

From my point of view, it appears that there is a double standard being applied here. I do not know why.

Previously my account has been banned for the most minor of faux-pas, but in this case it seems that for some reason not known to me, a technicality is being used to enable someone to mock someone else on the forums.

If I had posted this, I am pretty sure I would get banned again.

Previous decisions by the moderation are not the subject here. We have already discussed those at length with you, both in public and in private. The same standards apply to everyone.

You are entitled to your reactions. Meanwhile, @Jeff_Emanuel’s intent has been clarified and an olive branch has been extended. I suggest we collectively move on.

7 Likes