Let’s assume I define a function f with two parameters x and n, where x should be a vector of integer numbers and n should be an integer. I though that I could use the abstract Integer type to write the following function header:
function f(x::Vector{Integer}, n::Integer)
x .* n
end
However, this gives an error when calling f([1, 2, 3], 2):
When I change the type parameter of Vector to <:Integer, everything works as expected, i.e.
function f(x::Vector{<:Integer}, n::Integer)
x .* n
end
Intuitively, I expected the abstract type Integer to work, because by definition it should accept all its (direct and indirect) subtypes. This is the case for n::Integer, but not x::Vector{Integer}. What is the reasoning behind this behavior? Where is the error in my thinking that it should work?
I read that section multiple times, but I don’t think it explains the problem I’m having. It even contains examples like Point{AbstractString}, which conceptually should be Vector{Integer} in my example (both AbstractString and Integer are abstract types) – but this doesn’t work and I need to use Vector{<:Integer}.
I think I’m struggling with how to write type annotations and not types. The UnionAll section even mentions the syntax Array{<:Integer}, but my question is why the <: operator is necessary when Integer is already an abstract type.
A superficial explanation that might be easy to remember is that Integer is an abstract type, so if you input an integer of type Int64, this is a subtype of Integer and is therefore a match:
jl> Int64 <: Integer
true
jl> 3 isa Integer
true
But Vector{Integer} is not abstract type, it is concrete:
jl> isconcretetype(Vector{Integer})
true
Now, [1, 2, 3] has type Vector{Int64}. Since concrete types cannot have subtypes, Vector{Int64} cannot be a subtype of Vector{Integer}, so you don’t get a match:
jl> Vector{Int64} <: Vector{Integer}
false
So why is Vector{Integer} not abstract? Well, it can be instantiated, it’s a vector with heterogeneous elements, like Integer[1, 0x03]. It’s the same thing with Vector{Any}, like Any[5, sin, "hello"]. If Vector{Any} weren’t concrete, you couldn’t make arbitrary collections like this.
This isn’t really the reason for this choice, there are some type-theoretic explanations that are hard to grasp (for me), but my explanation is what I use to remember this.
In short, [1,2,3] is a Vector{Int64}, which cannot contain, for instance, an Int32. Thus it has a constraint that Vector{Integer} does not have. That is why Vector{Int64} cannot be used as a subtype of Vector{Integer}.
Note that they use Point{AbstractString} to demonstrate Point{AbstractString} <: Point, and Point{AbstractString} <: Pointy{AbstractString} - which are equivalent here to Vector{Integer} <: Vector and Vector{Integer} <: AbstractArray{Integer} , both of which are true.
The relevant part for this discussion is
julia> Point{Float64} <: Point{Real}
false
Warning
This last point is very important: even though Float64 <: Real we DO NOT have Point{Float64} <: Point{Real} .
It then goes on to explain why this is, and shows an example of how it’s significant for performance reasons.
Thank you, these are all really helpful answers ! I guess I should have read that section more carefully, because as you point out it is already explained.
All three answers by @DNF, @lmiq, and @digital_carver are solutions to my question, so I’m having a hard time marking just one as the solution. I hope this is OK with everyone if I just mark the last answer.