Skip to content
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

Add promote_typejoin() and use it instead of typejoin() to preserve Union{T, Nothing/Missing} #25553

Merged
merged 7 commits into from
Jan 27, 2018

Conversation

nalimilan
Copy link
Member

This is a continuation of #24332, which cannot be reopened. It's an alternative to adding a more general strict/exact promotion mechanism (#25423). It achieves the same goal, i.e. it ensures that we use Union{T, Nothing/Missing} instead of Any with collect, map, broadcast and with Tuple and NamedTuple promotion.

test/tuple.jl Outdated
@test x isa Vector{Tuple{Int,Union{Int,T}}}

y = map(v -> (v[1], v[2]), [(1, T()), (1, 2)])
@test y isa Vector{Tuple{Int64,Union{T,Int64}}}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These Int64 explain the 32-bit CI failures. Will fix in the next push.

@StefanKarpinski
Copy link
Sponsor Member

Very nearly a CI straight flush. Win32 failure was a timeout.

@JeffreySarnoff
Copy link
Contributor

😎

@nalimilan nalimilan added the triage This should be discussed on a triage call label Jan 25, 2018
@@ -1873,7 +1873,7 @@ function limit_tuple_type_n(@nospecialize(t), lim::Int)
p = t.parameters
n = length(p)
if n > lim
tail = reduce(typejoin, Bottom, Any[p[lim:(n-1)]..., unwrapva(p[n])])
tail = reduce(promote_join, Bottom, Any[p[lim:(n-1)]..., unwrapva(p[n])])
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not be changed in inference.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right. I thought this was needed to change the promotion of tuples, but clearly I've been overzealous. I've reverted it.

@JeffBezanson
Copy link
Sponsor Member

Seems basically right.

@JeffBezanson JeffBezanson added this to the 1.0 milestone Jan 25, 2018
@JeffBezanson JeffBezanson removed the triage This should be discussed on a triage call label Jan 25, 2018
…, Void/Missing}

This allows for a more efficient code and array layout than using Any.
…priate

typejoin() is now used only for inference, and promote_join() everywhere else
to choose the appropriate element type for a collection.
base/tuple.jl Outdated
end
return r
end

# promotion

promote_rule(::Type{S}, ::Type{T}) where {S<:Tuple, T<:Tuple} =
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be removed. Changing the type of [(1,), (1.0,)] seems to be outside of the goal here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The goal is to have [(1,), (missing,)] isa Vector{Tuple{Union{Int,Missing}} rather than Tuple{Any}. I agree that's not totally required in this PR, but it makes a lot of sense to avoid an inconsistency with map(identity, [(1,), (missing,)]). That's also consistent with [[1], [missing]]. In general I don't see why tuples shoudn't implement promotion, like other containers.

Another solution is to use promote_join rather than promote_type. This could be done either here (i.e. special-case tuples promotion to just use promote_join), or more generally when concatenating arrays. I would prefer a solution which applies consistently to all types.

Anyway I've added a commit to revert this for now, so that we can address these issues separately.

@@ -55,6 +55,12 @@ indexed_next(t::NamedTuple, i::Int, state) = (getfield(t, i), i+1)
isempty(::NamedTuple{()}) = true
isempty(::NamedTuple) = false

promote_rule(::Type{NamedTuple{n, S}}, ::Type{NamedTuple{n, T}}) where {n, S, T} =
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would also remove this for now. Can be raised as a separate issue if necessary.

Remove promotion tests, revert unneeded Any[ specifications.
Also add @_pure_meta to two methods to preserve current behavior
(without it, vcat((1,), (1.0,)) is not inferred as Vector{Tuple{Real}},
which is caught by a test which previously would not detect this problem).
@nalimilan
Copy link
Member Author

Thanks for the review. I must say I'm not very happy with the name promote_join, since promote operates on values but promote_join operates on types. Should it be promote_typejoin? Anyway it's internal so we can change it later.

@JeffreySarnoff
Copy link
Contributor

promote_jointype?

function typejoin(@nospecialize(a), @nospecialize(b))
typejoin(@nospecialize(a), @nospecialize(b)) = (@_pure_meta; join_types(a, b, typejoin))

function join_types(@nospecialize(a), @nospecialize(b), f::Function)
@_pure_meta
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not pure anymore (due to the abstractly typed f::Function parameter)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I always get this wrong... Fixed.

Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't we need this function to be pure though?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have no idea. We could use a loop inside @eval to define two separate functions which would be pure, without duplicating the code.

@nalimilan
Copy link
Member Author

I've renamed promote_join to promote_typejoin. I find this more consistent with both promote_type and typejoin (promote_jointype would have had the drawback of being slightly different from the latter for no clear reason). Better suggestions welcome (typejoin_promote?...).

@JeffreySarnoff
Copy link
Contributor

🆗

@@ -237,7 +237,7 @@ eltype(::Type{TwicePrecision{T}}) where {T} = T

promote_rule(::Type{TwicePrecision{R}}, ::Type{TwicePrecision{S}}) where {R,S} =
TwicePrecision{promote_type(R,S)}
promote_rule(::Type{TwicePrecision{R}}, ::Type{S}) where {R,S} =
promote_rule(::Type{TwicePrecision{R}}, ::Type{S}) where {R,S<:Number} =
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure this is right. For some reason TwicePrecision{T}'s parameter does not need to be a Number. @timholy Why is that?

@JeffBezanson JeffBezanson merged commit f58d2cf into master Jan 27, 2018
@JeffBezanson JeffBezanson deleted the nl/typejoin branch January 27, 2018 17:17
@nalimilan nalimilan changed the title Add promote_join() and use it instead of typejoin() to preserve Union{T, Nothing/Missing} Add promote_typejoin() and use it instead of typejoin() to preserve Union{T, Nothing/Missing} Jan 29, 2018
@@ -5,61 +5,66 @@
"""
typejoin(T, S)

Compute a type that contains both `T` and `S`.

Return the closest common ancestor of `T` and `S`, i.e. the narrowest type from which
Copy link
Member

@andreasnoack andreasnoack Jan 1, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this docstring could be made clearer. I'm trying to figure out exactly what typejoin and promote_typejoin are doing and I don't think the current descriptions are complete. The current docstring for typejoin states that it returns

the narrowest type from which they both [T and S] inherit

but that must always be Union{T,S}. However, my understanding is that typejoin doesn't return unions and that promote_typejoin was introduced exactly because of that. So what is this function returning? My guess would be something like

T if S<:T
S if T<:S
else min(TS) for which T<:TS and S<:TS and isabstract(TS)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I just copied "closest common ancestor" from a comment and tried to make it a little more explicit. You're right that typejoin returns an abstract supertype and never a Union AFAIK.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the definition I stated above is correct then it might be fine to just define ancestor to cover only declared abstract types (and concrete types), i.e. exclude unions. It probably makes sense the declared subset of the type lattice is a tree so ancestor feels natural.

The name of the function would be slightly a misnomer as the lattice join is the least upper bound, but I guess some upper bound is fine for our use.

Examples would then also be helpful in the docstring. I wouldn't mind preparing the PR but I would need a confirmation that my understanding of the function is correct.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants