-
-
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
RFC: Do not consider iterators as scalars in broadcast #25356
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -53,7 +53,7 @@ end | |
abstract type IteratorSize end | ||
struct SizeUnknown <: IteratorSize end | ||
struct HasLength <: IteratorSize end | ||
struct HasShape <: IteratorSize end | ||
struct HasShape{N} <: IteratorSize end | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
struct IsInfinite <: IteratorSize end | ||
|
||
""" | ||
|
@@ -63,8 +63,9 @@ Given the type of an iterator, return one of the following values: | |
|
||
* `SizeUnknown()` if the length (number of elements) cannot be determined in advance. | ||
* `HasLength()` if there is a fixed, finite length. | ||
* `HasShape()` if there is a known length plus a notion of multidimensional shape (as for an array). | ||
In this case the [`size`](@ref) function is valid for the iterator. | ||
* `HasShape{N}()` if there is a known length plus a notion of multidimensional shape (as for an array). | ||
In this case `N` should give the number of dimensions, and the [`size`](@ref) function is valid | ||
for the iterator. | ||
* `IsInfinite()` if the iterator yields values forever. | ||
|
||
The default value (for iterators that do not define this function) is `HasLength()`. | ||
|
@@ -75,7 +76,7 @@ result, and algorithms that resize their result incrementally. | |
|
||
```jldoctest | ||
julia> Base.iteratorsize(1:5) | ||
Base.HasShape() | ||
Base.HasShape{1}() | ||
|
||
julia> Base.iteratorsize((2,3)) | ||
Base.HasLength() | ||
|
@@ -110,7 +111,7 @@ Base.HasEltype() | |
iteratoreltype(x) = iteratoreltype(typeof(x)) | ||
iteratoreltype(::Type) = HasEltype() # HasEltype is the default | ||
|
||
iteratorsize(::Type{<:AbstractArray}) = HasShape() | ||
iteratorsize(::Type{<:AbstractArray{T, N}}) where {T, N} = HasShape{N}() | ||
iteratorsize(::Type{Generator{I,F}}) where {I,F} = iteratorsize(I) | ||
length(g::Generator) = length(g.iter) | ||
size(g::Generator) = size(g.iter) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -705,11 +705,15 @@ julia> collect(Iterators.product(1:2,3:5)) | |
""" | ||
product(iters...) = ProductIterator(iters) | ||
|
||
iteratorsize(::Type{ProductIterator{Tuple{}}}) = HasShape() | ||
iteratorsize(::Type{ProductIterator{Tuple{}}}) = HasShape{0}() | ||
iteratorsize(::Type{ProductIterator{T}}) where {T<:Tuple} = | ||
prod_iteratorsize( iteratorsize(tuple_type_head(T)), iteratorsize(ProductIterator{tuple_type_tail(T)}) ) | ||
|
||
prod_iteratorsize(::Union{HasLength,HasShape}, ::Union{HasLength,HasShape}) = HasShape() | ||
prod_iteratorsize(::HasLength, ::HasLength) = HasShape{2}() | ||
prod_iteratorsize(::HasLength, ::HasShape{N}) where {N} = HasShape{N+1}() | ||
prod_iteratorsize(::HasShape{N}, ::HasLength) where {N} = HasShape{N+1}() | ||
prod_iteratorsize(::HasShape{M}, ::HasShape{N}) where {M,N} = HasShape{M+N}() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think these will be inferrable. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
f(::AbstractArray{S,M}, ::AbstractArray{T,N}) where {S,T,M,N} = Array{M+N}()
@code_warntype f([1], [1 2]) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Oh, you're right. |
||
|
||
# products can have an infinite iterator | ||
prod_iteratorsize(::IsInfinite, ::IsInfinite) = IsInfinite() | ||
prod_iteratorsize(a, ::IsInfinite) = IsInfinite() | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -57,3 +57,20 @@ struct RangeStepRegular <: TypeRangeStep end # range with regular step | |
struct RangeStepIrregular <: TypeRangeStep end # range with rounding error | ||
|
||
TypeRangeStep(instance) = TypeRangeStep(typeof(instance)) | ||
|
||
## iterable trait | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This code isn't used in the PR currently, but it illustrates the alternative approach based on a trait rather than o BTW, I've noted an inconsistency in the naming of traits: we have There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Uppercase makes the most sense when what will be returned is a type-instance---for example, There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👎 Adding a new thing every iterable type needs to define is not ideal. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe not "ideal", but not the end of the world either IMHO given that you need to define several methods anyway. And if we really don't want to add another trait, we can add a new type to Anyway I'm all ears if somebody has a better solution that works (i.e. is inferrable, see @timholy's comment above). |
||
""" | ||
TypeIterable(instance) | ||
TypeIterable(T::Type) | ||
|
||
Return `IsIterable()` if object `instance`` or type `T` is iterable, and | ||
`NotIterable()` if it is not. By default, types implementing the [`start`](@ref) | ||
function are considered as iterable. | ||
""" | ||
abstract type TypeIterable end | ||
struct IsIterable <: TypeOrder end | ||
struct NotIterable <: TypeOrder end | ||
|
||
TypeIterable(instance) = TypeIterable(typeof(instance)) | ||
TypeIterable(::Type{T}) where {T} = | ||
method_exists(start, Tuple{T}) ? IsIterable() : NotIterable() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not inferrable, and thus might be pretty bad for code like
Unfortunately #16422 wouldn't help.
At the same time, I recognize that any problematic type can be optimized by adding a specific defintion.
At a minimum we may have to change the
typeof
calls incollect_styles
toCore.Typeof
, and then specializeThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could require iterators to define a trait. That would be more consistent with what we do elsewhere, and that wouldn't be a terrible burden either. I had contemplated adding an
NotIterable
type toBase.iteratorsize
, but that could also be a separate function likeisiterable
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On balance I think it's better to require iterators to define a trait, and make scalars the default.
One slightly-crazy thought is that the trait name could be the output of
BroadcastStyle
. But on balance I'm not sure this is a good idea, because there may be reasons to have things that act like scalars that don't returnScalar()
. It's probably better to have a separateisiterable
trait.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@vtjnash mentioned here that
method_exists
could be made inferable now, presumably circumnavigating the problem mentioned in #16422.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It could, but it hasn't been in the past since we don't want the coupling. Also, #25261 will break this.