Greetings and apologies if this is very basic.
I am trying to create a custom type subtyped from AbstractArray
to dispatch on it in place of a dense array at times. I’ve been following instructions in the manual for specifying arrays with custom indices. I can make dereferencing and iteration work, but can’t get pairs()
to function properly, it stubbornly produces unitbased indices, but unlike enumerate()
, also pairs them with the nonexistant elements.
Here is my MWE
import Base: length,size,iterate,getindex,axes, has_offset_axes
struct myrange <: AbstractVector{Int}
LStart::Int
LStop::Int
end
IndexStyle(::Type{myrange}) = IndexLinear()
length(r::myrange) = r.LStopr.LStart+1
size(r::myrange) = (length(r),)
function Base.getindex(r::myrange,i::Integer)
@boundscheck r.LStart≤ i ≤ r.LStop  throw(BoundsError(r,i))
i
end
firstindex(r::myrange) = r.LStart
lastindex(r::myrange) = r.LStop
The manual says it’s better to create a custom class for the indices range, but suggests it’s possible to use UnitRange
. This is the approach I follow as I don’t need anything fancier
@inline Base.axes(r::myrange) = (UnitRange(r.LStart,r.LStop),)
Base.has_offset_axes(::myrange) = true
I also overloaded similar()
as suggested in the manual, but this does not seem to matter.
Now, this seems to work:
julia> axes(z)[1]
4:8
But when attempting to use the class:
julia> LinearIndices(axes(z))
5element LinearIndices{1,Tuple{UnitRange{Int64}}}:
1
2
3
4
5
and consequently
julia> pairs(IndexLinear(), z)
pairs(::myrange{Int64}) with 5 entriesError showing value of type Base.Iterators.Pairs{Int64,Integer,LinearIndices{1,Tuple{UnitRange{Int64}}},myrange{Int64}}:
ERROR: BoundsError: attempt to access 5element myrange{Int64} with indices 4:8 at index [1]
The post mortem reveals that pairs()
has indeed got the wrong indices from LinearIndices
:
julia> properties(pairs(IndexLinear(), z))
2element Array{Tuple{Symbol,AbstractArray{T,1} where T},1}:
(:data, Integer[#undef, #undef, #undef, 4, 5])
(:itr, [1, 2, 3, 4, 5])
But curiously
julia> LinearIndices(axes(z)).indices
(4:8,)
One might think that overloading LinearIndices
for the custom class is required, but do observe this working perfectly:
zz=OffsetArray(4:8,4:8)
LinearIndices(axes(zz))
pairs(IndexLinear(), zz)
@which LinearIndices(axes(zz))
shows that both myrange
and OffsetArray
get dispatched to the same (default) LinearIndices
constructor (in Base at indices.jl:444)
So my questions are:

how is it possible that
LinearIndices
carries wrong indices despite explicitly containing the correct ones in the eponymous field? What is the difference betweenaxes(::myrange)
andaxes(::OffsetArray)
that causes the divergence? 
what is the minimal way to modify my class to avoid the above behaviour?
I suspect there may be hints in the paragraph below, but I am not getting conversion errors, nor does this explain the mystery of LinearIndices(::myrange)
By this definition, 1dimensional arrays always use Cartesian indexing with the array’s native indices. To help enforce this, it’s worth noting that the index conversion functions will throw an error if shape indicates a 1dimensional array with unconventional indexing (i.e., is a
Tuple{UnitRange}
rather than a tuple ofOneTo
). For arrays with conventional indexing, these functions continue to work the same as always.