Struggling with signatures

This is called the Diagonal Rule.

In general, if you use a type parameter (e.g., T) in your signature, Julia expect all values to match the same type. Moreover, type parameters inside parametrict types (in your case, both uses, as T is a parameter for Dict and a parameter for Vector) are considered invariant. This means Julia will not accept a supertype (or subtype) over the specified type, only the exactly specified type.

For example, this will work:

julia> Container(Dict(S1(1) => [S1(2), S2(3)], S2(4) => [S1(5), S2(6)]))
Container(Dict{S,Array{S,1}}())

But only because, the key of the Dict is inferred to be S (because it has both S1 and S2 and S is the narrowest type that matches both) and every Vector value is also inferred to have elements of type S. So both types match exactly. If you let a single vector have only elements of one type the code breaks.

julia> Container(Dict(S1(1) => [S1(2), S2(3)], S2(4) => [S1(5)]))
ERROR: MethodError: no method matching Container(::Dict{S,Array{T,1} where T})

The reason is simple. You already created a Dict of the wrong type at this point because you relied on inference, and because one Vector was inferred to Vector{S} and another to Vector{S1} Julia probably tried to use something like Union{Vector{S},Vector{S1}} (or Any). You can fix it, but it would need to make a copy, what I think it is not what you want. See below.

I see that you have locked the container to the type S (instead of leaving it with a parametric type). I am not sure if this is intended, but I will consider it is intended. The problem boils to the fact you already have structures of the wrong types, Dict{S1, ...} is not a Dict{S, ...} these are distinct types (this is related to the invariance I mentioned above).

function Container(source::Dict{T, Vector}) where {T <: Any}
       # and here create a vector of the right type (Vector{Tuple{S, Vector{S}}})
       # to pass to the Dict constructor and have a Dict of the right type
       # Note the inner value vectors need to be converted from
       # whichever type of vector (like `Vector{S1}`) to `Vector{S}`.

The question is not that the Julia language definition says Any will be used but that the compiler is free to infer anything that is valid. In my first sample code above, with Julia 1.5.3, it inferred S. The language creators did not specify this on purpose, to have more leeway in the way inference is implemented in the compiler. The inference just need to be valid, not tight.

Final observation: you are aware that your last constructor always create empty containers and throw away the arguments, right?

3 Likes