Incorrect concrete type for Vector{Pair{Any, Any}}?

Hello,

My question’s rather simple:

julia> [1=>2]
1-element Vector{Pair{Int64, Int64}}:
 1 => 2

julia> [1=>2] isa Vector{Pair{Any, Any}}
false

Why is Vector{Pair{Int64, Int64}} not a subtype of Vector{Pair{Any, Any}}?

And if useful, here is my Julia version:

julia> versioninfo()
Julia Version 1.6.4
Commit 35f0c911f4 (2021-11-19 03:54 UTC)
Platform Info:
  OS: Linux (x86_64-pc-linux-gnu)
  CPU: Intel(R) Core(TM) i9-10885H CPU @ 2.40GHz
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-11.0.1 (ORCJIT, skylake)
Environment:
  JULIA_DEPOT_PATH = /home/andreinicusan/Prog/Julia/builds/julia-1.6.4/packages:
  JULIA_NUM_THREADS = 16

Thanks,
Leonard

I don’t know the why but

julia> [1=>2] isa Vector{Pair{T,T}} where T <: Any
true

Yes, this is correct and expected behaviour. Vector{Pair{Any, Any}} is a concrete type that can be instantiated, it would be a vector of pairs of different types. Concrete types cannot have subtypes, therefore Vector{Pair{Int, Int}} cannot be its subtype.

This has to do with invariant and contravariant types, you can read more about this here: Types · The Julia Language

In particular, you can find this warning in the docs:

This last point is very important: even though Float64 <: Real we DO NOT have Point{Float64} <: Point{Real} .

An alternative convenient test could be

1.7.0> [1=>2] isa Vector{<:Pair}
true
6 Likes

Thank you for the very quick replies. It seems odd that Tuple is covariant, while Pair is invariant; I understand that covariance is special-cased, but why isn’t a type as fundamental as Pair - not unlike Tuple - made covariant?

I am currently working with a library that asserts types with constraints::Vector{Pair{Any,Any}}, so I need to enforce the Any quite awkwardly:

julia> [1=>2, ""=>""][1:end-1]
1-element Vector{Pair{Any, Any}}:
 1 => 2

A type like Vector cannot really be covariant: one cannot pass e.g. Vector{Int} everywhere where Vector{Any} is expected. This function would fail if you could pass Vector{Int} to it:

f(x::Vector{Any}) = push!(x, "abc")

but it works for Vector{Any}.

If you do need to create a Vector{Pair{Any, Any}} containing pairs of a single type (do you really need it?), it’s possible much less awkwardly: Pair{Any, Any}[1 => 2].

3 Likes

Apparently, Tuple is abstract unless its parameters are concrete, so you cannot instantiate it. I am not sure if that makes it contravariant or not, but at least there is nothing preventing it to have subtypes.

Yes, or, alternatively:

[Pair{Any, Any}(1,2)]

This is a common idiom in Julia, btw, for any type, T, you can create a Vector{T} by writing T[...]

That is terrible, BTW. Maybe you can open an issue/PR to have it changed. This is really not idiomatic Julia.

1 Like

it might not be bad. sometimes this is what you need to prevent ridiculous amounts of compiler specialization. that said, 90% of the time, this is bad.