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...