Type annotations with abstract types (or when is `<:` required?)

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

However, this gives an error when calling f([1, 2, 3], 2):

ERROR: MethodError: no method matching f(::Vector{Int64}, ::Int64)
Closest candidates are:
  f(::Vector{Integer}, ::Integer)

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

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?

This is described in detail here in the Julia Manual. If something is not clear there can you please comment?


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

jl> 3 isa Integer

But Vector{Integer} is not abstract type, it is concrete:

jl> isconcretetype(Vector{Integer})

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}

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.


maybe this helps: Vector{Int} <: Vector{Real} is false??? · JuliaNotes.jl

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

1 Like

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}


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 :heart:! 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.