Scopes: accessing a global variable hidden by a local of the same name

I’m not exactly “new to Julia” but I thought this would belong here.

How do you access a global variable hidden by a local variable of the same name? Reading the documentation (see the bottom of this message), I got the impression that global x was the way, but I found that it’s an error once the global x is hidden by the local one.

By just a guess, I discovered that Main.x is accessible. So, is this the “official” way? Or is there a standard way to refer to the outer scope? Here is a sample code:

x = 123
function func(x)
  @show x
  x = "hello"
  @show x
#  global x += 321 # -> MethodError: no method matching +(::String, ::Int64)
  Main.x += 321
  @show x
  @show Main.x
end
func(3.14)

[Now, please don’t mention 1) that using the same name is a bad practice or 2) that modifying a global variable is a bad design. I know that this is a bad practice and it’s a bad design in general. But, I have a legitimate use for this, but to describe it would double the length of this message. :slight_smile: So, please assume that I know what I’m doing. :slight_smile: ]

https://docs.julialang.org/en/v1/manual/variables-and-scoping/

1 Like

If you uncomment that line in your example, the global x is not hidden. The global x declaration does not occur in order at runtime, it applies across the whole scope at parse-time, so what happened was you reassigned the global x at x = "hello", then the + fails. You can check the global x after the call.

The difference between declaring global x and Main.x is that the former accesses the enclosing module but the latter accesses the specified module Main. You could try @__MODULE__ to expand to the enclosing module. You’re restricted from assigning variables in arbitrarily specified modules, so you’ll need to work around that with @eval(@__MODULE__, x+=321) @eval x+=321.

1 Like

That only works if the global variable is in the Main module, e.g. if you are running the code in the REPL. It won’t work in any other module. But if you know what module Foo the global symbol is defined in, you can reference it as Foo.x.

The more common solution is to rename your local variables so that they don’t conflict with any global symbols you want to access. For example, if you want to call the sum function, but also have a local variable named sum, you can either call sum(...) via Base.sum(...) or rename your local variable (e.g. to s).

If you are getting these local variables by code generation / metaprogramming (which you should rarely do), then you need to be careful of “hygiene” — don’t generate variable names like x, but instead use gensym() or similar to generate unique local-variable names. If the generated local-variable names come from an external source pass them through gensym to hygienize them. (A Julia macro does this for you automatically.)

By not including this context, you have probably doubled the work of the people trying to respond to you, because now we have to guess at things like “where does the conflict come from?”, “why can’t you just rename your local variable?”, and “do you know the module where the global symbol is defined?” in order to answer your question in several different scenarios.

6 Likes

If I understand the docstring correctly, passing @__MODULE__ to @eval is redundant; this is already the default behavior:

  eval(expr)

  Evaluate an expression in the global scope of the containing module. [...]

So the easiest solution is perhaps just a plain @eval:

julia> x = 123
123

julia> function func(x)
         @show x
         x = "hello"
         @show x
         @eval x += 321
         @show x
         @show @eval x
       end
func (generic function with 1 method)

julia> func(3.14)
x = 3.14
x = "hello"
x = "hello"
#= REPL[2]:7 =# @eval(x) = 444
444

julia> module A
           x = 456
           function func(x)
               @show x
               x = "hello"
               @show x
               @eval x += 321
               @show x
               @show @eval x
           end
       end
Main.A

julia> A.func(3.14)
x = 3.14
x = "hello"
x = "hello"
#= REPL[4]:9 =# @eval(x) = 777
777
2 Likes

@eval expands to Core.eval(mod, expr), not the module-wise eval(expr), but you’re right that @eval defaults to the enclosing scope the same way the module-wise eval runs Core.eval in the enclosing scope, idk why it slipped my mind.

1 Like

Thank you everybody for the helpful discussion. I know understand that either eval or Modulename.x is the way to go.

Also, I understand it correctly, global x hides the local x entirely within the scope. So, a new question, purely out of curiosity without any practical use, is: How can you access the local x?

x = 123
function func(x)
  global x
  # Are you able to get the value
  # of the parameter to this function here in this scope?
end

By the way

Of course! That’s what I (and everybody) do 99% of the time. I don’t use the same name without a reason. As I said in my initial message, to explain the scenario with a complete context would take a lot of space, but a shorter version of explanation is this:

module Mymod
soundspeed = 340.0 # m/s, default value
meantemp = 20.0 # ℃, default value
function set_pars(; soundspeed = nothing, meantemp = nothing)
  (! isnothing(soundspeed)) && Mymod.soundspeed = soundspeed
  (! isnothing(meantemp)) && Mymod.meantemp = meantemp
end
# . . . some functions to export . . .
end

# --- main program
import Mymod
Mymod.set_pars(soundspeed = 300.0)

Because these names are standard throughout the project, there is no motivation to come up with variant names. You could argue that

soundspeed_ = 340.0
meantemp_ = 20.0
function func(; soundspeed = nothing, meantemp = nothing)
  (! isnothing(soundspeed)) && soundspeed_ = soundspeed
  . . .

would be better, but I don’t necessarily agree with that, because the first code is perfectly clear and my modules often start out as a stand-alone program: I turn a program into a module when the functions in it turn out to be useful in other programs. In such a case, I want to minimize the modification to it.

Whether we should use global variables like that is another issue. When two parallel threads use the same module and try to modify the same global variables . . . I have no experience in parallel programming and I don’t think my little programs and modules will be used in parallelized programs.

For completeness, I just realized there are functions getglobal and setglobal!, which also accomplish what you asked for. See docs for setglobal! here: Essentials · The Julia Language. But note the final comment:

Users will typically access this functionality through the setproperty! function or corresponding syntax (i.e. module.name = x) instead, so this is intended only for very specific use cases.

Sounds like Mymod.soundspeed is the sanctioned way.

This can be written just as:

module Mymod
soundspeed = 340.0 # m/s, default value
meantemp = 20.0 # ℃, default value
function set_pars(; soundspeed = soundspeed, meantemp = meantemp)

And you don´t need to mess around with any of that, with the additional important advantage of not using global variables in your functions anymore.

You could rename the default parameters to default_soundspeed and default_meantemp to use set_pars(; soundspeed=default_soundspeed...), and it would be very clear.

Maybe useful, you can also put all parameters in a struct with default values, such as:

julia> @kwdef struct Parameters
           soundspeed::Float64 = 1.0
           meantemp::Float64 = 300.0
       end
Parameters

julia> function f(; pars = Parameters())
           return pars.soundspeed, pars.meantemp
       end
f (generic function with 1 method)

julia> f()
(1.0, 300.0)

julia> f(;pars = Parameters(meantemp=500.0))
(1.0, 500.0)

I think it is a perfectly good advice that you should never use global variables, ever. Before giving up and using one, think ten times. There are not many situations in which the code without them are worst.

1 Like

You don’t, global x basically erases its existence in its scope. You are aware that this is considered “bad practice” so it shouldn’t be surprising that difficult restrictions show up.

It’s standard practice to vary the names so they can coexist without conflict. I’d keep the unembellished name for the global variables accessible to all enclosing scopes, save soundspeed_, soundspeed1, etc for function bodies.

Interesting, they changed this in v1.9, setproperty! on modules used to call setfield! to error.

I don’t know about “never”, after all base Julia has a few like pi. It’s more like when you don’t have the excuse of mathematical or physical constants, pass data through arguments as much as possible, then there’s alternate ways for outer variables to make it into functions with untyped globals dead last on the list.

2 Likes

Yes, never is an exaggeration, but even physical constants can change units or one may want to vary them to make tests, and having them as parameters ends up being practical.

All these suggestions are far more complicated than what I’d do: create a separate scope.

julia> x = 123
123

julia> function func(x)
         @show x
         x = "hello"
         @show x
         let # create a separate scope!
           @show x # because we define x as global in this scope (yes, even "after" this line), this is the global
           global x += 321
           @show x
         end
         @show x
       end
func (generic function with 1 method)

julia> func(pi)
x = π
x = "hello"
x = 123
x = 444
x = "hello"
"hello"

Note that let blocks evaluate to the result of their last expression, so in this way you can simply “exfiltrate” the global back to your original scope more succinctly:

julia> function local_times_global(x)
           x_global = let
               global x
               x
           end
           x * x_global
       end
local_times_global (generic function with 1 method)

julia> x = 123
123

julia> local_times_global(2)
246

But, yeah, I also agree that this isn’t ideal and will likely confuse even future-you and it’s better to avoid globals in the first place.

8 Likes

[quote=“lmiq, post:8, topic:107253”]

I didn’t foresee that this misunderstanding would happen!! I didn’t mean it that way.

When I wrote “default value”, I meant 20℃, not meantemp:

module Mymod
soundspeed = 340.0 # m/s, its default value is 340.0 m/s
meantemp = 20.0 # ℃, its default value is 20.0℃

"Function to change the module-global parameters"
function set_pars(; soundspeed = nothing, meantemp = nothing)
  (! isnothing(soundspeed)) && (Mymod.soundspeed = soundspeed)
  (! isnothing(meantemp)) && (Mymod.meantemp = meantemp)
end
# . . . some functions that depend on the module global parameters . . .
function printvalues()
  @show soundspeed
  @show meantemp
end
#. . . some more functions  . . .
end

# --- main program
Mymod.printvalues()
Mymod.set_pars(soundspeed = 222.2)
Mymod.printvalues()

end

# --- main program
import Mymod
Mymod.printvalues()
Mymod.set_pars(soundspeed = 300.0)
Mymod.printvalues()

As you can see (I hope), I want to provide a function to change those module-global variables.

. . . This was the very reason why I was very reluctant to say what I was doing! It takes a log of careful writing to clearly explain the context and intention and such a writing would be long.

I just asked what’s the standard way to modify a module-global variable when there is a local variable of the same name. It’s a very clear technical question, which should have a clear technical answer.

Yes! If I have to discuss the issue fully, I would mention that style.

There are several possibilities to manage parameters which are shared by a set of functions and which you need to change occasionally. Your method above is one of them. My method is another. There are cons and pros in each.

Then your values cannot be const values, and you’ll have probably enormous performance issues.

1 Like

Trust me that I’m aware of performance implications of non-const global variables. You are not able to estimate the size of the potential performance issues because you haven’t measured that of my code.

When my code is too slow, I optimize. That’s all. I don’t avoid global variables before measuring the performance.

No need to take it personally. More often than not unusual questions reveal bad design patterns, and we cannot know the background of everyone.

The performance issue is easily fixed improved by declaring the type of the global variables:

module Mymod
soundspeed::Float64 = 340.0 # m/s, its default value is 340.0 m/s
meantemp::Float64 = 20.0 # ℃, its default value is 20.0℃
...
end

Is it completely? I’ve seen many examples where typing the globals did not reproduce the performance of local variables, or global consts. I imagine that a typed global is still a heap allocated object, thus it can be optimized to the same level as an immutable local value. Correct me if I’m wrong.

2 Likes

The type stability definitely helps, but yeah there’s extra work in retrieving the value from the heap. I don’t know if dereferencing happens every time the variable is used or if the compiler tries to reduce it, I would need to learn how to read @code_llvm. There’s an extra layer to it where a typed global::T need more dereferencing than a const Ref{T}.

1 Like