First, a bit of background. In Julia, there are no instances of abstract types. For example, typeof(1) == Int
. Int
is indeed a Real
so 1 isa Real
is true, however typeof(1) == Real
is false. You can think of the type system as a directed acyclic graph in which the concrete types are the leaves.
If you have a Vector{Real}
, this is a Vector
the elements of which are Real
s. However, they can be any concrete type which is descended from Real
. So, for example you can have Real[1, 1.0]
, that is, a Vector
with one element which is Int
and another which is Float64
. Because the container type is Vector{Real}
, the compiler only knows that the elements are guaranteed to be Real
, but it does not know what concrete type they are. They could be, Int
, Float16
, UInt8
, who knows.
When you write Vector{<:Real}
this is the union of all Vector
s the elements of which are real, for example
julia> Vector{Real} <: Vector{<:Real}
true
julia> Vector{Int} <: Vector{<:Real}
true
julia> Vector{Float64} <: Vector{<:Real}
true
As should be becoming clear, it is “better” for performance reasons to have a Vector{Float64}
than a Vector{Real}
in which all of the elements happen to be Float64
. This is because Vector{Float64}
guarantees to the compiler that the elements are all Float64
, whereas for Vector{Real}
the elements can be any Real
type.
So, back to the type signatures of functions. When you write Vector{Real}
on an argument, you are requiring that the input be a Vector{Real}
(perhaps confusingly, Vector{Real}
is itself a concrete type, even though it has an abstract type as a parameter). In the vast majority of such instances, what you really want is to get a Vector
the elements of which are Real
. Yes, this could be Vector{Real}
, but it can just as well be Vector{Int}
or Vector{Float32}
or Vector{BigFloat}
. This is what Vector{<:Real}
is telling it, the parameter of Vector
must be a subtype of Real
, but not necessarily equal to Real
.