Custom index arithmetic

This is a clarifying question about custom indexing.

If x::AbstractVector has a conforming interface, can I assume that that the valid indexes are firstindex(x):lastindex(x), ie they are

  1. integers (so nothing wacky like Float64s or Symbol),
  2. forming a contiguous range, so I can do arithmetic on them, at least index += some_step.

This is for an algorithm that I find difficult to express in an iterator form using eachindex.

Sorry if this is obvious and already clearly discussed in the manual.

1 Like

We currently assume that axes(A::AbstractArray, i) <: AbstractUnitRange in lots of code (so even tighter than your range assumption), and we also expect axes to have integer elements. I think you’re safe to assume the same. A custom “array” that breaks this will already be in lots of deep water and I’d say they’re fine swimming out there on their own.

4 Likes

Thanks. Do you think it might be worth documenting this explicitly? I would then open an issue.

2 Likes

I also asked myself from time to time what should be assumed about AbstractArray indices. I guess its a common question, when writing generic code. So I think it would be good to document this.

2 Likes

After a close reading of the Interfaces and Arrays with custom indices documentation, I saw that both are pretty specific about axes returning (tuples of) AbstractUnitRange.

I am under the impression that this allows non-integer types though: anything goes that has a oneunit, eg I could define

struct RealUnitRange{T <: Real} <: AbstractUnitRange{T}
    start::T
    stop::T
    len::Int
end

RealUnitRange(start, length::Integer) = RealUnitRange(start, start + length, length)

function Base.getindex(rur::RealUnitRange, i::Int)
    @boundscheck @assert 1 ≤ i ≤ rur.len
    rur.start + i
end

# ...

and it would be valid for axis to return this. Or the same exercise with, for example, Dates.Date.

This means that I cannot rely on arithmetic with integers; I should at least increment with multiples of oneunit. Also, in theory, I could be worried about floating point error and similar, since the eltype of the range could be anything.

Is this a correct interpretation?

Interesting — you might be right. It’s not something that I’ve personally seen or tried, so I wouldn’t be surprised if such an array would fail in some situations.

One thing we do assume is that A[axes(A)…] == A. This means that it also needs to be a valid index type. We also sometimes grab axes from one array and use them to index into another — so it needs to be a valid index type for all array types. It may be possible to extend to_index to make that work for a non-integer, but I’d still be surprised if everything just worked.

Perhaps restricting to AbstractUnitRange{<:Integer} for the time being could be a reasonable limitation for the time being.

I think this is worth a clarification, so I will open an issue.

https://github.com/JuliaLang/julia/issues/29062