I believe, shared indexing is performance optimization at the expense of usability. A slice looks in every way just like an array, except for indexing. I have to be very careful with indexing when there are slices involved. But since this happens not very often, it is easy to forget, and face runtime error. With inferred types, it is easy to misread.
Another exercise: take a slice from a slice and try indexing into that.
This is so confusing. Do you know any other language with this design?
It wasn't intended as a performance optimization, but to improve the usability of slices in divide-and-conquer algorithms, since you can take the indexes from a slice and use them as-is on the original whole collection or an overlapping slice without having to track adjustments. @dabrahams may have more background on the design choice here.
The Array() call is going to cause a copy that's otherwise unnecessary, which is a lot of overhead just to get zero-based indexing. Instead of using absolute indexes with slices, you might have an easier time making your indexes startIndex-relative for the slice. This'll be easier for the future if you choose to genericize the algorithm to work with non-Array collections too.
I think this is the most sensible choice for a default behaviour, but I think it would be best to also provide a subscript(zeroIndexed index: Int) -> T
Try implementing this with 0 based indexing, and see how much uglier it gets:
As a new user, this slice behavior puzzled me. It seems that the only benefit to a slice (besides zero allocations) is that it's a (run-time) error to index it below a certain point. This doesn't seem like a huge benefit.
This behavior isn't really a huge deal to me either way, except that more generally as a mathematician it vaguely bothers me that we have a canonical map from the non-negative integers to the set of indices given by 0 --> startIndex and + --> indexAfter but for some reason we've chosen not to identify them.
It's a quirk, and somewhat weird coming from other languages I've used, but it hasn't proven to be a huge deal in my personal experience.
On the other hand, if you always use .startIndex, you also don't need to track which variables are Arrays. I would say that Collection indexes are more like pointers or C++-style iterators (Cursor would've been a more honest name IMO); they just happen to look like traditional integer indexes on Array.
Not to improve usability in divide-and-conquer algorithms, but to make them usable at all in generic divide-and-conquer algorithms. Not all collections have the ability to measure distances between, and offset, indices in O(1).
From user perspective, this is a promise not kept. Because a collection accepting integer index mentally is thought to behave as array indexing. It adds cognitive load on user to take them apart, where language should be unambiguous. Either don't allow integer indexing on slices, or be array-like on slices.
Yes, there are a set of design tradeoffs here. Having nonzero-based indexing with integers is not ideal, but it was the best compromise overall. Stop writing your algorithms specifically for arrays, and you won't have this problem.
The key step in learning about Swift collections is to think of indexes as abstract pointers into the collection. Pointing at a tree nodes comes naturally during their typical usage. This isnโt the case with Set and Dictionary. Array doesnโt help you make this mental leap at all, because itโs contiguously ordered in memory and its Index is Int, which leads people into demanding zero based indexing for Slices.
@paiv General point is, when you want to subscript into a collection, you should get your index from that collection. Int indexes are easy to form in your head and when you hardcode them into your subscripts, you can be surprised by the result.
When you realize that Slices are just views into the underlying collection, it makes perfect sense why they would share indices.
Remember that when you have an index from array, itโs a logical pointer to the element, not an ordinal position from the beginning of an array. For that you should use index arithmetic: a.index(a.startIndex, offsetBy: 3)