Hacker News new | ask | show | jobs
by john_reel 3495 days ago
Honestly, it’s a great article. That said, there’s nothing wrong with nils in tables in Lua if you know what you’re doing. Yes, they can introduce weird behaviors, (particularly with the # operator) but if you understand how next, pairs, ipairs, and # work, which are required if you want predictable table iteration behavior in Lua, then this shouldn’t be an issue.

I think it’s dangerous to say that nils terminate tables if you’re not going to explain table iteration in Lua.

Edit: Here's a picture example showing how easily nils can cause confusion if you're using a #table based loop. https://i.imgur.com/qW0XoSs.png

for i,v in pairs(table) will go through an entire table.

for i,v in ipairs(table) will stop at a nil. It's meant for consistent behavior in tables with numerical indices.

2 comments

The key phrase to keep in mind in Lua is "sequence". The # operator on a table, and the ipairs function, are meant to be used with sequences. Per the Lua manual

  We use the term sequence to denote a table where the set of
  all positive numeric keys is equal to {1..n} for some non
  negative integer n, which is called the length of the
  sequence (see §3.4.7).
If a table contains numeric keys with any gaps (i.e. nil values between sequential numeric indices), then it's not a sequence. Because Lua uses a binary search to find the end of the sequence, it might select any nil as the one ending the sequence.

Also, another thing to remember is that a Lua table can have both a sequence and non-numerical (e.g. string) keys. So

  local t = { 1, 2, 3, n = 3 }
is a valid sequence. Using n to store the length of the "array" part is a common Lua idiom when you want array behavior but cannot guarantee a valid sequence. For example, table.pack creates a table out of a variable argument list; but because any of the arguments might be nil, it sets n as the length of the argument list. Of course, you have to explicitly make use of n in those cases.

Lots of people complain about this aspect of Lua. But Lua is very minimalist and there aren't any obvious alternatives. Lua could try to keep track of the maximum numeric index, but that can have poor asymptotic behavior unless Lua used a binary tree for tables instead of arrays and hashes. Lua could implement a type-safe array object, but that violates a core design guideline of Lua, which is that it provides the table as the only primitive for compound data structures. And in any event, it's trivial to implement your own array implementation using metamethods to maintain the invariants of a table while providing transparent support for # and ipairs.

The easiest way to have a value which "acts like null" but doesn't mess with table indexing is to use `false`, which is the only Lua value other than nil which is falsey (0 and "" are truthy).
Yeah, I wish Lua hadn't taken undefined and nil to be the same thing given the way tables work in it. Most of the time it doesn't matter, thankfully. Sometimes you bump into it.

A sigil value can work too if you absolutely need it. ex:

  undef = {}

  function isundef(val)
    return undef == val
  end
The sigil value is truthy and makes your code more convoluted, though. ex:

  if not val or isundef(val) then -- ...
So, :/. I do love the language though.
If you want, you can add t.exists[key]=true, and check it if you really want to know if key exists despite it's value is nil.

    function exists(t, k)
        return t[k] ~= nil or t.exists[k]
    end
    
    t = { 1, 2, nil, 4, exists={ [3]=true } }
    i = math.random(4)
    if exists(t, i) then
        ...
That's pretty close to Perl's semantics, where undef is inoperable under 'use strict' but arrays/hashes can have them explicit. Also can move exists out of t easily with weak table.

    local exists_t = setmetatable({ }, { __mode='k' })

    function set_exists(t, k)
        exists_t[t] = exists_t[t] or { }
        exists_t[t][k] = true
    end
    
    function exists(t, k)
        return t[k] ~= nil or exists_t[t][k]
    end
That said, I really like math-like attitude of definitions in Lua. No bs like 'bloated for your convenience', you just use your logic skills to program.