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
# reads global k
# also reads global k
k_smallest = get_kth_smallest(arr)
k_largest = get_kth_largest(arr)
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
global k = k′
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!
@with_kw struct Phys # edited typo here
g::Float64 = 9.81
rho_w = 1000.0
press(h,p::Phys) = p.rho_w*p.g*h
p = Phys()
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.