Is there a good reason why `!(Tuple <: AbstractVector)`?

I’m assuming there is, but I don’t know what that might be. I’m pretty sure tuples implement everything in the AbstractVector interface except for size (and I see no reason why you couldn’t just do size(t::Tuple)=tuple(length(t))). Of course, the principle difference between vectors and tuples is that vectors are associated with a single element type, however tuples still seem to me like they are AbstractVector{<:Any}s. In fact (at least in v0.6) eltype is defined for tuples.

More often than not I find !(Tuple <: AbstractVector) to be an annoyance.

Tuples are the only covariant datatype in Julia, all others are invariant. Thus being its own type seems reasonable (necessary?).

1 Like

Some discussion here:

Tuples are immutable. The array interface assumes mutability (setindex!). If you put tuples into codes for AbstractArrays, you can’t expect it to work. Maybe there should be AbstractImmutableArray

It’s just fine to have AbstractArrays that don’t support setindex!. See: Ranges.

1 Like

True… that gets me all the time.

Woah, I did not realize until just now that Tuples are covariant. Now that is confusing, what is the reason for that?

Dispatch itself is covariant. Tuples are the data structure that represent argument lists.


That makes sense. It’s now bugging me that it’s impossible to declare any other covariant types in Julia, or am I wrong about that?

It’s not something that I really see the need for, it’s just bothering me for purely academic reasons.

Also , what T would it be if Tuple{Int,String} <: AbstractVector{T}?

Academically speaking, you can make a hacky system of “tagged” tupples like

struct MyTypeTag; end
const MyType = Tuple{MyTypeTag, Integer, AbstractVector}
MyType(a::Integer, b::AbstractVector) = (MyTypeTag(), a, b)

and probvide other constructors and methods for MyType as you please. You however loose access to getfield. But it’s fully compatible with multiple dispatch, and macros could make this easy.

I say “academic” because there’s no support for a covariant Array (I guess the relatively modern untyped comprehensions come close), so you can only get so far by assuming that types are covariant. But it’s a fun mind experiment to think of how this could play out if covariance was supported/assumed at the language level (for structs and Buffer, say).

IMHO, covariance would be plausible for immutables, but does not really play well with inheritance from abstract types. E.g. if you accept a Tuple{Integer,Integer}, then of course a Tuple{Int16,Int16} is fine; after all, any element you can obtain from the tuple isa Integer, and the question whether you can store another Integer than Int16 is irrelevant for an immutable Tuple. The same would hold for hypothetical ImmutableVector{Integer}. But now if ImmutableVector{T} <: AbstractVector{T}, then ImmutableVector{Int16} <: AbstractVector{Int16} and ImmutableVector{Integer} <: AbstractVector{Integer}, and - with covariance allowed - ImmutableVector{Int16} <: ImmutableVector{Integer}. So transitivity would mean that ImmutableVector{Int16} <: AbstractVector{Integer}, which also makes sense, but might be quite are to deduce is slightly more involved cases.

In that case T would be Any. This would be consistent with the existing behavior of eltype when applied to tuples. However, note that eltype((1,UInt(1))) == Integer, so it does seem like a slightly complicated issue to infer the AbstractVector types (though it’d still be well-defined from a graph theory perspective I think).

Anyway, I think the current situation of having invariant types but being able to do things like AbstractVector{<:Integer} works quite well (especially now that we have this shorthand notation in 0.6!), and there is probably less chance for confusion than there would be if there were covariant types flying around.

The original motivation of my post was purely practical: in most cases if I have a function that takes an AbstractVector, I’m also ok with it taking a Tuple. I suspect that’s true for everyone in most circumstances. Maybe it would be nice to, in Base, define something like

const AbstractAbstractVector{T} = Union{AbstractVector{T}, Tuple{Vararg{T}}}

(the name is kind of a joke). However, the subtyping of this thing gets damn confusing because of the covariance of tuples, so this is probably a bad idea.

For mutable covariant arrays I would see it as inference’s job to prove a given element type for the array and allow efficient code for that case. When you can’t prove absolutely what types get put into setindex!, then you must leave the elements boxed (e.g. Any). To me that seems “fine”: Julia already follows a dynamic semantic but with inference for optimization, so it’s more of the same.

The bigger question is if such a covariant semantic is worthwhile or desirable at all. As stated above, it might be too confusing. It might make the FFI harder. I dunno.

For AbstractAbstractArray I think what you want is some kind of trait indicating that you can index it similar to an array, i.e. You certify your type follows some interface for getindex, say. My personal prediction is that traits will eventually (in the longer term) become a part of Julia, and maybe then it will be easier to use tuples as simple vector-like storage.

I’ve needed something like this as well and also use Unions as a fix. I think there is some kind of plan to have traits to cover this type of use case, though I’m not sure what the priority on that is. See also


@mauro3 Do you have any updates on this?

Interesting. I didn’t even know about traits. It never ceases to blow my mind how much one can achieve with metaprogramming. As I get more used to it, I find it harder to imagine using programming languages without it.

1 Like

Not really any updates. SimpleTraits.jl is ready for use and is being used. I am not working on anything in Julia Base and I don’t know of anyone else (but I don’t have much of a grasp on what is going on). According to Stefan’s JuliaCon2016 talk, traits are scheduled for Julia 2.0.

1 Like