I am trying to learn a lisp and I settled on fennel-lang, since I have a lot of Lua experience. But, working with fennel is showing me so much I never bothered to grasp about Lua. This is a post by one of the main fennel contributors, and it has so much useful and practical details about Lua. One of those times I realize I am not operating at the same level as other folks.
I feel like with some languages you get pretty comfortable with them on the surface level, and then after you’ve been using it for a while you start to dig a little deeper and get really weirded out by some questionable choices in the lower-level bits that you’d been shielded from so far. Lua has been the opposite of this; the deeper into the language you get, the more appreciation you get for the fact that everything has a good reason behind it, even if there are times when I don’t 100% agree with it. (mostly around multiple values)
Glad you liked the post!
I appreciate this; are you okay with sharing an insight or two into this? For example, I can’t imagine any good reason that referencing an undefined variable or table entry returns nil rather than an error. It might make “update table entry if it’s not there” easy I guess, but it’s been far more of a footgun than anything else each time I’ve used it.
I’m not 100% in agreement with this behavior when it comes to locals, but for tables I think Lua’s handling of nil is better than any other dynamic language I know (other than the ones like Erlang or Racket which just don’t have nil at all, which is a much safer path). In Lua, nil has exactly one meaning: it is the absence of a value. So when you look something up in a table and get nil back, there’s no ambiguity about “did I get a nil because someone put a nil in that table, or because the table has no value at that key?” It’s impossible to put nil in a table, and that’s great. It’s also very easy to turn a nil into an error; simply wrap it with assert, which returns its argument when truthy. In other languages that don’t work this way, you end up with nonsense situations like “I put nothing into this data structure, and now it contains more things than it did before”. Just logically absurd.
Now while that’s great for tables, applying this same logic to variables is more of a stretch. Part of it may be because global variables technically are nothing more than table lookups in the _G table. But I think the behavior they decided on is a compromise between “easy for newcomers to learn” vs “allows for more maintainable code in larger codebases”. They chose the out-of-the-box behavior that favors the former. But it’s worth noting here that just because it doesn’t work that way out of the box, it’s very easy to change the behavior so that trying to reference an undefined variable throws an error! (using metatables) And the people who are likely to benefit from this behavior are the more experienced programmers, who are also more likely to be knowledgeable enough to opt-in to stricter variables. That said, in Fennel we chose to make it a compile-time error to reference an unknown variable by default, and allow Lua’s sloppier approach as an opt-in flag.
So while I don’t totally agree with the decision that was made, I understand the rationale behind it, and it’s clear that they understood the tradeoffs and chose the compromise that best supported what they were trying to accomplish.
Aha, I didn’t realize Lua’s meaning for nil was quite so specific; I am indeed more used to Python’s ability to, say, insert multiple None‘s into a list. That’s a fantastic explanation, thank you!