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

HasLength trait for some Flatten iterators #22691

Merged
merged 1 commit into from
Aug 3, 2017

Conversation

mschauer
Copy link
Contributor

@mschauer mschauer commented Jul 6, 2017

It is worthwhile to give some Flatten-ed iterators the trait HasLength, if the elements of the iterator which gets flattened are iteratables with type-static length, for example Tuples. This PR does this for iterators with elements of type Tuple and Number.

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 Vector of SVector's efficiently.
Replaces #16680

iteratoreltype(::Type{Flatten{I}}) where {I} = _flatteneltype(I, iteratoreltype(I))
_flatteneltype(I, ::HasEltype) = iteratoreltype(eltype(I))
_flatteneltype(I, et) = EltypeUnknown()

flatten_iteratorsize(::Union{HasShape, HasLength}, b::Type{T}) where {T<:Tuple} = HasLength()
Copy link
Member

Choose a reason for hiding this comment

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

Split lines at 92 characters. Here, you can do ::Type{<:Tuple} to save some space.

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"))
Copy link
Member

Choose a reason for hiding this comment

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

Line length here as well. Perhaps write this method definition long-form with an if-else?

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
Copy link
Member

Choose a reason for hiding this comment

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

Likewise here re. line length. Perhaps also better as a long-form method definition with an if-else? :)

@@ -347,7 +347,9 @@ end
@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
Copy link
Member

Choose a reason for hiding this comment

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

A space between the arguments to zip would aid visual parsing :).

@test_throws ArgumentError collect(flatten(Any[]))
@test_throws ArgumentError length(flatten(NTuple[(1,),()])) # #16680
Copy link
Member

@Sacha0 Sacha0 Jul 6, 2017

Choose a reason for hiding this comment

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

Likewise here, a space following the comma between Tuples might make this easier to parse visually? :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@ararslan ararslan added the collections Data structures holding multiple items, e.g. sets label Jul 6, 2017
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)
Copy link
Member

Choose a reason for hiding this comment

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

T is not defined anymore, adding T::Type{<:Tuple} in the signature should work

Copy link
Member

Choose a reason for hiding this comment

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

Isn't function flatten_length(f, ::Type{T}) where T<:Tuple better?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry, I did not pay attention here

Copy link
Member

Choose a reason for hiding this comment

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

No problem!

@fredrikekre you mean better in style, or... in efficiency?

Copy link
Member

Choose a reason for hiding this comment

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

Both.

Copy link
Member

Choose a reason for hiding this comment

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

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...

end
nfields(T)*length(f.it)
end
flatten_length(f, ::Type{T}) where {T<:Number} = length(f.it)
Copy link
Member

Choose a reason for hiding this comment

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

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))
Copy link
Member

Choose a reason for hiding this comment

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

For non-(number, tuple), this will throw a MethodError right? why not a fall-ball flatten_length which throws a more informative message?

@test_throws ArgumentError collect(flatten(Any[]))
@test_throws ArgumentError length(flatten(NTuple[(1,), ()])) # #16680
@test_throws ArgumentError length(flatten([[1],[1]]))
Copy link
Member

Choose a reason for hiding this comment

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

Klempen!

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"))
Copy link
Member

Choose a reason for hiding this comment

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

  • 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) ...
  • Flatten is private no? could be better to use flatten.

Copy link
Contributor

Choose a reason for hiding this comment

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

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)
Copy link
Member

Choose a reason for hiding this comment

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

Do you know if this check is statically resolved?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

From experience: yes. Can we test that?


function iteratorsize(::Type{Flatten{I}}) where {I}
if isleaftype(eltype(I))
flatten_iteratorsize(iteratorsize(I), eltype(I)) # eltype is always defined
Copy link
Member

Choose a reason for hiding this comment

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

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, this would be better.

@@ -347,7 +347,10 @@ end
@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
Copy link
Member

Choose a reason for hiding this comment

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

you could add a test for numbers too

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes

throw(ArgumentError(
"Cannot compute length of a tuple-type which is not a leaf-type"))
end
nfields(T)*length(f.it)
Copy link
Member

Choose a reason for hiding this comment

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

it seems that nfields was deprecated it favor of fieldcount

Copy link
Contributor

Choose a reason for hiding this comment

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

this shouldn't have passed tests then. is this covered and running?

Copy link
Member

Choose a reason for hiding this comment

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

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?)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

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).

@rfourquet
Copy link
Member

LGTM. Just one question: you are testing tuples with isleaftype, to handle the case where the eltype is an NTuple which doesn't have a fixed number of elements. Is there another case than NTuple which doesn't have a fixed number of elements? otherwise, you could dispatch on NTuple instead, and allow non-leaf-types tuples which have a fixed length (for e.g. flatten(Tuple{Integer}[(1,), (3,)])) to benefit from your work.

@mschauer
Copy link
Contributor Author

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.

@mschauer
Copy link
Contributor Author

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)
Copy link
Contributor

Choose a reason for hiding this comment

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

it's not very intuitive that isleaftype is the right predicate for this - are Vararg and nesting coupled to this?

Copy link
Member

@rfourquet rfourquet left a comment

Choose a reason for hiding this comment

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

I will merge tomorrow if no-one objects. Finer handling of tuples can be added later if needed. Thanks @mschauer !

@tkelman
Copy link
Contributor

tkelman commented Jul 24, 2017

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?

@mschauer
Copy link
Contributor Author

mschauer commented Jul 25, 2017

it's not very intuitive that isleaftype is the right predicate for this - are Vararg and nesting coupled to this?

If a tuple type T is a leaftype, it can have instances t::T. For the instances
length(t) == nfields(typeof(t)) so length is type constant.

@rfourquet
Copy link
Member

@tkelman good to go now W.R.T. the isleaftype question?

@rfourquet
Copy link
Member

One CI failure is a timeout, the other seems unrelated.

@rfourquet rfourquet merged commit 5e32423 into JuliaLang:master Aug 3, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
collections Data structures holding multiple items, e.g. sets
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants