The Lua standard library also uses -1 to mean "last element" in various functions; never found it to be an issue. In fact, it feels quite symmetric, going 1,2,3 from one side and -1,-2,-3 from the other.
Potential for one-offs is always there, but I am highlighting how _most_ of the math is simplified when there is a zero between positive and negative numbers :)
No, because Lua is 1-based :) The last element is l[#l] (#l is "length of l"). Negative indices aren't really used in arrays, but are used in the string library, so given s = "hello"; s:sub(1,4) gives you "hell" and s:sub(-2,-1) gives you "lo".
> Sure, that was my point: s:sub(#s-2,#s-1) != s:sub(-2,-1) — if # works on s at all :)
And why would they ever be the same? Writing "#s-2" you're treating -2 as a relative _offset_, not an index.
> Basically, some things are more "natural" with 1-based counting, but some aren't.
Yes, offsets are naturally zero-based, and indices (everywhere other than languages derived from the C tradition) are naturally 1-based. The reason why C and its descendants use zero-based indexes is because C mixes indexes with offsets (since offsets are the low-level primitive in machine language), so that given a pointer p, we have that p[i] and p+i are the same.
If instead C had not done that and used 1-based indexing like everyone else back then, I'm sure people today would be claiming how C is superior for providing both 1-based indexing with [] and 0-based offsets with +, since there are always scenarios where one leads to more natural expressions that the other, and how people mixing them up were clearly not suited for the subtleties of low-level programming in C, and should be instead using simpler languages with garbage collection and without pointer arithmetic. :)