Julia: subtyping regression: Vector{T} is not considered concrete

Created on 23 Feb 2018  Â·  12Comments  Â·  Source: JuliaLang/julia

This example used to work in julia 0.6.0 and 0.6.2 but now is broken by the commit 88a4db2:

julia> (Tuple{Vector{T}, Vector{T}} where T) <: Tuple{T,T} where T
false

Should be true because Vector{T} is concrete for any instantiation of T.

types and dispatch

Most helpful comment

At least, that is what was trying to say, yes.

All 12 comments

This was an intentional change.

(Tuple{Vector{T}, Vector{T}} where T) consists of the elements (Tuple{Vector{T}, Union{}}, Tuple{Union{}, Vector{T}}, Tuple{Vector{T}, Vector{T}}, Tuple{Union{}, {}})

while

Tuple{T,T} where T is only the latter two (Tuple{Vector{T}, Vector{T}}, Tuple{Union{}, {}})

So we can see that they aren't equivalent.

Should it be Vector{Union{}} instead of Union{} everywhere in the example?

Let's try a more restrictive example:

(Tuple{Vector{T}, Vector{T}} where T<:Int) <: Tuple{S,S} where S<:Vector{Q} where Q<:Int

My understanding is that the left type Tuple{Vector{T}, Vector{T}} where T<:Int is semantically equivalent to

Union{
  Tuple{Vector{Int}, Vector{Int}},
  Tuple{Vector{Union{}}, Vector{Union{}}}
}

because T ∈ {Union{}, Int} and we just enumerate all possible instances of T to get a respective where-type.

The type on the right Tuple{S,S} where S<:Vector{Q} where Q<:Int is semantically equivalent to

Union{
  Tuple{Vector{Int}, Vector{Int}},
  Tuple{Vector{Union{}}, Vector{Union{}}}
}

Because Q ∈ {Int, Union{}}, and therefore S ∈ {Vector{Int}, Vector{Union{}}, Union{}}, but since S should be concrete we throw out Union{}.

Type such as Tuple{Vector{Int}, Union{}} is certainly subsumed by Tuple{Vector{Int}, Vector{Int}}, but I don't see why it should be initially considered an element of Tuple{Vector{T}, Vector{T}} where T<:Int.

Isn't Tuple{Vector{Int},Union{}} a subtype of of any two-element Tuple where the first element is a supertype of Vector{Int}, unless it is constrained by the diagonal rule?

Should it be Vector{Union{}} instead of Union{} everywhere in the example?

Vector{T} includes Vector{Union{}}

is semantically equivalent to

Not precisely. There's also the diagonal constraint, which states that the types must be the same.

since S should be concrete we throw out Union{}

This isn't necessarily true, but it's subtle, since it starts to run up against the question of how we define the term "concrete", and then simply becomes a tautology. Instead, consider by analogy that Vector{Union{}} _is_ a concrete type, so we should expect that Tuple{Union{}} is too. But it's simply just not an _inhabited_ concrete type (e.g. it can't be constructed). However, subtyping doesn't care if a type is inhabited, only if it is valid. We can similarly also observe that if Julia allowed null references, it would be inhabited, so this isn't relevant for subtyping.

@vtjnash I totally agree with the last paragraph, but I still don't see why we should somehow explicitly consider Tuple{Vector{T}, Union{}} belonging to Tuple{Vector{T}, Vector{T}}.

When we say that (Tuple{Vector{T}, Vector{T}} where T) <: Tuple{T,T} where T is false because the LHS type includes Tuple{Vector{T}, Union{}},
why don't we say that Tuple{Int,Int} includes Tuple{Int,Union{}} and, therefore, Tuple{Int,Int} <: Tuple{T,T} where T is also false?

I don't see the difference between Vector{T} and Int that would justify including Union{} into the former type and not-including it into the latter one.

julia> (Tuple{Vector{T},Union{}} where T) <: (Tuple{Vector{T},Vector{T}} where T)
true

julia> Tuple{Vector{T} where T,Union{}} == (Tuple{Vector{T},Union{}} where T)
true

But Vector{T} where T is not concrete.

Yes, I agree that's a problem. We currently have no way to identify that Tuple{Int, Int} means exactly that (and not Tuple{Int, Union{}}) in dispatch. However, this result is needed for dispatch, so it has to be computed wrongly. Looking back at the issue martinholters opened about this, I see he talked about this specific case: https://github.com/JuliaLang/julia/issues/24614#issuecomment-349679271

@martinholters @vtjnash Oh, I think I finally see what you are saying. Since

julia> Tuple{Vector{T} where T, Union{}} <: Tuple{Vector{Q}, Vector{Q}} where Q
true

it is bad to have (Tuple{Vector{Q}, Vector{Q}} where Q) <: Tuple{S,S} where S because it breaks transitivity in

julia> Tuple{Vector{T} where T, Union{}} <: Tuple{S, S} where S
false

Is that right?

At least, that is what was trying to say, yes.

Right. Another possible option would be prohibit forming types with Union{} as a type-parameter – this appears to have been the path taken by Rust, for example (only ref I have for this is https://github.com/rust-lang/rfcs/blame/master/text/1216-bang-type.md#L345) – then compute whether any of the type-parameters might have a subtype after apply_type (this is what the code attempted to do before 88a4db2).

Just more discussion.
After some thinking it seems that the problem here stems from inconsistency in subtyping between diagonal types and tuple-where types.

If we don't have diagonality, then it always holds that Tuple{t where T, ...} == Tuple{t, ...} where T. Including this:

julia> (Tuple{Vector{T}, Union{}} where T) == Tuple{Vector{T} where T, Union{}}
true

However, the following subtype relations are not consistent (in julia 0.6.2):

julia> (Tuple{Vector{T}, Union{}} where T) <: Tuple{S, S} where S
true

julia> Tuple{Vector{T} where T, Union{}} <: Tuple{S, S} where S
false

You fixed this by making the first relation false. Another way would be to make the second true.

The second approach would work naturally if subtyping was defined on "normalized" types exclusively. Where normalization makes Tuple{t where T, ...} ==> Tuple{t, ...} where T.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ararslan picture ararslan  Â·  3Comments

felixrehren picture felixrehren  Â·  3Comments

yurivish picture yurivish  Â·  3Comments

wilburtownsend picture wilburtownsend  Â·  3Comments

tkoolen picture tkoolen  Â·  3Comments