Const variables

I am sure this issue has been debated before.
By now, I have learned that all global variables must be constants. But I am less clear on global variables that are a function of other global variables, already defined as const. For example:

const var1 = 3
var2= 3 * var1

Must var2 be defined as const to get the maximum efficiency if var1 is used within a function? I would think not since its type is known to the compiler.

Thank you for any input.

1 Like

There’s nothing as “global variables that are a function of other global variables”. If var1 and var2 are both globals they are just that, there’s nothing that links the two.

Also, it’s not right to say “all global variables must be constants”. There are many good use of non-const globals. Just be careful if you want to use them where performance matters a lot.

4 Likes

Yes you should make var2 const as well if you want performance.

But ideally parameters should just be passed to each function that needs them.
If there are several then they can just be wrapped into a struct.

To expand on the previous answers:

Remember that in Julia the variables are only names bound to objects, not memory allocations, etc. When you declare a variable as const, you are just telling something about how that name shall be used.

So, it doesn’t matter what content you assign to var2 the first time. If you don’t declare it as const, there is nothing stopping you from assigning it a different value – maybe of a different type – later on.

5 Likes

Complementing even more what @heliosdrm said. Why you think var2 has its type know by the compiler, but var1 has not? Clearly, var2 can only have the type known if var1 has its type known.

Also, if var2 is not const, it can be changed completely at any moment, there is nothing making that its value must be always related to var1, if you need a global variable that changes accordingly every time some other global variable changes its value, then what you want is a function that computes some formula using global variables already available, and not a new global variable.

2 Likes

Not sure I follow. The type of var 1 is known: it is an integer, in this case. It can change. In my example, var2 is of type integer as well (i.e., the same type as var1. But as was already mentioned, after compilation, I could change the type of either variable, and the compiler must allow for that, thus decreasing efficiency.

The word const is really a misnomer. It is not that the variable is constant. It is the variable type that is constant, which is quite different. If I write

const a = 3::Int64

I am allowed to change the value of a without impacting the efficiency of the compiler, but I cannot change the type of a.

I thank you all for deepening my understanding. I will mark this problem solved, but please continue to write!

1 Like

Here is an example of why (1) global variables are bad, and (2) changing const variables does not do what one might expect:

julia> const a = 3
3

julia> f(x) = a * x
f (generic function with 1 method)

julia> f(10)
30

julia> a = 4
WARNING: redefinition of constant a. This may fail, cause incorrect answers, or produce other errors.
4

julia> f(10)
30
6 Likes

Ok, I have to study that. I do note that you have not recompiled the function. But this also means that if I use a function in a precompiled library and use a constant as one of its argument, I will get the wrong result when changing its value.

What happens if I defined instead a struct?

struct A
   a::Int64
end

and perform the same experiment. I will have to try some things out.

You still need to make an instance of that struct, which will presumably be a global variable, and you’re back to the same issue.

A simple solution is to make your “global variables” functions:

julia> f(x) = aa() * x
f (generic function with 1 method)

julia> f(10)
30

julia> aa() = 4
aa (generic function with 1 method)

julia> f(10)
40

The best solution is to just make f take a as an argument:

julia> aaa = 3
3

julia> f(aaa, 10)
30

julia> aaa = 4
4

julia> f(aaa, 10)
40

It’s so much easier to understand what’s going on now!

EDIT: Forgot to include the definition of f with two arguments:

f(a, x) = a * x
2 Likes

I just tried your example and was not able to reproduce your results. I tried this in REPL. I will now do so using the console.

@show const bb = 3
tst2(x) = 3*x
@show tst2(bb)
@show bb = 10
@show tst2(bb)

Here is the output, which behaves as I would expect, but differently than what you showed:

const bb = 3 = 3
tst2(bb) = 9
WARNING: redefining constant bb
bb = 10 = 10
tst2(bb) = 30

It behaves the same as he showed.
The bb in the function remains equal to 3. (EDIT: Sorry, as said below, there wasn’t even a bb in the function, it was hardcoded to 3.)

The one you use as an argument is updated.

Not sure I follow. The type of var 1 is known: it is an integer, in this case. It can change. In my example, var2 is of type integer as well (i.e., the same type as var1 . But as was already mentioned, after compilation, I could change the type of either variable, and the compiler must allow for that, thus decreasing efficiency.

You cannot change the type of a const.

julia> const V1 = 3
3

julia> const V1 = 5
WARNING: redefinition of constant V1. This may fail, cause incorrect answers, or produce other errors.
5

julia> const V1 = 5.0
ERROR: invalid redefinition of constant V1
Stacktrace:
 [1] top-level scope
   @ REPL[12]:1

To edit a specific method, type the corresponding number into the REPL and press Ctrl+Q

You didn’t include the global variable in the definition of tst2.

I am sure @dpsanders is much more knowledgeable than I am, but I find necessary to point that

The word const is really a misnomer. It is not that the variable is constant. It is the variable type that is constant, which is quite different.

Is just completely wrong. The binding is constant, not the type. What difference this make? Exactly what @dpsanders pointed out, but trying to explain more exhaustively I would say: if the binding var1 is constant, then you cannot assign a new value/object for var1 (even of the same type), you can change a mutable field inside that value, but you cannot ever replace the entire object inside var1 by another.

This is: if tmp1 is an array, then you can add, remove, and replace elements; but you cannot never replace the original array by a brand new array (for example, to “empty” the array temporarily while keeping the old array in an auxiliary variable, with the intent of putting it back after, without having to make any copies, a swap basically); it is not the type that is fixed, it is that initial object (which can have mutable fields) that is fixed there forever. If it is an Int like in your example, this means you cannot never change that value (because an Int has no mutable fields inside it you can change). That name will point to the same ‘memory position’/reference/value its entire existence.

4 Likes

I admit I am a beginner in Julia, which is why I am posting. Consider my duplication of the example provided by @dpsanders :

julia> const cc = 3
3

julia> const cc =5
WARNING: redefining constant cc
5
WARNING: redefining constant zzz

Note that the printout is different and my constant changed. I am using Julia 1.4.0-rc1.0. His example is copied from his message above (what version are you using, @dpsanders?)

julia> const V1 = 3
3

julia> const V1 = 5
WARNING: redefinition of constant V1. This may fail, cause incorrect answers, or produce other errors.
5

julia> const V1 = 5.0
ERROR: invalid redefinition of constant V1
Stacktrace:
 [1] top-level scope
   @ REPL[12]:1

To edit a specific method, type the corresponding number into the REPL and press Ctrl+Q

This is just a convenience to be used in the REPL instead of re-starting it. If you check the manual section on constants you will find:

Note that although sometimes possible, changing the value of a const variable is strongly discouraged, and is intended only for convenience during interactive use. Changing constants can cause various problems or unexpected behaviors. For instance, if a method references a constant and is already compiled before the constant is changed then it might keep using the old value: […]

In other words, the documentation gives no guarantee that it will work, and if it works there may be hidden problems. I really would have preferred that the behavior was just always throwing an error, as this ends up causing more headaches than just having to re-start the REPL, if someone does this without reading the manual.

2 Likes

Thank you. So the bottom line seems to be (as I have read in several places):
(this does make a greater impact when one is actually programming as opposed to simply reading.)

  1. do not use globals
  2. if you use globals, the preference is not to use const
  3. if efficiency is the desired outcome, use structs to store any global parameters
  4. the preference is to use immutable structs for maximum efficiency.
  5. Minimize the number of parameters defined outside a function.

So my remaining question is: if I use a struct, and initialize a structure in the global space, must I make it a const for maximum efficiency? Initializing in the global space is the only solution UNLESS one uses command-line arguments or inputs data from an input file (which is likely the best and more flexible approaches).

Did I miss anything important?

Thank you, again.

About your list, well, let me give you my more nuanced (and incredibly verbose) vision:

  1. Avoid globals. Specially for parameters. Just use a Dict, or create your own struct and pass it along (there some package solutions too). I use globals. I have a global timer in my module, so I can reset it, run a bunch of my methods, and then get how much was spent in each method (and each method called inside them). It is the best solution? No. But it is good enough and used only for debug, not for anything that will get in a paper.
  2. Considering that you will use globals. If you may need to change the entire object including its type: do not use const (and take the performance hit). If the type never changes, but you may need to change the whole object, not just a field, then use something like const var = Ref(10) for storing an Int, this creates a const binding to a reference (you need to use [] to access, or set, the Int value), so you get type-stability and gets to change the value safely. If you have a lot of these globals (type-fixed but with an object that may need to be wholly replaced), then instead of using Ref you may also declare your own mutable struct and use it with const and without the Ref (because the fields are mutable and can be changed safely). If you are completely sure you will never need to replace the entire object but only mutate a fields of it (or call mutating methods over it), then you use const (directly over the value, not wrapping inside a Ref).
  3. I do not think the main point of using structs is efficiency, but just that you are following (1) by doing this. Unless you are talking about using a global struct to store the values, I have addressed this in the point above. Basically, if const was used (one way or another) then the performance of the global variable will not be so much different from the passed-along parameters (but I welcome someone showing me wrong).
  4. If you have a grouping of fields that never change (or change very rarely), or they change but almost all fields in the same point (so it is almost the same cost of creating the struct again), then immutable structs are a good idea. If you need to consistently change the values of single fields, not so good. If you actually have a loop that needs to change a single field in that structure multiple times, then surely use a mutable struct.
  5. Yes. This is basically (1) again.

I addressed your question about structs and global space in (2) I think.

I am not sure if I understand the “is the only solution UNLESS” bit. This is a little crude, but why do you not just gives every method a last parameter called params that is either a Dict, a NamedTuple, or your own struct and each method pass it along for the methods it calls? Then you can have a main function like:


function main()
    # initialize your struct/dict/tuple here
    params = ...
    # call your method with such params
    my_method(..., params)
end

main()

And instead of changing global variables change the definition of params.

2 Likes

This really depends on your application. If the value is actually a constant, use const. If you want a mutable container, also use const.

Again, if you need mutable struct because you change those values, that is fine too.

If the function gets the value as a parameter, then it does not matter. Eg like this:

function foo(parameters)
    ...
end

par = parse_from_args(ARGS)
foo(par)
1 Like

Great answer!!! Yes, my answer was very crude in comparison. I will perhaps perform some experiments. My preference is not to use goals. Passing known types via function arguments is my preference.

Thanks again! I hope this helps others. I have nothing to add.

Gordon

That is good to know! However, wouldn’t that only be true if the parameter values had specified types? For example:

a = 35

function anyfunc(a::Int64)
   ...
end

would be as efficient as using const a since the function knows the type of its argument. However,

a = 35
function anyfunc(a)
...
end

would be less efficient, since the type of a could change and the compiler has to take that into account. Whatever the answer, I assume that the more information is known to the compiler, the higher likelihood of improved efficiency.