Hacker News new | ask | show | jobs
by samatman 743 days ago
> The point is that # is basically unusable for non-sequence tables because there are no guarantees on which border it will return.

This isn't true, though! The concept of a "non-sequence" table is not something Lua actually has, and if it means a table without an array portion, then `#mapTable` returns 0, no exceptions.

The ambiguity is if there is a sequence, and you stuck `nil`s into it. So definitely don't do that. Want to call that a trap for the unwary? I don't disagree. If you want to make entirely sure that you iterate all the keys, you use `pairs` or `next`. Not much else to it.

Laser-focusing on one case of malformed input is a strange move. If a Python dict doesn't have a key you expect, looking for it will throw an error, in Lua you just get `nil`. Which is better? No idea, but I know which I prefer, and it's the one that doesn't drag my entire program to a screaming halt.

Trying to create an artifical difference between "sequence tables" and "non-sequence tables" is exactly what I meant by wanting Lua to be Python and being surprised or offended when it isn't. There's just... tables. It's one of my favorite things about the language, in fact, because it makes for a very clean expression of ASTs. Metadata goes in keys, child nodes go in the array portion, everyone's happy. Works a treat for XML and HTML too: attributes are keys, child elements are in the array. In a language like Python, you need an `.attr` dict and a `.child` array, because if you just use a dict, you could have an attribute collision if there's a `child` attribute. It's an entire level of indirection which I don't have to deal with in Lua.

Just don't stick `nil`s in the array portion. It's a mistake. You won't be happy. If you need a conditional branch while iterating, use a `false`. It's a cost-cutting measure that was taken to get a language runtime that fits in 70KiB and has fast arrays which are also dicts. I have several reasons why that's a good idea, you've got one reason why it's bad. I say don't do the bad thing. Simple.

Again, I've never had a single bug from bad `nil` insertion in a table. Used the language for years. I've had nil-related bugs, dynamic-type related bugs, and plenty of logic errors. Just never the one thing that people focus on so diligently anytime Lua comes up. YMMV I suppose.

Maintainability wise, I've seen no difference between Lua and Python, having written plenty of both, up until Python added annotations. A gradual type system for Lua would be an excellent addition, Luau-style but compiling to plain Lua. Teal exists, but it ain't quite it.

1 comments

I linked directly to the Lua language specification earlier which precisely defines which table is a sequence and which isn't:

"A table with exactly one border is called a sequence. For instance, the table {10, 20, 30, 40, 50} is a sequence, as it has only one border (5). The table {10, 20, 30, nil, 50} has two borders (3 and 5), and therefore it is not a sequence. (The nil at index 4 is called a hole.) The table {nil, 20, 30, nil, nil, 60, nil} has three borders (0, 3, and 6), so it is not a sequence, too. The table {} is a sequence with border 0."

> If a Python dict doesn't have a key you expect, looking for it will throw an error, in Lua you just get `nil`. Which is better? No idea

The one that lets you easily diagnose an error at the point where it happens instead of silently producing incorrect output is better, naturally.

> Metadata goes in keys, child nodes go in the array portion

There is no "array portion", they are all keys, just some are numbers and some are not. It's not at all like XML & XDM, where attributes and child elements are completely different namespaces, so count(foo/bar) and count(foo/@bar) are two different things.

> In a language like Python, you need an `.attr` dict and a `.child` array, because if you just use a dict, you could have an attribute collision if there's a `child` attribute.

Python dicts map exactly to Lua tables. If you want to store data in this manner, you absolutely can:

   foo = {"a": x, "b": y, 1: z, 2: ...}
But in Python usually you would instead do:

   class Foo(list): ...
   foo = Foo([1, 2, ...], a=x, b=y)
and then:

   foo.x, foo.y
   len(foo)  # only counts items, not attributes; None is okay!
OTOH if Foo semantically does not have child items, then you wouldn't derive from list, and then len(foo) would straight up throw an exception. And if your index is out of bounds, you again get an exception rather than None.

> Just don't stick `nil`s in the array portion. It's a mistake. You won't be happy.

It's not like anybody is deliberately writing something like {1, nil, 2}. But tables get filled with dynamically computed data, and sometimes that data happens to be nil (often because you e.g. computed an element by indexing another table, and the key was missing so that operation returned nil).

So now you have to always remember that and guard against it, because it is not an error to construct such a table, either. Which, again, is weird if it is "a mistake".