-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Invariant Tuples #24614
Comments
Another potential advantage is that |
Hm, currently julia> typeintersect(Tuple{T, Ref, T} where T, Tuple{T, Ref{T}, T} where T)
Tuple{T,Ref{T1},T} where T1 where T which doesn't seem quite right. If I translate this to *) Incidentally, if I just define julia> typeintersect(InvariantTuple{T, <:Ref, T} where T, InvariantTuple{<:T, <:Ref{T}, <:T} where T)
InvariantTuple{T,#s11,T} where #s11<:Ref where T which is also not quite correct... |
It could be a concrete type. Or we still impose the restriction that only |
@JeffBezanson yes, |
I guess part of this thinking would be that the type of a method signature, e.g. |
Yes. Although some way of opting in to it actually becoming |
I would imagine that the diagonal rule would still apply (although not in that case). Whatever the diagonal rule is expressed in terms of could perhaps be used explicitly for that behavior in that case. |
Yes, the signature for Usage of To me, it's interesting to note that the new type system is powerful enough that we no longer need covariant tuples to describe how dispatch behaves! :) The question then becomes: what do we actually gain from covariance at all? |
Also - I hope I'm not wrong, but I vaguely remember a comment once discussing that |
Finally, at JuliaCon it seemed uncertain even if we thought that suitable algorithms things like accurate type intersection for a multiple inheritance type system even exist - I would guess that simplifying the type system might help with forward progress (I'm hardly an expert but it seems logical). I suspect this might be a pretty large simplification of the existing subtyping algorithm, and will be easier for users to grasp. |
The more I think about this idea, the more I like it. I would make a couple of observations:
I also don't think the deprecation process would be too bad: people don't write tuple types all that much and we can deprecate abstract parameters without |
A wrinkle would be -- if @andyferris's observation is right:
-- that the (mental) translation of a type signature to a tuple type gets complicated. Now the translation is easy: |
I don't understand enough this topic to be sure that this is related (apologies for the noise if it's not), but I wanted to raise a point about the way tuples work just in case it could be addressed in a tuple overhaul. The problem is related to how types are handled as function arguments: to get type-stable code, we often write a type signature with |
@mauro3: yes, that's the tradeoff. But the flip side is that right now that complexity is baked into the rules of how f(x::T1, y::T2) ==> Tuple{A, B} where {A<:T1, B<:T2} ==> Tuple{<:T1, <:T2}
f(x::T, y::T) ==> Tuple{A, A} where {A<:T} The fact that the "diagonal rule" just falls out of the combination of the translation rule and tuple invariance seems considerably easier to explain and justify than having an ad hoc rule. |
Actually that rule is a bit off: you need deduplicate the |
I realize also that the change I wish (that |
Whatever the other merits of this proposal, I really don't think tuple covariance is arbitrary. It can be formally derived from the other properties of tuples.
Is @andyferris I don't think this is related to ccall; C doesn't have subtyping so covariance doesn't come up. |
I wasn't saying that tuple covariance was arbitrary, I was saying that the diagonal rule is arbitrary: it works the way it does because that way is practical, not as a consequence of something else. But it's very interesting that roughly this same rule also falls naturally out of invariant tuples. |
For testing the waters here, I have started to add a new built-in type Concerning subtyping, everything without
with the obvious extension when replacing Opinions? |
Thank you so much for investigating, @martinholters! I think @JeffBezanson’s opinion on that will be most valuable but it all seems sane to me. |
To be clear, I think only the diagonal rule is the source of any complexity, and not covariance by itself. Invariance is possibly more difficult to handle. Vararg is also very complex. The proposed rule for
One operation we'll need (and already need as long as named tuples stay invariant) is "promoting" types in this way: |
Do you not agree that the diagonal rule somewhat "falls out of" invariance though? Or is that claiming too much? |
I think it's claiming a bit too much. The whole diagonal thing has two parts:
In
|
Wouldn't that break sorting of methods according to specificity? |
I would imagine this would be expressed the same way: At this point we're not likely to change this for 0.7, so I have only one observation here: If we were to introduce an invariant tuple type in the 1.x timeframe (and maybe even give it nice syntax like
|
With invariant tuples, I would think that the corresponding type would be diagonal. I.e. |
I've started playing with a
But (simply with julia> (Tuple{Ref{S}} where S<:Number) == Tuple{Ref{S} where S<:Number}
true
julia> (InvTuple{T} where T<:Ref{S} where S<:Number) == (InvTuple{T} where T<:(Ref{S} where S<:Number))
false My current thinking is that it should be valid to |
Yes, that's correct (the type system tries to do that internally too, and occasionally gets wrong answers where it fails to do so). |
Mapping the diagonal rule to julia> InvTuple{Union{},Int} <: InvTuple{<:Int,<:Int}
true # sure
julia> InvTuple{Union{},Int} <: InvTuple{T,T} where T
false # no surprise given invariance
julia> InvTuple{<:Int,<:Int} <: InvTuple{T,T} where T
false # has to follow to maintain transitivity So either
I'm not a fan of 2. because it would defeat the objective of fewer special cases/easier reasoning to some extent. Considering, in theory, mapping julia> Ref{<:Int} == Union{Ref{Union{}}, Ref{Int}}
false although we would need that to give
This leaves me with 4.---so: ideas anyone? |
Isn't this one of the areas where covariance of tuples is kind of annoying? E.g. the whole |
Hm, another argument in favor of 3 is that But that might also mean that the idea of a gentle transition by first doing the |
Yes, unfortunately I don't think a gentle transition is really possible. The invariant tuple approach seems to lead to some fundamental difference, which is actually precisely what's potentially appealing about it – that it might lead to different and possibly better semantics. |
I think the point of this issue is precisely to change |
At this point, I'm pretty sure that if we do something like this, it will be breaking, so post-1.x. And if it is breaking anyway, we can go even further. And one thing I have been pondering is to add e.g. foo(x::T, y::AbstractVector{T}) = ... # same dispatch behavior as now
bar(x:::T, y::AbstractVector{T}) = ... # T has to be the concrete type of x This would mainly affect dispatch using the diagonal rule which would need to be written with |
EDIT: Reducing my own noise EDIT2: |
I've just got the following idea:
EDIT It's just that I prefer those operators over the combination It makes it clear that both subtyping aswell as instance checking is part of the check. Currently that produces a syntax error in the case with a space julia> 5 <:: Integer
ERROR: syntax: space not allowed after ":" used for quoting and a type error due to binding to the "wrong" function in the case without space julia> 5<::Integer
ERROR: TypeError: in <:, expected Type, got Int64 As symbols and Expr instances behind the subtyping operator diagonal dispatch: It also could be extended towards parameters: |
This makes so much sense, I am looking forward to this change. Removing the tuple's "magic" and giving "power" to the user makes the whole type system much slicker. I wonder how much work it will require to introduce this change. |
seems like there are additional benefits |
Type intersection assumed it was equal to Union{}, so this makes it unconstructable so that holds true. This is similar to what the NamedTuple constructor does. Secondarily, this fixes an inference bug where it would create Vararg{Union{}} and then incorrectly handle that fieldtype. - Fixes #32392 - Addresses part of the concerns discussed in #24614 (comment) - Addresses part of the issues presented in #26175 - May allow improving jl_type_equality_is_identity (https://github.com/JuliaLang/julia/pull/49017/files#diff-882927c6e612596e22406ae0d06adcee88a9ec05e8b61ad81b48942e2cb266e9R986) - May allow improving intersection (finish_unionall can be more aggressive at computing varval for any typevars that appears in covariant position and has lb=Union{} and ub=leaf type)
Type intersection assumed it was equal to Union{}, so this makes it unconstructable so that holds true. This is similar to what the NamedTuple constructor does. Secondarily, this fixes an inference bug where it would create Vararg{Union{}} and then incorrectly handle that fieldtype. - Fixes #32392 - Addresses part of the concerns discussed in #24614 (comment) - Addresses part of the issues presented in #26175 - May allow improving jl_type_equality_is_identity (https://github.com/JuliaLang/julia/pull/49017/files#diff-882927c6e612596e22406ae0d06adcee88a9ec05e8b61ad81b48942e2cb266e9R986) - May allow improving intersection (finish_unionall can be more aggressive at computing varval for any typevars that appears in covariant position and has lb=Union{} and ub=leaf type)
Type intersection assumed it was equal to Union{}, so this makes it unconstructable so that holds true. This is similar to what the NamedTuple constructor does. Secondarily, this fixes an inference bug where it would create Vararg{Union{}} and then incorrectly handle that fieldtype. - Fixes #32392 - Addresses part of the concerns discussed in #24614 (comment) - Addresses part of the issues presented in #26175 - May allow improving jl_type_equality_is_identity (https://github.com/JuliaLang/julia/pull/49017/files#diff-882927c6e612596e22406ae0d06adcee88a9ec05e8b61ad81b48942e2cb266e9R986) - May allow improving intersection (finish_unionall can be more aggressive at computing varval for any typevars that appears in covariant position and has lb=Union{} and ub=leaf type)
Type intersection assumed it was equal to Union{}, so this makes it unconstructable so that holds true. This is similar to what the NamedTuple constructor does. Secondarily, this fixes an inference bug where it would create Vararg{Union{}} and then incorrectly handle that fieldtype. - Fixes #32392 - Addresses part of the concerns discussed in #24614 (comment) - Addresses part of the issues presented in #26175 - May allow improving jl_type_equality_is_identity (https://github.com/JuliaLang/julia/pull/49017/files#diff-882927c6e612596e22406ae0d06adcee88a9ec05e8b61ad81b48942e2cb266e9R986) - May allow improving intersection (finish_unionall can be more aggressive at computing varval for any typevars that appears in covariant position and has lb=Union{} and ub=leaf type)
Type intersection assumed it was equal to Union{}, so this makes it unconstructable so that holds true. This is similar to what the NamedTuple constructor does. Secondarily, this fixes an inference bug where it would create Vararg{Union{}} and then incorrectly handle that fieldtype. - Fixes #32392 - Addresses part of the concerns discussed in #24614 (comment) - Addresses part of the issues presented in #26175 - May allow improving jl_type_equality_is_identity (https://github.com/JuliaLang/julia/pull/49017/files#diff-882927c6e612596e22406ae0d06adcee88a9ec05e8b61ad81b48942e2cb266e9R986) - May allow improving intersection (finish_unionall can be more aggressive at computing varval for any typevars that appears in covariant position and has lb=Union{} and ub=leaf type)
Type intersection assumed it was equal to Union{}, so this makes it unconstructable so that holds true. This is similar to what the NamedTuple constructor does. Secondarily, this fixes an inference bug where it would create Vararg{Union{}} and then incorrectly handle that fieldtype. - Fixes #32392 - Addresses part of the concerns discussed in #24614 (comment) - Addresses part of the issues presented in #26175 - May allow improving jl_type_equality_is_identity (https://github.com/JuliaLang/julia/pull/49017/files#diff-882927c6e612596e22406ae0d06adcee88a9ec05e8b61ad81b48942e2cb266e9R986) - May allow improving intersection (finish_unionall can be more aggressive at computing varval for any typevars that appears in covariant position and has lb=Union{} and ub=leaf type)
Type intersection assumed it was equal to Union{}, so this makes it unconstructable so that holds true. This is similar to what the NamedTuple constructor does. Secondarily, this fixes an inference bug where it would create Vararg{Union{}} and then incorrectly handle that fieldtype. - Fixes #32392 - Addresses part of the concerns discussed in #24614 (comment) - Addresses part of the issues presented in #26175 - May allow improving jl_type_equality_is_identity (https://github.com/JuliaLang/julia/pull/49017/files#diff-882927c6e612596e22406ae0d06adcee88a9ec05e8b61ad81b48942e2cb266e9R986) - May allow improving intersection (finish_unionall can be more aggressive at computing varval for any typevars that appears in covariant position and has lb=Union{} and ub=leaf type)
Type intersection assumed it was equal to Union{}, so this makes it unconstructable so that holds true. This is similar to what the NamedTuple constructor does. Secondarily, this fixes an inference bug where it would create Vararg{Union{}} and then incorrectly handle that fieldtype. - Fixes #32392 - Addresses part of the concerns discussed in #24614 (comment) - Addresses part of the issues presented in #26175 - May allow improving jl_type_equality_is_identity (https://github.com/JuliaLang/julia/pull/49017/files#diff-882927c6e612596e22406ae0d06adcee88a9ec05e8b61ad81b48942e2cb266e9R986) - May allow improving intersection (finish_unionall can be more aggressive at computing varval for any typevars that appears in covariant position and has lb=Union{} and ub=leaf type)
Type intersection assumed it was equal to Union{}, so this makes it unconstructable so that holds true. This is similar to what the NamedTuple constructor does. Secondarily, this fixes an inference bug where it would create Vararg{Union{}} and then incorrectly handle that fieldtype. - Fixes JuliaLang#32392 - Addresses part of the concerns discussed in JuliaLang#24614 (comment) - Addresses part of the issues presented in JuliaLang#26175 - May allow improving jl_type_equality_is_identity (https://github.com/JuliaLang/julia/pull/49017/files#diff-882927c6e612596e22406ae0d06adcee88a9ec05e8b61ad81b48942e2cb266e9R986) - May allow improving intersection (finish_unionall can be more aggressive at computing varval for any typevars that appears in covariant position and has lb=Union{} and ub=leaf type)
A radical idea that came up in #24166 and on slack: Make
Tuple
invariant in its parameters.Pro:
Tuple
s less special/magical.Tuple{Union{Int,Missing}, Union{Int,Missing}}
to be concrete. (Added based on comment below.)Con:
(Hoping to stir up a discussion that adds to these lists.)
Of course the current covariant behavior is useful more often than not, but the new
InvariantTuple
(for the sake of discussion) could be use to express it:Tuple{Number,Any}
would becomeTuple{<:Number, <:Any}
. The diagonal rule would be trivial:Tuple{T,T} where T
would becomeInvariantTuple{T,T} where T
(butT
would no longer be confined to concrete types), whileTuple{T,T,Ref{T}} where T
(N.B. diagonal rule does not apply!) would becomeInvariantTuple{<:T,<:T,<:Ref{T}} where T
.Handling
Vararg
s needs more thought:Tuple{Vararg{Any}}
would need to be interpreted asInvariantTuple{<:Any,<:Any,...}
whileTuple{Vararg{T}} where T
would need to be interpreted asInvariantTuple{T,T,...} where T
. Hence we would need two different syntaxes forInvariantTuple{Vararg}
to distinguish these cases. MaybeInvariantTuple{Vararg{T} where T}
forTuple{Vararg{Any}}
vs.InvariantTuple{Vararg{T}} where T
forTuple{Vararg{T}} where T
?I have no idea whether we actually want to go down this road eventually, but I feel like it at least deserves discussion. (CC @andyferris)
The text was updated successfully, but these errors were encountered: