Ambiguity on Vector{<:Abstract*}?

this may fit better in development than first steps, but before I bother the developers, let me ask the basics?

would it introduce any ambiguity if Vector{AbstractFloat} was interpreted as Vector{<:AbstractFloat}.

The former seems quite natural, and I have been bitten a few times by inadvertently forgetting the <: .

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.

6 Likes

No, because they are two distinct concepts, and you need a way to express both.

This comes up a lot. Probably would make a good blog post to explain the motivation in more detail

6 Likes

See, for example, this post: Problem with Complex{Rationals} - #7 by stevengj

1 Like

thanks. understood.

to understand most easily what would be lost by automatic recognition, can someone please give me an example of what call

function f(x::Vector{AbstractFloat}); 2; end

catches at the moment?

regards,

/iaw

It catches a Vector{AbstractFloat}, just like it says:

julia> x = Vector{AbstractFloat}(5)
5-element Array{AbstractFloat,1}:
 #undef
 #undef
 #undef
 #undef
 #undef

julia> f(x)
2
1 Like

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.

julia> x=AbstractFloat(3)
3.0

julia> typeof(x)
Float64

This was my misunderstanding.

No, an object cannot be of type AbstractFloat, because AbstractFloat is an abstract type — objects can only have concrete types.

If you have Vector{AbstractFloat}, each element is a concrete floating-point type, but the elements can of be different concrete types:

julia> a = AbstractFloat[1.0, 2.0f0, big(3.0)]
3-element Array{AbstractFloat,1}:
 1.0                                                                             
 2.0                                                                             
 3.000000000000000000000000000000000000000000000000000000000000000000000000000000

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.

5 Likes

yep. this is exactly what I had missed.

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.

See Is a simple, beginner style with named parameters and no unnecessary type annotations acceptable? and discussion.

5 Likes

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.