Incomplete description of type vector of pairs

Why does Julia not fully describe the type of the elements of the pair vector?

shouldn’t it be the union of the types of the individual elements of the vector?

Pair{Tuple{Char, Int64, Int64}, Float64}
#and
Pair{Tuple{Char, Int64, Int64}, Nothing}
18-element Vector{Pair{Tuple{Char, Int64, Int64}}}:        
 ('p', 1, 2) => 2.5
 ('q', 1, 2) => 2.5
 ('p', 1, 7) => 4.1
 ('q', 1, 7) => nothing
 ('p', 2, 3) => 7.4
 ('q', 2, 3) => 7.4
 ('p', 2, 5) => nothing
 ('q', 2, 5) => 5.6
 ('p', 3, 4) => nothing
 ('q', 3, 4) => nothing
 ('p', 3, 5) => nothing
 ('q', 3, 5) => nothing
 ('p', 4, 1) => nothing
 ('q', 4, 1) => nothing
 ('p', 4, 5) => nothing
 ('q', 4, 5) => nothing
 ('p', 5, 2) => nothing
 ('q', 5, 2) => 3.2

julia> typeof([ (person,pair...)=>person_values(person,pair...)  for (person,pair) in
                               Iterators.product(Persons,Pairs)][:])
Vector{Pair{Tuple{Char, Int64, Int64}}} (alias for Array{Pair{Tuple{Char, Int64, Int64}}, 1})

julia> typeof([ (person,pair...)=>person_values(person,pair...)  for (person,pair) in
                               Iterators.product(Persons,Pairs)][:][1])
Pair{Tuple{Char, Int64, Int64}, Float64}

julia> typeof([ (person,pair...)=>person_values(person,pair...)  for (person,pair) in
                               Iterators.product(Persons,Pairs)][:][4])
Pair{Tuple{Char, Int64, Int64}, Nothing}

I am also hoping somebody knowledgeable will answer this question because I have a similar issue. In the proposed revision (https://github.com/JuliaCollections/DataStructures.jl/pull/787) of sorted containers from DataStructures.jl, I rewrote constructors to fully infer types when given untyped collections of pairs, e.g., SortedDict(Base.Forward, (1=>"a", 2=>7)). Here is the relevant code:

function SortedDict(o::Ordering, kv)
    c = collect(kv)
    if eltype(c) <: Pair
        c2 = collect((t.first, t.second) for t in c)
    elseif eltype(c) <: Tuple
        c2 = collect((t[1], t[2]) for t in c)
    else
        throw(ArgumentError("In SortedDict(o,kv), kv should contain either pairs or 2-tuples"))
    end
    SortedDict{eltype(c2).parameters[1], eltype(c2).parameters[2], typeof(o)}(o, c2)
end

Notice that this constructor copies the data three times in a row in order to infer the types. The reason that three copies are needed is the following Julia behavior:

julia> typeof(collect((1=>"a",2=>7)))
Vector{Pair{Int64}} (alias for Array{Pair{Int64}, 1})

I could remove at least one of the three copying operations if the above statement returned Vector{Pair{Int64,Any}} or Vector{Pair{Int64, Union{String,Int64}} instead of the inner type Pair{Int64} that has a free parameter.

Note that neither of those will actually work because julia types are invariant:

julia> Pair{Int, String} <: Pair{Int, Any}
false

julia> Pair{Int, String} <: Pair{Int, Union{String, Int}}
false

so collect won’t return a collection with either of those element types, since a vector of that type can’t hold any of the things you’ve put into it.

The syntax Pair{Int} is just a shorthand for the set of all Pair types with Int as their first parameter:

julia> Pair{Int} == Pair{Int, T} where T
true

(incidentally, I’m surprised that === returns false in the above case).

That’s not to say, though, that a Vector{Pair{Int, Union{Int, String}}} is an unreasonable thing to want. But constructing one will involve making a new Pair{Int, Union{...}} for each element, which is something collect isn’t going to do.

I agree that Pair{Int,Int} is not a subtype of Pair{Int,Any}, but I disagree that Vector{Pair{Int,Any}} is unsuitable for the requested operation:

julia> u = Pair{Int,Any}[1=>"a",2=>7]
2-element Vector{Pair{Int64, Any}}:
 1 => "a"
 2 => 7

julia> typeof(u)
Vector{Pair{Int64, Any}} (alias for Array{Pair{Int64, Any}, 1})

julia> push!(u, 3=>9.9)
3-element Vector{Pair{Int64, Any}}:
 1 => "a"
 2 => 7
 3 => 9.9

So I still think it would be reasonable (and helpful in my use-case) for collect to return Vector{Pair{Int,Any}}.

not sure I have fully understood the argument on the functioning of collect in relation to the determination of the types of the elements making up the vector.
I try to submit two other examples that if clarified I can allow myself to better understand how things are going.

functions derive this discussion

Here the compréhension completely determines the type of the components the dict

julia> [ (p,pp...)=>pv(p,pp) for(p,pp) in Iterators.flatten([k.=>v for (k,v) in d1])]
7-element Vector{Pair{Tuple{Char, Int64, Int64}, Float64}}:
 ('p', 1, 2) => 2.5
 ('p', 1, 7) => 4.1
 ('p', 2, 3) => 7.4
 ('q', 1, 2) => 2.5
 ('q', 2, 3) => 7.4
 ('q', 2, 5) => 5.6
 ('q', 5, 2) => 3.2

here not

ulia> [ (person,pair...)=>pv(person,pair)  for (person,pair) in
                               Iterators.product(Persons,Pairs)][:]
18-element Vector{Pair{Tuple{Char, Int64, Int64}}}:
 ('p', 1, 2) => 2.5
 ('q', 1, 2) => 2.5
 ('p', 1, 7) => 4.1
 ('q', 1, 7) => nothing
 ('p', 2, 3) => 7.4
 ('q', 2, 3) => 7.4
 ('p', 2, 5) => nothing
 ('q', 2, 5) => 5.6
 ('p', 3, 4) => nothing
 ('q', 3, 4) => nothing
 ('p', 3, 5) => nothing
 ('q', 3, 5) => nothing
 ('p', 4, 1) => nothing
 ('q', 4, 1) => nothing
 ('p', 4, 5) => nothing
 ('q', 4, 5) => nothing
 ('p', 5, 2) => nothing
 ('q', 5, 2) => 3.2

but if I define the dictionary by hand, I get

julia> Dict(
       ('p', 1, 2) => 2.5,
        ('q', 1, 2) => 2.5,
        ('p', 1, 7) => 4.1,
        ('q', 1, 7) => nothing,
        ('p', 2, 3) => 7.4,
        )
Dict{Tuple{Char, Int64, Int64}, Union{Nothing, Float64}} with 5 entries:
  ('p', 1, 7) => 4.1
  ('q', 1, 7) => nothing
  ('p', 2, 3) => 7.4
  ('p', 1, 2) => 2.5
  ('q', 1, 2) => 2.5
julia> [('p', 1, 2) => 2.5
        ('q', 1, 2) => 2.5
        ('p', 1, 7) => 4.1
        ('q', 1, 7) => nothing]
4-element Vector{Pair{Tuple{Char, Int64, Int64}, Union{Nothing, Float64}}}:
 ('p', 1, 2) => 2.5
 ('q', 1, 2) => 2.5
 ('p', 1, 7) => 4.1
 ('q', 1, 7) => nothing

Let me just interject: for my own use-case, it would be OK that collect((1=>"a",2=>7) returns an object of type Vector{Pair{Int}} rather than Vector{Pair{Int,Any}} if only I knew of a documented (i.e., likely to persist in future versions of Julia) method to retrieve the types of the inner objects. This snippet illustrates the issue:

julia> u = Pair{Int,Any}[1=>"a",2=>7]
2-element Vector{Pair{Int64, Any}}:
 1 => "a"
 2 => 7

julia> eltype(u).parameters[1]
Int64

julia> u=collect((1=>"a",2=>7))
2-element Vector{Pair{Int64}}:
 1 => "a"
 2 => 7

julia> eltype(u).parameters[1]
ERROR: type UnionAll has no field parameters
Stacktrace:
 [1] getproperty(x::Type, f::Symbol)
   @ Base .\Base.jl:37
 [2] top-level scope
   @ REPL[39]:1