Right. I'm on the "no natural mapping" side as well, here, not with the "first = 1" people.
Actually, consider for a moment, a programming language that tried to avoid both of these mappings, at least in its programmer-facing syntax (it might compile down to one or the other mapping, but that's an implementation detail.) It might not be so bad!
Of course, you wouldn't be able to index arrays by a single ordinal number at all, instead being forced to do speak in terms of intervals:
letters = char{'A', 'B', 'C'}
letters[0ch .. 1ch] # => ['A']
letters[1ch .. 2ch] # => ['B']
pairs = short{'AB', 'CD', 'EF'}
pairs.slice(0sh .. 1sh) # => ['AB']
pairs[0sh .. 1sh] # => 'AB'
pairs[0ch .. 1ch] # => ['A'] or a type error, as the language author pleases
pairs[0sh .. 2sh] # => argument error
pairs.slice(0sh .. 2sh) # => ['AB', 'CD']
Effectively, the single-element index operation would just be a specialization of a slice operation. Both operations would take a mantissa indicating where the "left edge" and "right edge" containing the value you want is in the array's memory space. And you'd be forced to specify explicit units for the "addresses" of the mantissa, even though the language might reject a mantissa of the wrong type.This seems kind of annoying to write. But, I swear, it feels so pleasant to read. Much moreso than any language I've used. It's not hard at all to "keep the logic in my head" when I read the above back.
['AB'], no? Or the letters without []?
> This seems kind of annoying to write. But, I swear, it feels so pleasant to read. Much moreso than any language I've used. It's not hard at all to "keep the logic in my head" when I read the above back.
Well, yeah, but you conveniently left out index arithmetic :-)
If it's only about hard-coded indices, it really doesn't matter, IMO, as long as it's consistent, and you could just use any ordered set of symbols for indexing, and let the compiler figure out the mapping to binary numbers and machine code that's functionally equivalent.
The interesting case is when you, as the programmer, want to do arithmetic with indices. Of course, you could have a separate number type for array indices, say. But there really is no way around making its algebraic behaviour isomorphic to 0-based indices, because 1-based arithmetic is hell, and if you do that, it's probably a good idea to use the established names such as "0" or "1" for the established concepts such as "neutral element of addition" or "neutral element of multiplication", in order to avoid confusion.
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.
Well, I'm not convinced, but who knows? My guess would be that it wouldn't really reduce mistakes much. People would simply memorize the rule that for a single element, you always have to give some index and then follow that by index + 1 --and if the index is computed incorrectly, the result is just the same wrong result as before. You cannot force people to prove that the offset is correct by forcing them to write down the expressions for two values that have to always differ by exactly 1.
> 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
Nah, I think that's perfectly sensible, even theoretically. A pointer is a perfectly sensible memory-address-valued variable, it's just that it's not in "machine code addresses", because that is an implementation detail, but rather in "abstract address space addresses", with a separate address space per object (by which I mean an allocated memory region), and obviously, with arithmetic in that abstract space counting in elements of that respective address space's type. Why would counting in bytes make any more sense? The size of a byte (or a char, rather) is mostly an arbitrary implementation detail that abstract code shouldn't be bothered with.
BTW, what you write is kindof wrong: As per the abstract definition of the language, nothing is being multiplied by sizeof()s. That tends to be how it's often implemented on the usual platforms, but the abstract definition simply says that arithmetic with pointers works in units of elements, and that consistently: If you subtract pointers, you get the number of elements between them, not the number of bytes/chars/octets/words/whatever between them, the internal value is not really visible anywhere, and could relate to the abstract value in arbitrarily complicated ways anyway (like, you could have pointers that are implemented with tags to differentiate pointers to different machine address spaces and stuff like that).
> that you can't create a short︎*-typed variable containing a non-short-stride-aligned memory-address...
That's actually not the goal nor the case. Whether a pointer is aligned is an implementation detail, there is nothing in C that would require short objects to be aligned to short boundaries, and what implementations do simply depends on (a) whether the platform can handle non-aligned pointers, and (b) if it can, what makes sense as a matter of weighing performance vs. memory usage. I haven't checked it, but my guess would be that the AVR GCC, for example, does not align anything, because it only needs more memory and makes no difference whatsoever for performance (I think ... maybe I am forgetting something).
So, empty water bottles don't exist? Or do you object to saying "this bottle contains 0 ml of water"? Why? Or do you think that no water bottle is the same thing as a water bottle with no water in it?
> I have 1 car, not 0.
And what about someone who has no car? Are there no people who don't have cars? Or do you object to saying "this person has zero cars"? Why? Or do you think that no garage is the same as a garage with no car in it?
> If I had a lineup of cars, the first one would be 1, not 0.
No, it wouldn't. Unless you arbitrarily decided to label it as such. It would be the first. That's an ordinal number. Also, you would have 1 car. That's a cardinal number.
They exist conceptually, because a water bottle could be empty of anything that fits inside. We say it has 0 ml of water and not coke or sand because we've designated it to be a water bottle, but nature obviously doesn't care what goes in the bottle, as long as it fits. It's just a useful idea we have to say there is zero something to designate a lack of something. But there isn't zero water in nature, anymore than there are zero unicorns, or zero Kings of France.
>> And what about someone who has no car? Are there no people who don't have cars? Or do you object to saying "this person has zero cars"? Why? Or do you think that no garage is the same as a garage with no car in it? <<
Are there no people who don't have dinosaurs or time machines? The things we have zero of are infinite. But you don't actually have zero pink elephants in your backyard.
>> No, it wouldn't. Unless you arbitrarily decided to label it as such. It would be the first. That's an ordinal number. Also, you would have 1 car. That's a cardinal number.
So if we were to label to cars in increasing numeric order from one end to the other, we would start with 0 instead of 1 at the first car? We don't do that with building levels. You enter on the first floor, go to the elevator, and you push a button for the floor level. The first floor is 1. 0 Would be the basement level.
>So if we were to label to cars in increasing numeric order from one end to the other, we would start with 0 instead of 1 at the first car? We don't do that with building levels. You enter on the first floor, go to the elevator, and you push a button for the floor level. The first floor is 1. 0 Would be the basement level.
We opted for the word "Ground Floor" instead of "Zeroth Floor" likely due to the associations people make of "zero" (ie. not existing) and because it may have sounded weird but the concept is the same.
The U.K and U.S refer to different storeys as the "first floor". In the U.S, the first floor is considered the ground floor and in the U.K the first floor is considered the second storey. A source of headaches when told the place you're looking at is on the first floor and you spend ages looking for it on the ground floor.
That's just confusion on your part. All of language is just a useful idea we have to designate whatever we designate with it, and you are just confusing things for no reason at all. You obviously understand what "zero something" means, and that what it means is indeed something that can describe aspects of reality, and then you go on and claim that somehow it doesn't. When I say "there are 0 ml of water in this bottle", you understand what it means, right? And if there is indeed no water in the bottle, what is there to be confused about? It does mean that there is no water in the bottle, but it also means something else at the same time? Or there is no water in the bottle, but there is also not no water in the bottle?
> The things we have zero of are infinite. But you don't actually have zero pink elephants in your backyard.
Yes, I actually have. "having zero <whatevers>" is, as you evidently know, a synonym for "having no <whatevers>" or "not having <whatevers>", and I absolutely certainly actually do not have pink elephants in my backyard.
> So if we were to label to cars in increasing numeric order from one end to the other, we would start with 0 instead of 1 at the first car?
There is no such thing as "we would start". You can start wherever you want. That is, as derefr has already explained to you, an exercise in assigning serial numbers. You can use whatever numbering scheme you like. Obviously. Some might be more useful than others, though.
> We don't do that with building levels. You enter on the first floor, go to the elevator, and you push a button for the floor level. The first floor is 1. 0 Would be the basement level.
Except you don't enter on the first floor. The "first" thing in any order is defined to be the thing without a predecessor. Obviously, if there is a basement, the ground floor is not the first floor, because the basement precedes it. Just because the ground floor is commonly called the "first floor" for historical reasons, doesn't make it the mathematically first floor.
In some places (e.g., most of North America), that's the convention. In others, the "first floor" is the one immediately above the ground floor.
Ordinal numbers (0, 1, 2) map to the world as amounts, and certainly it seems 'real' that there can be "one of something" in a way that it isn't that there is "zero of something" (though it isn't, really.)
Cardinal numbers (first, second, third) map to the world as rankings or positions. Positions, further, often almost always map to distances from an origin. "First" really almost always means "the item that is +0 positions away from the origin." The person who wins a competition ("first place") is zero positions down the leaderboard. The "first" book on your book shelf can be found +0cm away from the left edge of your bookshelf. Etc.
With your cars, you're talking about a third thing, though: serial numbers. "Car 1" is what you might call the first car you bought (if you were planning on buying several), because we conventionally assign serial numbers to things starting with 1. But that has nothing to do with counting or ranking; it's just a convention (as can be seen when we choose to use an alternate scheme, like UUIDs, for labelling items in a database.)
What we're talking about here is the relationship between counting and ranking: between cardinal and ordinal numbers. Since they're both abstract, and not structurally connected in any important way, there's actually no "essential, required" way to relate them to one-another, and we have to just make one up. Some people relate "first" to the natural number 0; some people relate "first" to the natural number 1.
Which is good, because it means that you are not confused :-)
The confusion is the idea that there is an inherent link between "1" and "first" or "0" and "first", which there isn't, because one of them is a label, while the other is an ordinal number.
You need a definition of how to map one to the other, and as long as you don't have that, the labels do indeed remain ambiguous, which you correctly recognize.
Now, there is an argument to be made for why array indexing should use a definition that maps the first element to label 0, and that is, as I wrote, that array indexing is an additive operation.
As for how that connects to your fence example: What you necessarily need in addition if you want to model your fence with an array(!) is a definition that maps your fence posts' positions to ordinal numbers, which you then can map to array indices via the definition above. That exact mapping is necessarily arbitrary to a degree, and the only really important thing is that you are consistent.
But within the implementation, you are likely to encounter problems such as "find the third fencepost of the part of the fence that starts at the second fencepost". And that would be the fourth fencepost of the overall fence, not the fifth, or in indices: 1 + 2 = 3 vs. 2 + 3 != 4.
> There are nearly no cases where there is a natural "element zero" that exists at zero in the physical world
Well, nor is there a natural "element one" that exists "at one".