But what I have seen is that, starting from 1 felt weird to me for approximately 1 hour, tops. After that, it was no more or less of a switching challenge than switching between semicolon languages and non-semicolon languages. In other words, no big deal. And, once I got over it, I was pleasantly surprised to find out that I actually have fewer off-by-one errors in Lua than I do in other languages. Because the author is right: No matter how much professional experience I have starting with 0, starting with 1 is still the more innate way of thinking.
Also, my single biggest all time source of off by one errors is translating between mathematical notation and code in a 0-based language. That fact alone is cause for me to hope that Julia might one day supplant Python for data work.
Example:
# python
lov = ['l', 'o', 'v']
o_index = 1;
array = ['a', 'b', 'c', 'd', 'e', 'f']
move_position = 3
# elements moved here
array = ['a', 'b', 'c', 'l', 'o', 'v']
new_o_index = move_position + o_index
print(array[new_o_index]) # prints 'o'
-- lua
local lov = { 'l', 'o', 'v' }
local o_index = 2;
local array = { 'a', 'b', 'c', 'd', 'e', 'f' }
local move_position = 4
-- elements moved here
array = { 'a', 'b', 'c', 'l', 'o', 'v' }
local new_o_index = move_position + o_index - 1
print(array[new_o_index]) -- prints 'o'
Note that I'm not saying 0-based indexing is always absolutely better, but I generally prefer it and believe it usually makes more sense even outside of programming (including math), we're just not taught that way.[0] https://www.cs.utexas.edu/users/EWD/ewd08xx/EWD831.PDF transcript: https://www.cs.utexas.edu/users/EWD/transcriptions/EWD08xx/E...
And for those claiming the world is 1-based, they're full of it. Machining measurement, for example, is ALL 0-based. We're talking a field that has multiple standards for everything and yet they converged on 0-based measurement.
The bigger problem, though, is that I even have to use array-based indexing to iterate something. That's just asking for bugs.
And, my favorite question for people who like 1-based is "Given index and length N, how do I create index-1?"
The answer, of course, is newindex = index - 1 % N.
The question is, of course, a trick because the answer is really: newindex = (index - 1) % N. Don't forget the parentheses or you have a bug.
Yeah, no. I'll stay in my 0-based languages thanks.
Side Note: I have other objections to Lua. The big one being that it's not really compatible between Lua installations. Everybody compiles different modules so Lua becomes neither small nor is it the same language between any two implementations.
Create index-1? I don't think I've ever needed do that. What does come up all the time in practice in both JS and Lua is "how do I assign past the current end of an array?"
In javascript (if you don't use .push) its arr[arr.length] = x
In Lua its arr[#arr + 1] = x
Even having worked with 0-based indexes for over 20 years, I still doubletake reading that javascript code when it’s embedded in complex code before I can convince myself its inserting. But the lua code is intuitive.
Likewise "read the last element in an array":
JS: arr[arr.length - 1]
Lua: arr[#arr]
I much prefer the lua version in all these cases.
And simplicity is important here because the code gets much worse when the array isn't in a local variable, but instead is some complex expression. confabulate(foo.bar.baz[foo.bar.baz.length - 1]); <-- Simple statement. Absolute mess.
I don't think I've ever needed to avoid .push.
> I still doubletake
Why? "arr[i]" just means "skip i elements and get the next one," which is perfectly intuitive to me. I understand that familiarity is useful, but can you give an intuition for one-indexing that doesn't just appeal to what people are used to?
> the code gets much worse when the array... is some complex expression
Then... don't do that? Rust has "xs.last()", Python has "xs[-1]", and every language has variables. Anyway, even if that code is an "absolute mess" (which is a bit hyperbolic) it's hardly worse than the one-indexed equivalent, which would be
confabulate(foo.bar.baz[foo.bar.baz.length]); confabulate(foo.bar.baz[foo.bar.baz.length]);
The problem I have is that at a glance this looks correct. Even after decades of programming, I would scroll right past this line in JS without noticing the off-by-1 error here. The only saving grace is that this error will occur consistently.But yes, I'd take Rust's xs.last() in a heartbeat. Especially because you can assign through it with last_mut().
That said, I agree with others in this thread. People fixate on the 1 based indexing of lua but you get used to it pretty quickly once you spend time with the language. Lua's real problem is its sparse standard library and its weak package ecosystem. If history had run its course slightly differently, I could easily see lua ending up inside web browsers instead of javascript. And becoming much more popular as a result. Lua and javascript are remarkably similar languages semantically.
You absolutely do not, the standard Lua idiom is to use ipairs. The numeric for loop is there, of course, and the index of the pair (index, value) returned by ipairs will be 1 based. of course.
Iteration isn't why 1-based indexing is bad, it really shows its inadequacy when doing interval maths. I make abstract syntax trees in Lua, and a node in that tree with zero width at the 4th position is { left = 4, right = 3 } which is... awful.
I just did an N=1 experiment with my two children, aged 4 and 6. I held up a single apple in my hand, and asked them how many apples I was holding.
They said 1.
You count age from 0 in most English-speaking countries (there are places where "1 year old" means 0<=x<365 days old). When you are less than 1, we count with smaller units.
So, my N=3 sample (you and your two children) says we use 0-based counting.
Thank you for making my point. :)
And, as I pointed out, there are folks in the world who call their newborn "one year old". And they consider themselves quite sane, thanks.
They count from 1. We count from zero.
Back to the original issue ...
The problem you now have in programming is that it isn't enough that basing from 1 is equivalent. The vast majority of the programming world has converged on 0-based. 1-based has to demonstrate that it is better in order to compensate for the friction of existing in a 0-based world.
And 1-based simply isn't demonstrably better even if I concede that it isn't worse (and I do not concede that).
You keep saying index, but what you are referring to is an offset.
Part of this is using counting numbers for continuous values, part of it is philosophical, but there are a lot of mathematical properties of zero that make it really appealing (eg. being zero-based allows Python to have a very clear iterable[-1] interpretation).
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".
Basically, some things are more "natural" with 1-based counting, but some aren't.
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. :)
If you were working with polynomial coefficients, Fast Fourier Transforms and convolutions, or modulo arithmetic, you'd prefer zero based arrays. In my experience, the majority of places where math notation uses 1-based arrays are in applications where the index doesn't matter.
I prefer zero based arrays specifically because they are better for doing mathematics. Yeah, it sucks that a lot of matrix algorithms in text books use one based arrays, but I blame the text book for getting it wrong.
This is an aside, but I think that "index" is the wrong name. I wish that "index" meant you start counting at 1 (which everyone outside of CS does) and "offset" was the name for starting at 0.
In set theory (math foundations), ordinals also start at 0.
I think for FORTRAN or Julia, using 1-based indexing is a good idea, because most mathematical algorithms are written in notation using 1-based indices. As someone who spent a semester debugging subtle off-by-one errors and small differences in results in his scientific computing classes because I foolishly opted for C rather than FORTRAN, understand that converting algorithms with nested summations and integrators from one convention to another is painful.
Lua should never have used 1-based indexing because it was meant to interface with C code which itself will have data structures that use 0-based indexing. Writing lua extensions for wireshark, for example, is incredibly painful just because of this.
Lua uses 1-based indexing because it started as a data description language, and data tends to use 1-based indexing.
It evolved to where the primary use case is at odds with this origin. I work with Lua every day and consider the indexing convention to be a genuine flaw. But it's useful to know where that came from, because it wasn't a mistake.
Especially when it's used as an embedded language in a C/C++ app, or an additional Lua API alongside other APIs that exist already.
Having to convert back and forth between 0 and 1 based indices is very annoying, and error-prone. You can make helper methods to abstract some of it, but that only gets you so far, and it's annoying (at least I found it that way) having to context switch your brain depending on what part of the call stack you're looking at.
The only upside I'm aware of is that the 'length' of your array is now a valid index, which is mostly not very useful unless you want to iterate backwards.
Side note: As a former developer, turned regular human being who scripts from time to time, I don’t find switching between 0 based and 1 based indexing particularly intellectually taxing.
But there's certainly other stuff that's just weird and bad, and I think just the fact that it's dynamically typed made it harder for him to build an accurate mental model -- sometimes he didn't even realize that an identifier that he had typed in earlier was a variable, let alone what kind of value it'd be holding.
Really only reason to use 1-based indexing I see is to make array index arithmetic needlessly annoying, to discourage users from doing any complex operations there..
This whole conversation is really silly. 1 based indexing is such trivial detail of lua that I'm not surprised so many words have been spent discussing it.
I think this misses the point, at least for my case. It doesn't matter which is technically better. What makes more sense for humans is a matter of application (and even "non-dev" stuff varies as to which is better).
For me, my big concern about using Lua in my products is
- I expect other developers to be writing the Lua scripts
- I expect the developers to be frequently using 0-indexed languages
In this scenario, "being different" means you are likely to run into off-by-one errors. Think of that crufty Perl script you might have around at work where people avoid touching it because no one is comfortable enough with Perl. Having predictable semantics helps a lot in scenarios like this.