It would be a good idea to add the "END" indice notation in "splice!()" and the like for consistency

proposal

#1

Actually if you need to remove the 2 last elements of a Vector,

v=[1,2,3,4]
splice!(v,length(v)-1:length(v))

is the way to go (for me at least^^) and I believe that

splice!(v,end-1:end) # SADLY THIS DOES NOT WORK

Look awesome.

And because “splice!” can change the actual length of a vector it will be useful.

Since it is natural in Julia v1.0 to do

v[end-1,end]

to get the same sub part of the vector, it would be expected.

Please let me know if there is better ways to do that or if I am missing something.

Thanks


#2

Use last(v) instead:

julia> splice!(v,last(v)-1:last(v))
2-element Array{Int64,1}:
 3
 4

#3

First, I don’t see why last() would be better than length() maybe i am missing something here

second, last() have nothing to do with the consistency of the Julia language of using “end” while referring positions inside a vector

lastly, last() only work with my “stupid” example because the values are equals to there positions inside the vector

v=[4,2,1,5]
splice!(v,last(v)-1:last(v))  # BoundsError: attempt to access 4-element Array{Int64,1} at index [4:5]

will not work because of the boundserror

thanks you


#4

Probably lastindex was meant here. In general arrays do not have to be 1-indexed and end is lowered to lastindex.

What you could do is define a macro @splice! that would replace end by a proper call to lastindex - this is exactly what happens in getindex.


#5

Can you put me on the track of making this macro?

I went to see essentials.jl and I didn’t understand what you mean or how to make it.

This kind of code still harm my poor eyes because I have a lot to learn :slight_smile: !

Is it out of question of making this default and available for everyone or is it a lot of work?

Thank you for your reply.


#6

Since end is, in general, the keyword for the last portion of a block in Julia, its special behavior as the last index is limited to the indexing syntaxes. We simply cannot extend support to general purpose functions since we don’t know which arguments are the array and which arguments are the indices.

You can check out the @view macro on how to make this kind of macro:


#7

I am not sure I understand this part, what about all the well know functions like splice!() or deleteat!()?


#8

What does the end do in x[f(y, end)]? Is it grabbing the last index in x? Or is f a special-cased function that allows the expansion of indices with respect to y such that it’s the last index in y? What if I had assigned const f = deleteat! earlier in my file?

This transformation occurs before Julia even knows what functions are what. The parser knows the API of x[i] because that’s syntax. The API of a general function f(y, i) isn’t known, and at parse-time, Julia doesn’t even know which module f belongs to, let alone which argument is an array and which argument is an index. And if it’s hard for Julia to figure out what it should be doing, well then good luck to all mortal programmers trying to read the file!


#9

Thank you for your enlightening.

You opened my eyes and I see now that my question was a bit silly!

splice!(v,length(v)-1:length(v))

Having length(v) in the second argument let the function know It come from v but using end could be very confusing in some case.

Is lastindex really better than length?

Thanks


#10

Yes, lastindex is the last index while length is the length.


#11

These are the same if the array’s indices are 1:length(a) but that’s not always the case, e.g. for OffsetArrays. On the other hand, the first and last indices of an array are always given by the firstindex and lastindex functions (that’s what they’re for).


#12

Yes - this is right - I did not think about it when answering first. You cannot put end in expression even if you do not evaluate it - except in indexing. Right?

But one could imagine using some other symbol, different than end, and process it replacing by lastindex call using a macro - like @splice!(v, 1:END) that would be transformed to splice!(x, 1:lastindex(v)) (but probably this is not worth the effort and the macro itself would not be simple unless you restrict yourself to a narrow range of use cases).


#13

But where do you get the object that lastindex is supposed to be called on? In a[...] syntax, we know: it’s the thing before the [—the thing that you’re indexing into. In function call syntax how do you know what you’re supposed to take the last index of? One of the arguments of the function? The first one? That seems quite arbitrary and non-general.

There are also cases where you want to replace end in indexing with lastindex(a, d) where d is the index of the dimension into which you are indexing. In array indexing syntax, it’s completely clear what dimension you’re indexing into so that’s not a problem. How are you supposed to know if end in a general function call is supposed to lower to lastindex(a) or lastindex(a, d) for some d? And how would you figure out d?


#14

Oh, you’re right — of course you couldn’t do @upend deleteat!(A, end-1). The @view macro works because it works on a syntax where end is supported: @view A[end]. This didn’t occur to me initially, either!

For what it’s worth, there’s also a package (I think by Mauro) that uses a special End() type which supports some deferred operations and evaluates to the correct thing once it hits an indexing operation.


#15

There’s a version of all of this where end (and begin) in indexing is evaluated lazily, which could allow this to work, but that becomes really complex and hard to reason about very quickly and to be fully general, it needs to implement a complete lazy evaluation system. The current syntax can be implemented completely during lowering which makes it pretty simple.


#16

There are two possible solutions (the key is that we do not call a function but a macro):

  1. Use the syntax @splice! v[1:end] and now the macro @splice knows exactly that end refers to v and - in this case would rewrite this expression as splice!(v, 1:lastindex(v)); here the key difficulty in writing the macro would be to only replace the outermost end, so that we correctly handle something like @splice! v[1:end][a[end]:end] as h=v[1:end]; splice!(h,a[end]:lastindex(h)); where h would get generted by gensym
  2. Use the syntax @splice(v, 1:END) (here we need a special sentinel other than end). In this case the macro would rewrite this again as splice!(v, 1:lastindex(v)); in this case the macro is simpler to write as we have to assign the value of an expression in v to some new variable using gensym, call it h, and then rewrite END by lastindex(h) everywhere inside;

Also as splice! works only on vectors it is enough that we handle lastindex(x).


#17

Is there anything that prevents experimentation in a package? AFAICT one can do this with existing facilities by overloading getindex. Eg

import Base: getindex, +

struct FromMiddle{T <: Integer}
    Δ::T
end

+(fm::FromMiddle, i::Integer) = FromMiddle(fm.Δ + i)

const MIDDLE = FromMiddle(0)

getindex(v::AbstractVector, fm::FromMiddle) =
    getindex((firstindex(v) + lastindex(v)) ÷ 2 + fm.Δ)

# Then
julia> (1:9)[MIDDLE + 1]
6

If something evolves which is super-convenient and widely used, perhaps it could end up in Base eventually.