HasLength trait for some Flatten iterators#22691
Conversation
base/iterators.jl
Outdated
| _flatteneltype(I, ::HasEltype) = iteratoreltype(eltype(I)) | ||
| _flatteneltype(I, et) = EltypeUnknown() | ||
|
|
||
| flatten_iteratorsize(::Union{HasShape, HasLength}, b::Type{T}) where {T<:Tuple} = HasLength() |
There was a problem hiding this comment.
Split lines at 92 characters. Here, you can do ::Type{<:Tuple} to save some space.
base/iterators.jl
Outdated
| flatten_iteratorsize(::Union{HasShape, HasLength}, b::Type{T}) where {T<:Tuple} = HasLength() | ||
| flatten_iteratorsize(::Union{HasShape, HasLength}, b::Type{T}) where {T<:Number} = HasLength() | ||
| flatten_iteratorsize(a, b) = SizeUnknown() | ||
| flatten_length(f, ::Type{T}) where {T<:Tuple} = isleaftype(T) ? nfields(T)*length(f.it) : throw(ArgumentError("Cannot compute length of a tuple-type which is not a leaf-type")) |
There was a problem hiding this comment.
Line length here as well. Perhaps write this method definition long-form with an if-else?
base/iterators.jl
Outdated
| flatten_length(f, ::Type{T}) where {T<:Tuple} = isleaftype(T) ? nfields(T)*length(f.it) : throw(ArgumentError("Cannot compute length of a tuple-type which is not a leaf-type")) | ||
| flatten_length(f, ::Type{T}) where {T<:Number} = length(f.it) | ||
| length(f::Flatten{I}) where {I} = flatten_length(f, eltype(I)) | ||
| iteratorsize(::Type{Flatten{I}}) where {I} = isleaftype(eltype(I)) ? flatten_iteratorsize(iteratorsize(I), eltype(I)) : SizeUnknown() # eltype is always defined |
There was a problem hiding this comment.
Likewise here re. line length. Perhaps also better as a long-form method definition with an if-else? :)
test/iterators.jl
Outdated
| @test collect(flatten(Any[flatten(Any[1:2, 6:5]), flatten(Any[6:7, 8:9])])) == Any[1,2,6,7,8,9] | ||
| @test collect(flatten(Any[2:1])) == Any[] | ||
| @test eltype(flatten(UnitRange{Int8}[1:2, 3:4])) == Int8 | ||
| @test length(flatten(zip(1:3,4:6))) == 6 |
There was a problem hiding this comment.
A space between the arguments to zip would aid visual parsing :).
test/iterators.jl
Outdated
| @test eltype(flatten(UnitRange{Int8}[1:2, 3:4])) == Int8 | ||
| @test length(flatten(zip(1:3,4:6))) == 6 | ||
| @test_throws ArgumentError collect(flatten(Any[])) | ||
| @test_throws ArgumentError length(flatten(NTuple[(1,),()])) # #16680 |
There was a problem hiding this comment.
Likewise here, a space following the comma between Tuples might make this easier to parse visually? :)
There was a problem hiding this comment.
For future reference: https://en.wikipedia.org/wiki/Klempen
| flatten_length(f, ::Type{T}) where {T<:Tuple} = isleaftype(T) ? nfields(T)*length(f.it) : throw(ArgumentError("Cannot compute length of a tuple-type which is not a leaf-type")) | ||
|
|
||
| function flatten_length(f, ::Type{<:Tuple}) | ||
| if !isleaftype(T) |
There was a problem hiding this comment.
T is not defined anymore, adding T::Type{<:Tuple} in the signature should work
There was a problem hiding this comment.
Isn't function flatten_length(f, ::Type{T}) where T<:Tuple better?
There was a problem hiding this comment.
Sorry, I did not pay attention here
There was a problem hiding this comment.
No problem!
@fredrikekre you mean better in style, or... in efficiency?
There was a problem hiding this comment.
ok, I thought T::Type may be less efficient than ::Type{T} where T, but that it was not the case for T::Type{<:Tuple} which should be specialized for each different type...
base/iterators.jl
Outdated
| end | ||
| nfields(T)*length(f.it) | ||
| end | ||
| flatten_length(f, ::Type{T}) where {T<:Number} = length(f.it) |
There was a problem hiding this comment.
flatten_length(f, ::Type{<:Number}) = length(f.it) ? (just a suggestion!)
| nfields(T)*length(f.it) | ||
| end | ||
| flatten_length(f, ::Type{T}) where {T<:Number} = length(f.it) | ||
| length(f::Flatten{I}) where {I} = flatten_length(f, eltype(I)) |
There was a problem hiding this comment.
For non-(number, tuple), this will throw a MethodError right? why not a fall-ball flatten_length which throws a more informative message?
test/iterators.jl
Outdated
| @test length(flatten(zip(1:3, 4:6))) == 6 | ||
| @test_throws ArgumentError collect(flatten(Any[])) | ||
| @test_throws ArgumentError length(flatten(NTuple[(1,), ()])) # #16680 | ||
| @test_throws ArgumentError length(flatten([[1],[1]])) |
base/iterators.jl
Outdated
| end | ||
| flatten_length(f, ::Type{<:Number}) = length(f.it) | ||
| flatten_length(f, T) = throw(ArgumentError( | ||
| "Iterates of the argument to Flatten are not known to have constant length")) |
There was a problem hiding this comment.
- Strange indentation, I think this would be better with only 4 spaces
- "Iteratees" wouldn't be the correct word here?
- and as you have a
T, you might as well report it in the message, e.g."Iteratees (of type $T) ... Flattenis private no? could be better to useflatten.
There was a problem hiding this comment.
Iteratees would sound funny, even if possibly correct
| flatten_iteratorsize(a, b) = SizeUnknown() | ||
|
|
||
| function flatten_length(f, ::Type{T}) where {T<:Tuple} | ||
| if !isleaftype(T) |
There was a problem hiding this comment.
Do you know if this check is statically resolved?
There was a problem hiding this comment.
From experience: yes. Can we test that?
base/iterators.jl
Outdated
|
|
||
| function iteratorsize(::Type{Flatten{I}}) where {I} | ||
| if isleaftype(eltype(I)) | ||
| flatten_iteratorsize(iteratorsize(I), eltype(I)) # eltype is always defined |
There was a problem hiding this comment.
With this, you seem to prevent have HasLength for non-concrete numbers, e.g. Union{Int,UInt}[1, 0x2]. Why not move this test to flatten_iteratorsize for tuples, and having simply
iteratorsize(::Type{Flatten{I}}) where {I} = flatten_iteratorsize(iteratorsize(I), eltype(I))
? IOW, similar to what you did for flatten_length.
| @test collect(flatten(Any[flatten(Any[1:2, 6:5]), flatten(Any[6:7, 8:9])])) == Any[1,2,6,7,8,9] | ||
| @test collect(flatten(Any[2:1])) == Any[] | ||
| @test eltype(flatten(UnitRange{Int8}[1:2, 3:4])) == Int8 | ||
| @test length(flatten(zip(1:3, 4:6))) == 6 |
There was a problem hiding this comment.
you could add a test for numbers too
base/iterators.jl
Outdated
| throw(ArgumentError( | ||
| "Cannot compute length of a tuple-type which is not a leaf-type")) | ||
| end | ||
| nfields(T)*length(f.it) |
There was a problem hiding this comment.
it seems that nfields was deprecated it favor of fieldcount
There was a problem hiding this comment.
this shouldn't have passed tests then. is this covered and running?
There was a problem hiding this comment.
As far as I understand, this code path is tested. @mschauer do you know why this was not noticed by CI? (did the change happened after CI was run?)
There was a problem hiding this comment.
I think CI picked up the change, but I did not pick it up (locally on my machine a test regarding output format of arrays is broken recently).
|
LGTM. Just one question: you are testing tuples with |
|
Mixed tuples are not NTuples, for this reason I went with the test for leaftype. NTuples which are non-leaf-types could be covered additionally. |
|
Squashed and rebased. |
| iteratorsize(::Type{Flatten{I}}) where {I} = flatten_iteratorsize(iteratorsize(I), eltype(I)) | ||
|
|
||
| function flatten_length(f, ::Type{T}) where {T<:Tuple} | ||
| if !isleaftype(T) |
There was a problem hiding this comment.
it's not very intuitive that isleaftype is the right predicate for this - are Vararg and nesting coupled to this?
|
hold on at least until there's a good answer for the question above. is this correct even for Tuples with Vararg or nested tuples? |
If a tuple type |
|
@tkelman good to go now W.R.T. the |
|
One CI failure is a timeout, the other seems unrelated. |
It is worthwhile to give some
Flatten-ed iterators the traitHasLength, if the elements of the iterator which gets flattened are iteratables with type-static length, for exampleTuples. This PR does this for iterators with elements of typeTupleandNumber.This functionality can then be extended by packages defining iterators with type-encoded length.
For example it allows https://github.com/JuliaArrays/StaticArrays.jl to flatten a
VectorofSVector's efficiently.Replaces #16680