`Vector{Float64} <: Vector{Any}` = false, why?

My apologies if this has been covered previously. I just got burned by this, I was assuming I could use ::Vector{Any} to cover an input of Vector{Float64}, Vector{Int}, etc. But it turns out that Julia doesn’t believe Vector{Float64} is a subtype of Vector{Any}, I need to use instead…

Vector{Float64} <: Any

Or it also seems that this works too…

Vector{Float64} <: Vector

What am I missing?

1 Like

I don’t know how to answer your main question, but just to add to the discussion:

Running the code below in the REPL gives true.

julia> Vector{Float64} <: Vector{<:Any}
true

If you want to check if a type is subtype of a Vector of Ints or Float64s, you can do something like:

julia> Vector{Float64} <: Vector{<:Union{Float64, Int}}
true

Or better yet:

julia> Vector{Float64} <: Vector{<:Real}
true

I’m not sure how that works though

3 Likes

This is what was throwing me for a loop…

function test(x::Vector{Any})
    println(length(x))
end

test(zeros(10))

which gives the error:

ERROR: MethodError: no method matching test(::Vector{Float64})
Closest candidates are:
  test(::Vector{Any})

It turns out the notation you used solves the problem…

function test(x::Vector{<:Any})
    println(length(x))
end

Looks like I learned something new, didn’t realize I could use <: in the type definition.

Thanks!

2 Likes

This last point is very important: even though Float64 <: Real we DO NOT have Point{Float64} <: Point{Real} .
In other words, in the parlance of type theory, Julia’s type parameters are invariant , rather than being covariant (or even contravariant). This is for practical reasons: while any instance of Point{Float64} may conceptually be like an instance of Point{Real} as well, the two types have different representations in memory:

https://docs.julialang.org/en/v1/manual/types/#Parametric-Types

11 Likes

This is covered in the Inheritance section of this (almost ready) course.
In brief, use templates in these situations.

This appears to be one of the most frequently asked questions for beginners posted in this forum? I have encountered two similar posts in two days. Do we have a FAQ section or a MUST READ page for beginners in the Julia doc for all these Julia-specific niche issues?

3 Likes

This might be mainly for practical reasons, but also note that there is actually such a thing as a concrete Vector{Any}. Let’s say you have a function that looks like this:

function foo(x::Vector{Any})
    push!(x, 5)
    push!(x, "Hello")
    return x
end

Then

julia> foo(Any[])
2-element Vector{Any}:
 5
  "Hello"

What should happen if you pass in a Vector{Float64}?

1 Like

Another example

julia> [1, 1.0, :a]
3-element Vector{Any}:
 1
 1.0
  :a

julia> foo(x::Vector{Any}) = length(x)
foo (generic function with 1 method)

julia> foo([1, 1.0, :a])
3

julia> foo([1, 2, 3])
ERROR: MethodError: no method matching foo(::Vector{Int64})
Closest candidates are:
  foo(::Vector{Any}) at REPL[62]:1
Stacktrace:
 [1] top-level scope
   @ REPL[64]:1

What helps me understand better is the parametric notation…

f(x::Vector{T}) where {T === Any} = println("Vector{T} where T is only ::Any i.e. Vector{Any}")
f(x::Vector{T}) where {T <: Any} = println("Vector{T} where T can be anything")
f(x::Vector{T}) where {T === Float64} = println("Vector{T} where T is only ::Float64 i.e. Vector{Float64}")

One problem though is T === Any doesn’t work. Does anyone know how to properly write this in Julia? I feel like this notation may be a good best practice to avoid confusion.

3 Likes

You can write all of those without where clauses:

f(x::Vector{Any}) = "Vector{T} where T is only Any"
f(x::Vector{<:Any}) = "Vector{T} where T can be anything"
f(x::Vector{Float64}) = "Vector{T} where T is only Float64"

In action:

julia> f(Any[1])
"Vector{T} where T is only Any"

julia> f(Int[1])
"Vector{T} where T can be anything"

julia> f(Float64[1])
"Vector{T} where T is only Float64"

The construct where {T === X} isn’t supported because it doesn’t increase expressiveness: you can just eliminate the type parameter T entirely by substituting its value everywhere T appears. In other words, where clauses are only useful with upper/lower bounds. You can, however, enforce equality by using an upper and lower bound that are the same, so this does what you want using where clauses:

f(x::Vector{T}) where {Any <: T <: Any} = "Vector{T} where T is only Any"
f(x::Vector{T}) where {T <: Any} = "Vector{T} where T can be anything"
f(x::Vector{T}) where {Float64 <: T <: Float64} = "Vector{T} where T is only Float64"
11 Likes

My take on that: Vector{Int} <: Vector{Real} is false??? · JuliaNotes.jl

See Why doesn’t it work to declare foo(bar::Vector{Real}) = 42 and then call foo([1])? in the Julia FAQ.

4 Likes

Another thread on why parametric types are invariant: Reason behind designing parametric types as invariant. Note that these covariance and contravariace relations hold:

  • covariance: S <: TP{<:S} <: P{<:T}
  • invariance: S ≠ T ⇒ ¬ P{S} <: P{T}
  • contravariance: S <: TP{>:S} >: P{>:T}
2 Likes