-
-
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
Add CheckIndexStyle trait for bounds-checking #42029
base: master
Are you sure you want to change the base?
Conversation
The motivation here is to allow wrapper-types to exploit the same short- circuits of bounds-checking used for `AbstractRange`. This also fixes a previously-undiscovered bug in `checkindex` for `AbstractRange{Bool}` indices. It was hidden by the fact that `checkbounds` itself short-circuits before calling `checkindex` in this case. But since `checkindex` is an exported function, it should also give the correct result.
This seems like a good approach, but I'll have to check the specifics of each trait later.
I'm hoping to do a clean up of that file soon, but I think if we can get a non-breaking change in Base now this is a better place.
If we should instead be making these traits more generalizable than just to |
Gosh, at first glance this feels quite complicated for not much gain, but I see the ambiguity pain point in JuliaArrays/OffsetArrays.jl#255. The way we've typically dealt with this is through a layered approach:
All the pain is coming from that joint specialization in that final bullet. This not only adds another function name ( For example, the crux of the |
Co-authored-by: Lyndon White <[email protected]>
Perhaps I'm missing something, but I don't see how function checkindex(::Type{Bool}, inds::AbstractUnitRange, i::I) where {I<:AbsractVector}
if parent_type(I) <: I
b = true
for i in I
b &= checkindex(Bool, inds, i)
end
return b
else
return checkindex(Bool, inds, parent(i))
end
end We still have the default slow method but we don't have require explicit definitions of EDIT: Just looked at the OffsetArrays issue...not a simple wrapper. |
I'd also like to throw another issue here to consider: JuliaArrays/PaddedViews.jl#40 (comment) For a lazy array whose axes don't equal to its parent, there comes to two senses of |
The problem is this: if your dispatch rules work on both arguments, you have to solve ambiguities by remaining on the Pareto frontier of specificity. In contrast, with traits-based dispatch, you effectively work on one argument at a time, and hence have to only win the specificity battle in either a row or a column of the matrix defined by If there's some natural way to create a dispatch chain on |
Perhaps what we really want are traits that describe the composition of the index instead of the bounds checking. In this case we can tell from the element type whether it is |
Now that I look at this, it occurs to me that here the limitation is with |
I'd like to move this forward. @mbauman, since you're the one who had reservations, can you offer a more concrete proposal? I think I've done the best I can to address your reservations in the abstract. |
CheckIndexStyle(A::AbstractArray) = CheckIndexStyle(typeof(A)) | ||
CheckIndexStyle(::Type{Union{}}) = CheckIndexAll() | ||
CheckIndexStyle(::Type{<:AbstractArray{T}}) where T = T === Bool ? CheckIndexAxes() : CheckIndexAll() | ||
CheckIndexStyle(::Type{<:AbstractRange{T}}) where T = T === Bool ? CheckIndexAxes() : CheckIndexFirstLast() |
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.
Should we include things like FastContiguousSubArray
whose parent types have CheckIndexFirstLast
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.
Not sure I follow. This checks the indexes, not the array you are indexing. So if we checked only the first and last elements of idx
, this would be a problem:
julia> a = [1, 2, 37, 4]
4-element Vector{Int64}:
1
2
37
4
julia> b = 1:4
1:4
julia> sa = view(a, :)
4-element view(::Vector{Int64}, :) with eltype Int64:
1
2
37
4
julia> isa(sa, Base.FastContiguousSubArray)
true
julia> b[sa]
ERROR: BoundsError: attempt to access 4-element UnitRange{Int64} at index [[1, 2, 37, 4]]
Stacktrace:
[1] throw_boundserror(A::UnitRange{Int64}, I::Tuple{SubArray{Int64, 1, Vector{Int64}, Tuple{Base.Slice{Base.OneTo{Int64}}}, true}})
@ Base ./abstractarray.jl:651
[2] checkbounds
@ ./abstractarray.jl:616 [inlined]
[3] _getindex
@ ./multidimensional.jl:831 [inlined]
[4] getindex(A::UnitRange{Int64}, I::SubArray{Int64, 1, Vector{Int64}, Tuple{Base.Slice{Base.OneTo{Int64}}}, true})
@ Base ./abstractarray.jl:1170
[5] top-level scope
@ REPL[6]:1
That said, special rules might apply to AbstractSortedVector
but I am not sure we have one of those anywhere.
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.
The parent type would have to return CheckIndexFirstLast
, like this view(Wrapper(1:5), :)
.
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.
Oh, I think I get it, you mean if the index is a FastContiguousSubArray
. Sure, I'll add that.
So the crux of the issue is that both custom indices and custom axes want to implement My thought is to just have the following definitions: # checkindex deals specifically with axis-specific mumbo jumbo
checkindex(::Type{Bool}, axis, index) = issubset(index, axis)
checkindex(::Type{Bool}, axis::OffsetRange, index) = issubset(index, axis.value)
# issubset is mathematical, just deals with things as iterable bags-of-values with a handful of *optimizations*
issubset(index::Integer, r::AbstractUnitRange) = index in r
issubset(index::AbstractVector, r::AbstractUnitRange) = all(i in r for i in index)
issubset(index::CustomType, r::AbstractUnitRange) = ... The |
Thanks for filling in detail. Just a few responses:
That's the risk.
Example: in FFTViews, the axes have periodic boundary conditions. So there is no such thing as a BoundsError, unless the array is empty. So it has several specializations, and you can see it had to go to some effort to solve ambiguity problems already. As soon as you throw packages into the mix... My general view is that all the specialization that should be done on the indexes has already been done by Base; it's kind of like how we tell people to try to avoid specializing
In JuliaArrays/OffsetArrays.jl#255 it's actually |
One more shot: could we just go to a |
I think there could be a benefit to a more general Perhaps this would appease concerns that the current proposal is overly specific to checkindex |
I'll take another stab at this, but it may be a bit. |
I'm sorry I'm that fly-by stick-in-the-mud here. This does address a real problem, and indeed I've not come up with anything better. My reservations really do stem from the complexity size for what I see as a very narrow application, but that's all it is. Don't let me be the enemy of good here. |
No worries. Whatever we add, we have to keep supporting. Even though we're making opposite points, the reality is that there is not much daylight between us; it's worth giving this a fresh think in light of the comments here, but I may need to prioritize some other stuff for a couple of weeks. |
The motivation here is to allow wrapper-types to exploit the same short-
circuits of bounds-checking used for
AbstractRange
.This also fixes a previously-undiscovered bug in
checkindex
forAbstractRange{Bool}
indices. It was hidden by the fact thatcheckbounds
itself short-circuits before callingcheckindex
inthis case. But since
checkindex
is an exported function, it shouldalso give the correct result.
Example usage:
Wrapper(1:5)
is not necessarily anAbstractRange
(Wrapper
may also be able to handle other array types that aren't ranges), yet when used as an index vector it would be nice to be able to short-circuit the bounds checking to just the endpoints. You can specializecheckindex
, but that can be specialized on axis types rather than index types, so the trait is the way to go (xref JuliaArrays/OffsetArrays.jl#255).At the JuliaCon BoF on arrays there was discussion about adding more array traits. This might be viewed as a downpayment on that, but it will be interesting to see whether we think we get to a point of having too many. So imagine this is the first of several and see if you think it's worth it. An alternative would be to put this in ArrayInterface, but we'd have to duplicate a lot of stuff (there's already a fair amount of that in its "src/indexing.jl" file), and since
checkindex
is defined in Base it seems this is the right place.CC/review request @mbauman, @jishnub, @oxinabox, @Tokazama, @ChrisRackauckas, @chriselrod.