For f2 the input is mainly a vector of pointers that can hold Real, and f1 is a a vector of pointers that hold anything. Because the parameters vary inside the {}. For f3 the type has to be the same (outside the {}), and can be inferred statically to be Int64 in this case so it works.

Examples:

julia> bla=Array{Tuple{T,T} where T}(2)
2-element Array{Tuple{T,T} where T,1}:
#undef
#undef
julia> bla[1]=r[1];bla[2]=("cat","dog")
("cat", "dog")
julia> f1(bla)
2

Thanks a lot for all your answers. I read the manual before and read it again last night. I think I learnt a lot more.

I understood before that the position of where keyword affects the scope of the type variables. In Vector{NTuple{2, T} where T}, the T is local and cannot be seen outside of Vector{} and T can change from Tuple to Tuple in the Vector, whereas in Vector{NTuple{2, T}} where T, the T can be accessed in the method and all Tuple in the Vector must have the same element type T. What I didnâ€™t know is that the first one declares a concrete type and the second declares an abstract type. That explains why f1 and f2 didnâ€™t work because Array{Tuple{Int64,Int64},1} is a concrete type and cannot be a subtype of another concrete type.

This all makes sense now except I still donâ€™t understand why Vector{NTuple{2, T} where T} declares a concrete type and Vector{NTuple{2, T}} where T declares an abstract type. Is this just by definition? or is there some reasoning behind this behavior? If so, what is the reasoning? Thanks!

julia> f2(r::Vector{<:NTuple{2, Real}}) = length(r)
f2 (generic function with 1 method)

Thanks for this wonderful example. After carefully reading the manual, I realize this is covariant type and also unlike other types, Tuple types are covariant as well , so Tuple{2, Real} is an abstract type. Vector{<:NTuple{2, Real}} is the same as Vector{T} where T<:NTuple{2, Real} and Tuple{Int64,Int64} is a subtype of NTuple{2, Real}. That is why your example works.

One way to understand it is to think about what an abstract type is â€“ in Julia, itâ€™s a type that cannot be instantiated, and potentially has many concrete subtypes.

For example, Real (representing a real number) is an abstract type; you canâ€™t instantiate one, and it has many concrete subtypes â€“ among them Float32, Float64, Rational:

Now, hereâ€™s the trick â€“ the type Real is the same exact type as the type T where T <: Real: they both specify precisely the same set of concrete types. You can see this simplification at the REPL:

julia> T where T <: Real
Real

With a parametric type like Vector the same kind of logic applies â€“ Vector{T where T <: Real} is the same as Vector{Real} and is the concrete type of a list that contains real numbers of any kind:

In contrast, the type Vector{T} where T <: Real, or Vector{<:Real} for short, cannot be concrete because it has many subtypes, one for each concrete subtype of Real:

julia> Vector{Float64} <: (Vector{T} where T <: Real)
true
julia> Vector{Rational} <: (Vector{T} where T <: Real)
true
# and many more...