Any way to call `getindex(v, end)`?

If we have a vector v we can get the last element with v[end].
However that seems not to work with the equivalent getindex:

julia> v = rand(3);

julia> v[end]; # works

julia> getindex(v , end) #errors
ERROR: ParseError:
# Error @ REPL[37]:1:14
getindex(v , end) #errors
#            └ ── Expected `)`
Stacktrace:
 [1] top-level scope
   @ none:1

Anyway to get that going ?

The real use case is when using broadcasting with vector of vectors.
E.g. let’s say we want to get the last element from every vector

julia> vv = [rand(x) for x in rand(1:3, 3)]
3-element Vector{Vector{Float64}}:
 [0.37207620800228525, 0.8818089700283445, 0.39908834330666254]
 [0.35994314266508654]
 [0.38387950974422036, 0.2776285863311928, 0.403809461353824]

julia> getindex.(vv, length.(vv)) # currect working version
3-element Vector{Float64}:
 0.39908834330666254
 0.35994314266508654
 0.403809461353824

julia> getindex.(vv, end) # desirable that errors
ERROR: ParseError:
# Error @ REPL[40]:1:15
getindex.(vv, end) # desirable that errors
#             └ ── Expected `)`
Stacktrace:
 [1] top-level scope
   @ none:1

I would like to avoid length.(vv) and since I know that the infastructure for end is there, it seems like there should be some way to access it for such cases…

1 Like

What about

last.(vv)

?

2 Likes

end in indexing expressions just lowers to lastindex. That is, v[end] is lowered to getindex(v, lastindex(v)):

julia> Meta.@lower v[end]
:($(Expr(:thunk, CodeInfo(
1 ─ %1 = v
│   %2 = v
│   %3 = Base.lastindex(%2)
│   %4 = Base.getindex(%1, %3)
└──      return %4
))))
10 Likes

What version of Julia are you on? In 1.10.2, the lowered code only evaluates the array expression once:

julia> Meta.@lower v[end]
:($(Expr(:thunk, CodeInfo(
    @ none within `top-level scope`
1 ─ %1 = v
│   %2 = Base.lastindex(v)
│   %3 = Base.getindex(%1, %2)
└──      return %3
))))

which is obviously important for something like foo()[end] where computing the array does work (or worse, has side effects).

Hmm, looks like Julia master (1.12.0-DEV.360) replicates your results, though thankfully it only computes the array once if it’s more than a variable reference:

julia> Meta.@lower v[end]
:($(Expr(:thunk, CodeInfo(
1 ─ %1 = v
│   %2 = v
│   %3 = Base.lastindex(%2)
│   %4 = Base.getindex(%1, %3)
└──      return %4
))))

julia> Meta.@lower foo()[end]
:($(Expr(:thunk, CodeInfo(
1 ─ %1 = foo
│   %2 = (%1)()
│   %3 = Base.lastindex(%2)
│   %4 = Base.getindex(%2, %3)
└──      return %4

Not sure why it became more verbose in the v[end] case, but it looks harmless.

1 Like

last returns the last batch of elements. For example, it would be even more complex to get the second to last element. Sukera’s lastindex is the exact equivalent.

1 Like

I don’t think anyone has really answered this aspect of your question, but the answer is that end is a syntax keyword with exactly two uses: closing blocks (e.g., begin, if, etc) and this special case in [] ref expressions. You can’t even parse expressions that use end inside function calls like that, let alone “lower” them to their lastindex meaning.

julia> Meta.parse("getindex(A, end)")
ERROR: ParseError:
# Error @ none:1:13
getindex(A, end)
#           └ ── Expected `)`

This means it’s not even possible to make a macro that could do this transformation like @m getindex(A, end).

4 Likes

However, you could write a macro that goes in the other direction, transforming e.g. @bcast A[end] to getindex.(A, lastindex(A)), similar to @..

Or just use ibegin/iend from GitHub - JuliaArrays/EndpointRanges.jl: Julia package for doing arithmetic on endpoints in array indexing.

4 Likes