Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I love lua but the 1-based indexing is the worst decision of all. Just makes it an odd-duck when bouncing between languages.


1-based indexing I can live with. The fact that the "array length" operator stops at the first nil in the array is what makes Lua unusable for me.

Edit: 1-based indexing I can live with. The fact that the things you create with {a, b, c} act for the most part like arrays except for the fact that if they contain nils, the "length" operator is undefined and usually stops at the first nil, is what makes Lua unusable for me.


They are not arrays. There are no arrays in Lua. There's only one compound data type (excluding userdata) and that's table. Tables are hash maps, or dicts, not arrays. You can represent arrays with tables, but that would still be "tables acting somewhat like arrays" and not "arrays". Also, you cannot actually store a `nil` value in a table. Nils are what you get when you access nonexistent key; that means the iteration stops at the first n from a 1,2,3... sequence which has no value in the table. If you need to represent a "hole" in the array, use a placeholder object, then convert it to nil at the point of use.

There are many libraries of common utilities that make working with tables-as-arrays more convenient. If you don't want to use them, it's mostly trivial to write them yourself. What you shouldn't do, though, is trying to use the raw tables as arrays: that could indeed be a frustrating experience.


That's a really common misconception. If there is a "hole" in the table, the result from the length operator is undefined. It doesn't always stop at the first instance of nil. Tracking the table length manually and stashing it in a key (usually "n") is something you see pretty often.


^this, to me is hard to imagine. i’ve taken it for granted that i didnt need to track the lengths of my list-like data structures myself. at the least checking the length of the list of dictionary keys via some builtin.


Meh:

    --- get all packed entries, until gap
    -- func on the provided list
    -- @param list table to be inspected
    -- @param func do func what you func want
    table.each = function(list, func)
      for i,v in ipairs(list) do
        func(v, i)
      end
    end

    --- get all entries, packed or not, until completion
    -- func on the provided list, completely
    -- @param list table to be inspected
    -- @param func do func what you func want
    table.all = function(list, func)
      if (list ~= nil) then
        for i,v in pairs(list) do
          func(v, i)
        end
      end
    end


As long as you don't skip a numeric key, you can rely on the length operator for "array-like" tables. There's no built-in way to check the length of a "dictionary-like" table, but you can overload the length operator in Lua >= 5.2 or LuaJIT.

The base language is very simple, but you usually have the building blocks necessary to add the behavior you're missing.


Best explaination of this I've seen is 'The many lengths of Lua' from Soni L.

http://lua-users.org/lists/lua-l/2016-09/msg00234.html

Lua has many lengths. From string lengths to table lengths, from number lengths to sequence lengths, from sequence lengths to proper sequence lengths. They are the many lengths of Lua.

The # operator returns string lengths and table lengths. It is the standard length operator, and it's what you usually use to get the length of an object.

The string.len() function returns string lengths. It typechecks the argument to make sure it's a string, but otherwise returns the same value as #.

There are various types of table length. Some of them are sequences, some of them are not. Some have nothing to do with length, but rather with count.

The simplest length is the one provided by ipairs(). It only iterates the "proper sequence" part of a table. That is, it iterates the set of contiguous positive integer keys of a table.[1] When in doubt, this is the length you should rely on. Don't do `for i=1,#t`, but instead use `for i,v in ipairs(t)`.

Another simple length is the one provided by pairs(). This is actually a count. If for every iteration of pairs() you increment a counter, you'll end up with the number of keys in a table. It is rarely used, but can be useful sometimes.

If you want a manual table length, the simplest way to do it is probably to just use an `n` field. While Lua supports this usage, the standard library doesn't, so you have to deal with it manually. While the standard library doesn't natively support the `n` field, some functions, such as table.pack(), may emit it.

Another length option is the highest key of a table. You can get this length by combining pairs() and m ath.max(). It can be useful in some niche applications but it's quite slow, so consider a manual length (see previous paragraph) instead.

By combining pairs() with type(), you can get the number of non-integer keys in a table. While this count does exist, I have never seen it used in practice.

The length operator, #, can also be applied to tables. If your table has a positive integer key, and there's a smaller possitive integer that is not a key (i.e. the value associated with it is `nil`), then this shouldn't be used. When using a table without manual length, this operator is usually faster than any other method[2], but it does have the aforementioned drawback. This table length is the only table length that is unspecified for some tables.

Finally, you can also use your own length algorithm. Use this if you want fast runtime, but the drawbacks of the length operator make it unsuitable for your use-case.

And these are the many lengths of Lua!

[1] - I'm not sure how many people know this, but this property (stopping on first `nil`) is actually described in the manual. That is, ipairs() on a table with "holes" is actually well-defined.

[2] - The Lua manual doesn't guarantee O(log n) time complexity for the length operator. If you want guaranteed O(log n) runtime, use your own length algorithm. This means # could have O(n) time complexity (i.e. equivalent to ipairs()), or even O(m) where m = the number of keys in the table (i.e. equivalent to pairs() + math.max()).

PS: Sorry for the wall of text.


> "PS: Sorry for the wall of text."

In the future, it'd be best to just drop a link with a short message as to why the link is relevant.


Exactly. Lua is designed for close integration with C. This is made much harder by having such a fundamental operation work differently between the two languages.


Lua is 1-based because it was originally designed for use by the Fortran programmers of Petrobras, a Brazilian oil company.


I really wish someone did a luajit fork with zero based indexing. It's even more galling in Luajit, because you can otherwise use native C types completely frictionlessly.


I'm on it... sometime. Some people have told me "That would be bad because you would lose all these awesome Lua libraries" but I think it's not such a big problem because Lua has no libraries, the FFI will still work, C will still be 0-indexed, and it's not too much work to convert a Lua library to use 0-based indexing.


I 100% agree. Lua is a small and elegant language, with two unambiguous severe flaws: global scope as default and one based indexing (one based indexing might be fine in other contexts, but not as some C/C++-glue language). The global scope thing can be mostly worked around with tooling, but the 1-based indexing (which in the context of luajit's seamless FFI and terra really means both 1 and 0-based indexing) adds so much friction that it's well worth giving up on or patching existing lua libraries.

Additionally, the lua ecosystem is already fractured (luajit is 5.1 + some selective ports of 5.2 stuff) and my feeling is that making this fracture complete will probably work out better in the longer run.


Nothing prevents you from just using [0].


I know that luajit is carefully written to make this fast as well, but it's of course nonsense that nothing prevents one from doing that. It breaks `#` for starters:

    #{[0]=1,2,3} == #{2,3} -- true
To be able to use zero-based indices ergonomically, the standard library and so one need adjusting as well.


Yes. I looked into consistently using [0] in the past, overriding `__len` and `__ipairs` etc, but kept running into corner cases that stopped it working robustly.

Despite how it may appear, 1-based indexing is quite fundamental to Lua.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: