I'm entirely at fault for this one, but as a public and documented interface we're fairly constrained in the non-breaking changes we can make. There are two functions and four (!!) levels of indirection here, making things particularly hard to reason about.
to_indices(A, I::Tuple) — the main entry point. Note that I is a tuple — not a vararg. This is different from nearly all of our other indexing functions.
- At this point, simple indices that only contain
Integers get converted straight away (since calling indices(A) is expensive in some cases)
to_indices(A, indices(A), I::Tuple) — this is the general form, it recurses through the two types of indices in sync.
- At this point, multidimensional indices are expanded since they need to walk through multiple dimensions of the parent
- Also indices like
: that need access to the parent's indices in order to expand
Base.to_index(A::Specialized, I[1]) — for each element of the tuple of passed indices that makes it this far, we allow arrays to specialize their behaviors.
Base.to_index(I[1]) — and, finally, we allow the index itself to implement a conversion.
Note that Base.to_index is not exported but it's documented as a function you can specialize.
I think we should simplify this for 1.0. The reasons for this design are largely historical, but some of these are still true:
- We used to need to support partial linear indexing, which made the
indices(A) computation hard for final :s.
- Ambiguities used to be a PITA. Now I think you'd want to know when there's a conflict and your specialization may not take effect.
- Walking through
indices(A) and I together isn't trivial — you need to use something like Base._maybe_tail and such. Unlike the other two, I don't have a good solution for this one, and it makes documenting how to do this very complicated.
Not sure about the best way to resolve this (or if it's even worth futzing about), but I figured I'd open an issue to see what others think.
I'm entirely at fault for this one, but as a public and documented interface we're fairly constrained in the non-breaking changes we can make. There are two functions and four (!!) levels of indirection here, making things particularly hard to reason about.
to_indices(A, I::Tuple)— the main entry point. Note thatIis a tuple — not a vararg. This is different from nearly all of our other indexing functions.Integersget converted straight away (since callingindices(A)is expensive in some cases)to_indices(A, indices(A), I::Tuple)— this is the general form, it recurses through the two types of indices in sync.:that need access to the parent's indices in order to expandBase.to_index(A::Specialized, I[1])— for each element of the tuple of passed indices that makes it this far, we allow arrays to specialize their behaviors.Base.to_index(I[1])— and, finally, we allow the index itself to implement a conversion.Note that
Base.to_indexis not exported but it's documented as a function you can specialize.I think we should simplify this for 1.0. The reasons for this design are largely historical, but some of these are still true:
indices(A)computation hard for final:s.indices(A)andItogether isn't trivial — you need to use something likeBase._maybe_tailand such. Unlike the other two, I don't have a good solution for this one, and it makes documenting how to do this very complicated.Not sure about the best way to resolve this (or if it's even worth futzing about), but I figured I'd open an issue to see what others think.