Julia’s type parameters are “invariant.” Your suggestion would make them “covariant.” In short: that’s a major design change.
One place invariance is crucial is that Julia uses a different memory layout for Vector{Any}, Vector{Union{Int, Missing}} and Vector{Int}, with the latter two being much more optimized. The only way this could possibly work is if Julia can reason about those three types independently from each other. The same goes for custom parametric structs.
This is a whole big thing in computer science. This wiki article is long, but it’s actually not completely terrible—especially if you jump straight to the Array section.
thanks. this explains it. I did not realize that an object could be of type AbstractFloat. I thought AbstractFloat was more like an enumeration of possible types of an object, but one that could not be the type of an existing object.
Physically, this means that the elements of a must be stored as pointers to “boxes” that contain the value along with a type tag, in order to store the fact that a[1] is a Float64, a[2] is a Float32, and a[3] is a BigFloat. Such an array is very flexible in that it can store different types, but processing it is inherently slow. For example, if you compute sum(a), when every element is added it needs to (1) chase an extra pointer to the box, (2) look at the type tag, (3) dispatch at runtime to the correct + function for that type of AbstractFloat, and (4) store the result of the summation in a “box” as well because the type of the result can only be determined at runtime too.
In contrast, for a Vector{Float64}, every element is of the same type, so the Float64 type tag can be attached to the array as a whole, not the individual elements. Hence the elements can be stored as 64-bit values one after the other consecutively in memory. Something like sum just loads these values one at a time into a register and sums each with one machine instruction that was determined at compile-time, not at runtime.
if Julia had “compiler warning” settings, it would be good if it emitted a notice [<;warning ;-)) that the use of Vector{...} is much rarer than Vector{<:...} (where … are a couple of common types, like AbstractFloat, Real, etc.). This is probably a common beginner’s error, and would reduce novice pain.
I have to the conclusion that for beginners, the less type annotations the better. Let the compiler figure things out on its own, and let library writers worry about making sure all of the mathematics on the generic types are sound.
not my conclusion. it is less confusing until there is a bug, and then it is more confusing. of course, it will also create more bugs, too. then again, it means that novices understand less than they may need to (although often they never need to understand it, in which case we should have indeed spared them.)
it is a tradeoff, where reasonable people come to different conclusions. De gustibus non est disputandum. I am just glad that both are possible.