Julia does not yet have constant-type globals

question

#1

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

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


#3

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


#4

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


#5

Yes. And that seems to be the solution that’s settled on:

Issue:


#6

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


#7

Someone has to implement it.


#8

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


#9

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


#10

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


#11

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.


#12

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.


#13

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.


#14

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.


#15

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.


#16

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.


#17

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!


#18

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.