Apologies for a very basic question but: what does the current const not accomplish? I know it doesnāt force a variable to actually be constant, just that the type canāt be changed. But if itās an error to do something like const x = 3; x = 1.5, doesnāt that mean the compiler now knows x is always an Int?
(Or is it something like when x is redefined itās run-time checked to ensure that the type isnāt changing, in which caseā¦what does const do?)
It would also be useful to be able to write x::Int = 3 in global scope to indicate that x is not a constant value but that whatever value is assigned to x, it must be an Int. I should mention that this is currently a low priority since x::Int = 1 in global scope is currently an error, which means that the feature can be added post-1.0 without breaking anyoneās code. That doesnāt mean that we want the features, but thereās a lot of work to do for 1.0 right now.
In my opinion, a lot of the users of Julia are people that are not programmers and do not care about code style, like physicists or mathematicians running simulations. For the majority of these people, it is a lot easier to do prototyping code using global variables. Otherwise you are forced to think about a structure with functions and structs, which can be a distraction if your priority is to test an idea quick and dirty.
Thatās fine and all, but this isnāt really a style issue ā writing code with lots of mutable global state is inherently at odds with good performance. Avoiding global mutable state happens to also lead to better code, but thatās only a secondary beneficial effect. Of course, this is not accidental ā the reader of the code is in the same predicament whether they are a person or a compiler: global mutable state is hard to reason about and it makes programs difficult to understand.
It makes sense that in general avoiding global state is good practice, especially in production code. When prototyping, however, it can be useful (I think). What is the idiomatic way to define semi-constants that multiple functions depend on? By semi-constants I mean variables (typically numbers, but they donāt have to be) that a) lots of functions reference and b) whose value doesnāt change much, e.g. they would be constants in production code but in interactive usage, the user may wish to experiment with different values.
A silly non-realistic but hopefully illustrative example: suppose I am interested in the k-th largest and smallest elements of arrays. I might have functions like
k = 3
function get_kth_smallest(arr)
# reads global k
...
end
function get_kth_largest(arr)
# also reads global k
....
end
function analyze(arr)
k_smallest = get_kth_smallest(arr)
k_largest = get_kth_largest(arr)
...
end
In this case, especially for interactive use, itās convenient to rely on global k; the user knows that k changes iff the user changes it. But itās bad to make k be const, because then the user canāt experiment with changing k - the compiled functions wonāt update. The value k could be passed around as an argument to every function, but if many different functions rely on the value of k, then pretty much every function has to accept k as an argument so when that function calls another function, the value of k can be passed along. Thatās already ugly with a handful of functions and one constant, and gets really bad with many functions and many constants.
Is the trick to have each function accept default arguments referencing a non-const global variable, e.g. function get_kth_smallest(k = k)? That way within the function thereās no reference to global state? In some tiny benchmarks I did, this seems to have a large relative but negligible absolute performance cost relative to using const.
You may want to try different workflows to see how it suits you. I like to wrap code in a module then make changes, and run include(fname). Then, you can put const on k. The Atom/Juno editor can evaluate in a module which is nice for interactive use for code wrapped in a module. A lot of folks like that workflow. You can also try REPLinModule.jl for a similar effect at the REPL.
With Tim Holyās new Revise.jl, you donāt even need to wrap code in a module for most uses. Just edit/save, and itāll automatically pick up changes. Iām looking forward to using this more.
This is very likely how type-annotated globals will be implemented. I.e. when you write:
k::Int = 3
function set_k(kā²::Int)
global k = kā²
end
get_kth_smallest(arr) = arr[sortperm(arr)[k]]
it will actually be lowered to something very much like the above. As Iāve said elsewhere, this is a post-1.0 change because this is a feature which will not break any code and right now weāre focused on stabilizing the features we have, rather than introducing any new ones. Gotta leave some goodies for 1.1!
Slightly off-topic:
I found that one of the main reasons I wanted to use globals, especially when quickly prototyping was physical constants and other model parameters:
@with_kw struct Phys # edited typo here
g::Float64 = 9.81
rho_w = 1000.0
# ...
end
press(h,p::Phys) = p.rho_w*p.g*h
p = Phys()
press(10,p)
There is a bunch of other useful helpers in that package, such as unpacking of values from the structs. I found that the convenience provided with this package is enough to stop me using globals, even when prototyping.