5

New to Julia and hence, likely a basic question.

x = 1
function someFn()
   print(x)
   x = 3 
end

This throws an error at print(x) because global x inside is not seen inside the function. This makes sense.

x = [1,2]
function someFn()
   print(x)
   x[1] = 4
end
print(x)

Here print(x) is able to see the global array x and x[1]=4 changes value of global x globally. A similar behavior is observed when I make x a dictionary.

Three questions

  1. Is this behavior of variable / array and its scope inside a function consistent how Julia should work?
  2. When x is an array, it is visible inside the function even without passing a reference as an input to function. Is this correct?
  3. Also, changing a value of an entry in the array is reflected globally. Is this so because Julia treats array x as a reference everywhere?
Sundar
  • 53
  • 3
  • 2
    Yes, yes, and pretty much. Note that there's no difference between a global name that is used for an array and a global name that is used for a function (like `print`). https://docs.julialang.org/en/v1/manual/variables-and-scoping/#scope-of-variables-1 – mbauman May 18 '20 at 16:06
  • Thanks Matt for your comment. Highlighted reference doesn't capture difference between variable and other data types like array / dictionary / function (as you mention). Out of curiosity, is there a reference that captures the essence of my question in Julia documentation? I had tried looking but failed to spot one. Appreciate your help. – Sundar May 18 '20 at 17:51

1 Answers1

3

Let me comment on my understanding of the issue.

First, variable names point to values. The most basic way to make a binding between the variable name and the value is assignment of the form:

variable_name = value

Note that it is crucial that on the left hand side of = the only thing that is present is variable name and nothing else.

(side note: apart from = there are other ways to make a binding, e.g. += etc. or function definitions; but let us focus here on the core of the issue)

Now if you see = this does not mean that it is an assignment that creates a new binding. In particular:

variable_name[index] = value

is not an assignment operation (that creates a new binding) but setindex! operation. Writing variable_name[index] = value is the same as writing setindex!(variable_name, value, index.

(side note: this is true except for some corner cases like e.g. handling of begin or end, handling by @view macro etc., but again this is not crucial in the discussion)

Now to directly answer your questions given these comments:

Is this behavior of variable / array and its scope inside a function consistent how Julia should work?

Yes. But note that you get an error in:

x = 1
function someFn()
   print(x)
   x = 3 
end

because Julia knows that x = 3 is present in the body of the function which makes Julia aware that x is a local variable even before it is bound to the value (a variable is either local or global).

Here is a more extreme example of the situation:

julia> x = 10
10

julia> function f()
       if true
           println(x)
       else
           x = 5
       end
       end
f (generic function with 1 method)

julia> f()
ERROR: UndefVarError: x not defined

although we know that the branch after else will never executed still it makes variable x to be local.

When x is an array, it is visible inside the function even without passing a reference as an input to function. Is this correct?

It is visible beause x[1] = 4 does not create a local variable named x but is simply a call to setindex! function. So there is no x variable defined locally, therefore a global variable x is used.

Also, changing a value of an entry in the array is reflected globally. Is this so because Julia treats array x as a reference everywhere?

Here be sure to remember that x is not an array. It is a variable that is bound to point to an array. Then, again, x[1] = 4 is the same as if you have written setindex!(x, 4, 1) so you simply call a function on a value (an array in this case) that a variable x is bound to. And this works, because scoping rules in Julia say that if there is no local variable of a given name in the scope Julia searches global scope for this name.

Finally let me comment that using global variables in Julia is discouraged as this is slow. So while this discussion is highly relevant to understand how Julia works, in practice you almost never need to know this (you will use local variables 99.99% of the time, and if you use global variables better make them const).

Bogumił Kamiński
  • 66,844
  • 3
  • 80
  • 107
  • Thanks for your detailed answer that clarifies quite a lot. I would like further clarification (if possible) on your final comment. I understand that using global is discouraged. Now let me relate to what I am trying to build: I am building a system with several sub-units that talk to each other and report their status to one another. I have packaged states of these sub-units in separate dictionaries. With the aim of getting around global, would you recommend I send states of these sub-units as inputs to the many functions I call? – Sundar May 20 '20 at 00:53
  • 1
    Yes - either pass the state as inputs or make these dicts `const`. I personally would opt for passing variables - as it is usually clearer what is going on (inside the function you have 100% clarity what it works on - only its arguments), but using a `const` is also a valid approach and is sometimes used (usually to indicate some global state of the module). – Bogumił Kamiński May 20 '20 at 06:23