Why [1, 2, 3] is not a Vector{Number}?

Imagine what this would mean for working with an array a of type Vector{Float64}, for example:

  • Currently, this can be stored as a consecutive sequence of 8-byte chunks in memory, and if the compiler sees a[i] + 1.5 it knows that it can compile this into a load instruction and a floating-point addition.

  • If Float64 could be sub-typed, however, then the elements of a could be any subtypes of Float64 — they would have to be pointers to “boxes” containing a type tag (to say what type they really are) along with the actual data, and for an expression like a[i]+1.5 the compiler would need to generate code that chases the pointer, looks at the type tag, and does dynamic (runtime) dispatch to the correct + operation.

Moreover, dynamic dispatch in a multiple-dispatch language like Julia is even more expensive than dynamic dispatch in a single-dispatch (traditional OOP) language like C++ (where they have vtables), so devirtualization (figuring what what methods to call at compile-time) is a critical optimization, to the point where nearly all optimized Julia code is devirtualized in practice. Making concrete types final (non-subtypable) gives the compiler many more chances for devirtualization. Even in C++, people often recommend final classes to enable devirtualization.

Because multiple dispatch allows you to add new functions to existing types (see also this discussion and this talk on the expression problem of OOP), there is much less need for subtyping of concrete types in Julia than there is in OOP languages. Because of that, it was a practical choice to make all concrete types “final”, reaping vast benefits in performance as well as simplifying the language.

14 Likes