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

Fix eltype of flatten of tuple with non-2 length #56802

Merged
merged 7 commits into from
Dec 14, 2024

Conversation

jakobnissen
Copy link
Contributor

In 4c076c8, eltype of flatten of tuple was improved by computing a refined eltype at compile time. However, this implementation only worked for length-2 tuples, and errored for all others.
Generalize this to all tuples.

Closes #56783

In 4c076c8, eltype of flatten of tuple was improved by computing a refined
eltype at compile time. However, this implementation only worked for length-2
tuples, and errored for all others.
Generalize this to all tuples.
@jakobnissen jakobnissen added the bugfix This change fixes an existing bug label Dec 11, 2024
@nsajko nsajko added the collections Data structures holding multiple items, e.g. sets label Dec 12, 2024
base/iterators.jl Outdated Show resolved Hide resolved
base/iterators.jl Outdated Show resolved Hide resolved
base/iterators.jl Outdated Show resolved Hide resolved
@jakobnissen
Copy link
Contributor Author

Turns out we need to think about bootstrapping - iterators.jl is defined before a lot of Base (and the compiler), so several functions like mapreduce and tuple_type_tail are not defined at the time the module is defined (and used to compile the compiler).
I'm giving it a go now with the recursive - somewhat less elegant - implementation. If that still doesn't work, or is too complex for this small accuracy increase, we can just revert to before #55946

@nsajko
Copy link
Contributor

nsajko commented Dec 12, 2024

Turns out we need to think about bootstrapping

I second @martinholters suggestion for using afoldl above. The afoldl function is defined in operators.jl, which is included before iterators.jl.

@jakobnissen
Copy link
Contributor Author

That requires splatting the tuple, which might not be a good idea.

@nsajko
Copy link
Contributor

nsajko commented Dec 14, 2024

Splatting is only an issue for large tuples, in any case pretty sure splatting directly is preferable to calling tuple_type_tail, which seems like a monstrosity, and starts with a warning for incorrectness:

julia/base/tuple.jl

Lines 430 to 445 in ece1c70

function tuple_type_tail(T::Type)
@_foldable_meta # TODO: this method is wrong (and not :foldable)
if isa(T, UnionAll)
return UnionAll(T.var, tuple_type_tail(T.body))
elseif isa(T, Union)
return Union{tuple_type_tail(T.a), tuple_type_tail(T.b)}
else
T.name === Tuple.name || throw(MethodError(tuple_type_tail, (T,)))
if isvatuple(T) && length(T.parameters) == 1
va = unwrap_unionall(T.parameters[1])::Core.TypeofVararg
(isdefined(va, :N) && isa(va.N, Int)) || return T
return Tuple{Vararg{va.T, va.N-1}}
end
return Tuple{argtail(T.parameters...)...}
end
end

@nsajko
Copy link
Contributor

nsajko commented Dec 14, 2024

EDIT: this entire comment is incorrect. I misinterpreted/misused infer_effects - the inferred effects are always foldable simply because the given type signatures throw unconditionally.

Splatting is only an issue for large tuples

Actually, it doesn't seem to be an issue at all. Here are two variants on @martinholters' suggestion:

function f(::Type{Iterators.Flatten{I}}) where {I<:Union{Tuple,NamedTuple}}
    Base.afoldl((T, i) -> Base.promote_typejoin(T, eltype(i)), Union{}, fieldtypes(I)...)
end

function g(::Type{Iterators.Flatten{I}}) where {I<:Union{Tuple,NamedTuple}}
    function reducer(t::Type, i::Int)
        Base.promote_typejoin(t, eltype(fieldtype(I, i)))
    end
    Base.afoldl(reducer, Union{}, 1:fieldcount(I)...)
end

Both are foldable independently of tuple length:

julia> Base.infer_effects(f, Tuple{Type{Iterators.Flatten{<:Tuple}}})
(+c,+e,!n,+t,+s,+m,+u,+o,+r)

julia> Base.infer_effects(g, Tuple{Type{Iterators.Flatten{<:Tuple}}})
(+c,+e,!n,+t,+s,+m,+u,+o,+r)

julia> Base.infer_effects(f, Tuple{Type{Iterators.Flatten{<:NamedTuple}}})
(+c,+e,!n,+t,+s,+m,+u,+o,+r)

julia> Base.infer_effects(g, Tuple{Type{Iterators.Flatten{<:NamedTuple}}})
(+c,+e,!n,+t,+s,+m,+u,+o,+r)

Also note that the new variant works without splatting a tuple.

Co-authored-by: Neven Sajko <[email protected]>
@jakobnissen jakobnissen added the merge me PR is reviewed. Merge when all tests are passing label Dec 14, 2024
@DilumAluthge DilumAluthge merged commit 06f988b into JuliaLang:master Dec 14, 2024
8 checks passed
@DilumAluthge
Copy link
Member

Ah, I did not mean to merge this PR without getting @martinholters's approval first.

@martinholters Could you do a post-merge review of this PR? If there are any changes you suggest, we can either make those changes in a follow-up PR, or revert this PR if there are major changes needed.

@DilumAluthge DilumAluthge removed the merge me PR is reviewed. Merge when all tests are passing label Dec 14, 2024
@nsajko
Copy link
Contributor

nsajko commented Dec 14, 2024

FWIW the implementation that was merged is what @martinholters suggested: #56802 (comment)

@nsajko
Copy link
Contributor

nsajko commented Dec 14, 2024

Just made a PR to test foldability inference of eltype and other basic iteration interface functions for various iterator types: #56838

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bugfix This change fixes an existing bug collections Data structures holding multiple items, e.g. sets
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Regression in collect(Iterators.flatten((some_values..., ))) on the main branch
4 participants