You can do it without needing a reference to a by making a struct called Last or something, and defining the appropriate methods (Base.to_indices among others). This is how Base’s Colon() works as well as InvertedIndices.jl’s Not. I played around with this a couple of years ago to make ModularIndices.jl which provides a Mod struct to do wrap-around indexing:
julia> using ModularIndices
julia> A = rand(3)
3-element Array{Float64,1}:
0.523471984061487
0.3975791533002422
0.3230510641200286
julia> A[Mod(4)]
0.523471984061487
julia> A[4]
ERROR: BoundsError: attempt to access 3-element Array{Float64,1} at index [4]
Stacktrace:
[1] getindex(::Array{Float64,1}, ::Int64) at ./array.jl:729
[2] top-level scope at none:0
This is not very useful because as I later learned you can do A[mod1(4, end)] without a package to get this kind of behavior! (Although of course that syntax is only valid within the indexing expression, so the advantage of the package is that you can do ind = Mod(4) without a reference to the array, same as in the question here). But anyway, you could do the same thing with a Last struct so that say Last(n) would give v[Last(n)] == v[end - n].
edit: I said the methods needed were “Base.to_indices among others”, but after looking again at the source code of ModularIndices, it looks like that’s the only method needed! Though probably Base.checkbounds is good to define too, to say when the access is inbounds or not. It’s only a few lines of code so I think it should be pretty easy to define your own. InvertedIndices is a bit longer and more complicated because it’s doing a more complicated operation.
Unfortunately this needs a reference to a which isn’t always available. (In Python I can specify “next-to-last index” without needing that reference – just ix = -2.)
The package EndpointRanges.jl does exactly this. Plus methods to allow things like iend-2 to adjust what’s stored. (Although maybe it was written before Base handled this? Certainly before begin worked.)
Yeah, exactly. Base.to_indices is just a way to intercept indexing at a different level to be able to make it work for e.g. any dimension of a multidimensional array.
I think it would be nice for Julia to move away from being “indexed from 1” toward “abstract” indexing: currently people usually use a[3] even when they mean more generally a[begin+2]. I wonder if there’s a way to encourage index-generic programming like this.
It is up to the coder at this point; I imagine that older code will be updated on demand (by someone making a PR when they need it), but pretty much all new code should the relevant generic constructs.