Description
The pattern s[len(s)-1]
appears fairly often.
However, s[len(s)-1]
is neither readable nor easy to type,
especially if s
is a relatively complex expression.
Thus, I propose the addition of slices.Last
that
retrieves the last elements, respectively.
I also propose adding slices.First
for consistency.
Supporting evidence
Frequency of constant slice indexes relative to start or end:
Index | Occurrences | Percentage |
---|---|---|
0 | 4953854 | 41.684% |
1 | 2192516 | 18.449% |
2 | 1135707 | 9.556% |
3 | 1370480 | 11.532% |
4 | 521199 | 4.386% |
other | 1391696 | 11.710% |
-5 | 196 | 0.002% |
-4 | 443 | 0.004% |
-3 | 1682 | 0.014% |
-2 | 10336 | 0.087% |
-1 | 306168 | 2.576% |
Even though obtaining the last element is an order-of-magnitude
less frequent than getting the first element,
there are still ~300k occurences.
Of the number of cases where s[len(s)-1]
appears,
it appears alongside s[0]
78% of the time.
For example:
minTime, _ := extractTimeRange(lastShard.spans[0])
_, maxTime := extractTimeRange(lastShard.spans[len(lastShard.spans)-1])
For this reason, I propose the addition of slices.First
,
so that these case will read naturally:
minTime, _ := extractTimeRange(slices.First(lastShard.spans))
_, maxTime := extractTimeRange(slices.Last(lastShard.spans))
Considerations
This API does not help the following statement:
s[len(s)-1] = ...
since the returned value of slices.Last
cannot be assigned to.
This occurs 32344 times (10.6%).
It also does not help the following expression:
&s[len(s)-1]
since you cannot take the address of the return value of slice.Last
.
This occurs 8102 times (2.6%).
To support these 13% of cases, we could have slices.Last
return a pointer to the element, so that you could do:
*slices.Last(s) = ...
However, that is clunky to use.
Alternatives
Last
is unhelpful if you want the penultimate element (i.e., s[len(s)-2]
),
but there are an order-of-magnitude fewer use-cases for that compared
to just getting the last element.
We could consider a Python-like indexing operation with negative indexing:
// At retrieves the element at i.
// If i is negative, then it is indexed relative to the end.
func At[S ~[]E, E any](s S, i int) E
This provides a general-purpose way to retrieve elements relative to the end.
Negative indexing has been proposed many times in the past for slicing and was rejected
because it is easy for programming bug to go unnoticed.
Using an explicit API like slices.At
could be clear enough signal that we truly want
negative indexing. Perhaps that is an appropriate trade-off.
I personally would like to have At
as well,
but the evidence does not seem to indicate that it is as prevalent
compared to the need for Last
.