> ['AB'], no? Or the letters without []?
Without. The dyadic arr[range] operator would always return one (unwrapped) element, a bit like how SQL ORMs frequently have a Query#get_one method to match Query#get_all. `letters` was a char[], `pairs` was a short[], so you're grabbing a short by its interval-address (+0 shorts from origin, +1 shorts from origin).
(Or, to clarify if you were confused by the not-quite-C pseudocode, by 'AB' I meant the short 0x4142.)
> But there really is no way around making its algebraic behaviour isomorphic to 0-based indices
Oh, sure, it's "zero-based arithmetic" because we're still fundamentally dealing with offsets (and when you slice the array, you take offsets from the beginning of the slice, etc.)
I think it's just better—in some vague human-factors sense—to be forced to mention, in each indexing operation, both where the offset (position of hyperplane separating the element from its previous) and the offset+size (position of hyperplane separating the element from its successor) will land, so that you realize explicitly what two numbers those are in your program, as you write them.
Humans—especially beginner programmers—don't intuitively realize that array indexes are offsets. They just want to talk about cardinal-numbered positions, and find themselves unable to, so they guess at a mapping and make mistakes.†
This syntax sort of short-circuits that by giving them an indexing operation that doesn't ask them to decide on any cardinal:ordinal mapping at all. It just asks for two numbers that are clearly "offsets of the positions between array elements", rather than offsets (or counts) of elements.
But even for experienced programmers who understand that indexing operations are pointer offset math, such an indexing-by-continuous-slicing scheme is potentially still a bit helpful, somewhat like being asked to write out a deductive proof for a hand-wavy argument: it makes you aware of when you've made a leap of logic that has no rule allowing you to make it. Or, in this case, it makes you aware of what element you're actually capturing with your hand-wavy math.
> The interesting case is when you, as the programmer, want to do arithmetic with indices.
I think—and this I find even weirder to say than my first thought—that it might be good to restrict this "humane" indexing operation from containing compound expressions.
Instead of this:
foo[(a * 3 + 2)ch .. (a * 3 + 5)ch]
...you'd be forced to write this:
offset = a * 3 + 2
foo[(offset)ch .. (offset+1)ch]
or maybe the x86 LEA-alike:
pos = a * 3 + 2
foo[(pos)ch : 0ch..1ch]
This forces the programmer to
give a name to the number they're using as the "base" in the indexing operation—which pushes them even further toward noticing when they've borked up the fencepost math.
---
† A tangent: "pointers are hard" for beginning programmers because C tries very hard to make array access, and even plain pointer math, "act like" reference-by-cardinal-rank, but then still uses zero-based offsets, munging up the ability to deduce a coherent semantics of pointers as memory-address-valued variables.
In C, when you add an (ordinal!) integer to a pointer, it multiplies that integer by the sizeof() the pointer's referent type because it wants you to model the pointer as being an iterator-handle for a collection of things, rather than just being a memory-address-valued variable. It makes sense, for practical reasons, that you can't create a short︎*-typed variable containing a non-short-stride-aligned memory-address... but that should be a domain error on the result of the addition, not something you're mathematically unable to express.