If you've worked a bit with Lua, it's actually pretty reasonably and consistent: Everything is a table and accessing unset variables is accessing the table (named _G) holding all global variables. So
print(a)
assuming a isn't a local variable, is essentially the same as print(_G['a']) -- or print(_G.a)
As accessing unset keys within a table returns nil, doing so for globals makes it consistent. Also _G._G == _G :-)Lua 5.1 had something similar, but the implicit environment was per-function.
A common pattern in Lua is to change _ENV to an empty table with a __newindex meta-method that writes-through to the real global environment, and an __index meta-method that throws an error if the key (i.e. global symbol) doesn't exist. You can fancy and have an __index meta-method that that throws an error string with suggestions for a possibly misspelled symbol, similar to clang or GCC compiler diagnostics.
> Everything is a table and accessing unset variables is accessing the table (named _G) holding all global variables.
Before you said this, I had no idea about that. Because it's not like the tutorials are talking about the underlying implementation for this stuff, just like how Java tutorials don't usually go deep in explaining how bytecode works, or how the JVM can be used to interoperate with other languages.
Lua is an incredibly simple and elegant language. All the myths and bike-shedding on Stack Overflow and elsewhere can only lead you astray. For exceptional situations, use the mailing-list. The authors and almost all experts are on the mailing-list and will be quick to correct errors. (Everybody is wrong sometimes, even the authors! But you need enough experts in one place to be paying attention, and the only such place is the mailing-list.)
Another thing to keep in mind is that Lua maintains strict feature symmetry between what's possible in Lua code, and what's possible from the Lua C API. This is important to understand if you want to have a good mental model of the language and why it's designed the way it is. If you're not a C programmer or haven't heavily used the Lua C API, many of Lua's choices may seem arbitrary, especially coming from other languages where their C ABI and FFI interfaces are mostly an afterthought or don't otherwise constrain the in-language semantics. Most "better than Lua" projects are written by people without a sufficient appreciation for this aspect of Lua's design.
> This is important to understand if you want to have a good mental model of the language and why it's designed the way it is.
I want to have a good mental model of the code I write, and how that works, not the underlying language mechanisms themselves.
I've been picking up Kotlin, and I hardly know anything of how it works under the hood, other than whatever it 'inherits' from being a JVM language, and so far it's been totally fine. Why should I need to care about how the sausage is made?
Are you the nine-year-old? I was responding to a criticism you made regarding your understanding. Are online tutorials for children learning their first programming language supposed to explain the rationale behind the language design? If you want the latter don't use children's tutorials, or in the case of Lua, any tutorials, because the official material is at least as easy to understand, and infinitely more reliable and accurate.
I'm definitely critiquing it from a standpoint of how we've been using it: as a scripting language for making games. We're doing it with this Undertale thing, and there's a good chance he'll use it with Roblox, because the kid loves Roblox, and Lua is used there for scripting as well.
And in that context, it's for non-programmers in a sense, sure, but it's also for people learning to program. Those aren't conflicting descriptions, right? Because initially you can call them 'non-programmers', but at a certain level of complexity, calling them non-programmers doesn't even make sense anymore (and the Roblox custom games I've seen him play, they seem plenty complex to me).
It makes a lot of sense once you realise that assigning to a "global variable X" (which is equivalent to _G[X]) and then assign nil, is exactly the same as never assigning at all