This question arises from my attempt to write new constructors for DataStructures.jl. Please see the trace below (1.7.1), which raises the following questions:
-
Is there any difference between <type>.parameters
and <type>.types
? They gave the same answers in every test below.
-
Why did collect
return different answers when applied to a tuple versus an array in Tests 3 and 4 and yet there was no difference in Tests 1 and 2?
-
(Main question.) In Test #4, how can I extract the two parameters of the type? I would have expected the two parameters to be Int
and Any
. Both .parameters
and .types
failed.
julia> # Test 1
julia> c = collect([(1,3),(2,"a")]);
julia> eltype(c)
Tuple{Int64, Any}
julia> eltype(c).types
svec(Int64, Any)
julia> eltype(c).parameters
svec(Int64, Any)
julia> # Test 2
julia> c = collect(((1,3), (2, "a")));
julia> eltype(c)
Tuple{Int64, Any}
julia> eltype(c).types
svec(Int64, Any)
julia> eltype(c).parameters
svec(Int64, Any)
julia> # Test 3
julia> c = collect([1=>3, 2=>"a"])
2-element Vector{Pair{Int64, Any}}:
1 => 3
2 => "a"
julia> eltype(c)
Pair{Int64, Any}
julia> eltype(c).types
svec(Int64, Any)
julia> eltype(c).parameters
svec(Int64, Any)
julia> # Test 4
julia> c = collect((1=>3, 2=>"a"));
julia> eltype(c)
Pair{Int64}
julia> eltype(c).types
ERROR: type UnionAll has no field types
Stacktrace:
[1] getproperty(x::Type, f::Symbol)
@ Base .\Base.jl:37
[2] top-level scope
@ REPL[81]:1
julia> eltype(c).parameters
ERROR: type UnionAll has no field parameters
Stacktrace:
[1] getproperty(x::Type, f::Symbol)
@ Base .\Base.jl:37
[2] top-level scope
@ REPL[82]:1
1 Like
By tracking down the code, [1=>3, 2=>"a"]
calls Base.vect(1=>3, 2=>"a")
, which in turn calls promote_typeof
(base of promote
). The type Vector{Pair{Int,Any}}
is already determined at this point.
(1=>3, 2=>"a")
is a tuple type, so the promotion is delayed until collect
, which calls promote_typejoin
(base of typejoin
), which returns Pair{Int}
(shorthand for Pair{Int, T} where T
). These are two different types: Pair{Int,Any}
is a single type, while Pair{Int}
is a set of types: Pair{Int, Int}
, Pair{Int, String}
or even Pair{Int, Any}
.
The different code paths promote
vs typejoin
lead to different result, but I am unclear about the reason though.
Since you see their is no single type, you might need to convert them to a single type maybe with some type calculations.
Regarding Pair{Int}
: this appears to be some kind of gap in the Julia type system. As far as I can see, there is no difference between Vector{Pair{Int,T} where T}
and Vector{Pair{Int,Any}}
, except that the former is less amenable to type introspection. Note that Julia automatically interprets Vector{T where T}
as Vector{Any}
.
Regarding my main problem of inferring the data type of an arbitrary iterable of pairs: below is the constructor I am planning to use. This constructor has the big disadvantage that it copies the data three times. When the type parameters are known, the data is copied just once. (This disadvantage will be documented.) I use the same pattern for SortedMultiDict
. Better solutions are welcome.
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