Julia 1.7 and below do not have constant-type globals. Julia 1.8 implemented typed globals

Quoting the documentation (http://docs.julialang.org/en/latest/manual/types.html#Type-Declarations-1):

Currently, type declarations cannot be used in global scope, e.g. in the REPL, since Julia does not yet have constant-type globals.

Why? Are there plans to support global type declarations some day?

2 Likes

You can do that pretty easily with a constant mutable type Refs are commonly used so itā€™s not a high priority.

1 Like

Still, we should really have this feature for 1.0 ā€“ the Ref pattern is usable but rather awkward.

4 Likes

In my understanding, if we have globals with constant type, the performance loss of using globals disappears, correct?

Yes. And that seems to be the solution thatā€™s settled on:

Issue:

I really donā€™t understand what is the issue with simply allowing to state the type of gobal variables.

Someone has to implement it.

2 Likes

Ah, ok. I thought there was a deeper issue.

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?)

Any function compiled when x was 3 might still use the old value if you redefine the const (which is why you also get a warning).

3 Likes

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.

2 Likes

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.

1 Like

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.

To illustrate @yuyichaoā€™s point upthread, you can do

const kref=Ref{Int}(3)

function set_k(k::Int)
  kref[]=k
end

get_kth_smallest(arr) = arr[sortperm(arr)[kref[]]]

and the compiler will handle use of kref[] very efficiently. This is useful in Main (so, at the REPL) or any other module.

2 Likes

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!

4 Likes

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:

const g = 9.81
const rho_w = 1000.0
...
press(h) = rho_w*g*h`
...

Instead now I use https://github.com/mauro3/Parameters.jl:

@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.

3 Likes

Since Julia 1.8, Julia does now have constant-type globals: