For example, did you know that if you pass in a variable to a function, when that variable doesn't actually exist, Lua won't complain? I don't just mean that there's no compiler, but even the runtime is fine with it. It'll just silently assign the argument value to be nil.
This resulted in my son being very confused, after having a typo in the variable name he was using there. Why is the value nil? He was sure he gave it a value earlier, before passing it into the function (and he was correct, to an extent).
I have a laundry list of complaints about the language, things that make it harder to teach beginners. Sure, there's less 'setup' than, say, Java, and that's nice. And the 1-indexing is fine, really.
But there's other language decisions that are just weird, man. Why would you make this 'table' structure so flexible and important, and then completely neglect to provide a built-in function create copies of said table? How is that non-programmer-friendly? How is providing no ++ or += increment operators better for beginners?
With regards to variables, the best way to hack around this issue is to 1) always use `local` when declaring a variable and 2) set `_G`'s metatable to raise an error if you try to create a new key, which prevents accidentally using globals like you've inadvertently discovered is the default behavior. This is this way because it is I guess simpler than teaching someone how declarations work, but it obviously leads to a pretty immediately bad situation for anyone who wants to do more programming than setting a global constant. It harkens back from when Lua was basically a TCL-like.
Copying a table is generally unnecessary, if you never mutate the table to be cloned. Just set the __index metatable property of a new table to a reference to the table to be cloned. Ta-da, you have inherited the previous table. Not giving you an immediate, easy way to do this forces you to come to terms with copying not always being the right solution. It's not great, and the language offers no pointers to those who want to get a thing done, but it isn't the worst choice.
Not providing increment operators is a design choice, one that simplifies the metatabling for operators. Only having one case for addition, and not having to worry about increment / incremental assignment, means people creating metatables that override operators have a good experience. This is one of those times where power users are given the advantage at the cost of the less experienced, and I share your implied concern in feeling the inconsistencies in who is favored by which language features.
Keep in mind, I'm talking about beginners here. Beginner kids, even (it's a popular language for Roblox scripting!).
Are either of those things, things that will just come naturally to newbies? Are they definitely gonna know about them when they pick up the language in random text or video tutorials? Are they going to remember to use local as often as they can, and will they understand when it's appropriate to use a global?
The answer to all of these is no, obviously. For someone like me with a CS degree and several years of professional experience, your suggestions are perfectly reasonable, but in the wide, wacky world of beginners hacking things together, the right way should be easy and obvious. So obvious, even a beginner will catch on.
> Copying a table is generally unnecessary, if you never mutate the table to be cloned. Just set the __index property of a new table to a reference to the table to be cloned. Ta-da, you have inherited the previous table.
How is this more intuitive than just having a copy or clone function? You can find plenty of cloning functions on stackoverflow or wherever that people have written. Yes, there are workarounds, but why do you need a workaround for a pretty basic use case here? In python, you can just go "dict2 = dict(dict1)", or "dict2 = dict1.copy()", both of those are easy enough, and more importantly, they're intuitive: want a copy? Call the copy function! Why does Lua have to make it weird?
> This is one of those times where power users are given the advantage at the cost of the less experienced, and I share your implied concern in feeling the inconsistencies in who is favored by which language features.
Yes, it seems like the language design is a strange mix of "really easy to get started for beginners" and "footguns that you probably need to be experienced to avoid".
I think the argument is more that Roblox should be setting up its Lua environment that way.
> In python, you can just go "dict2 = dict(dict1)", or "dict2 = dict1.copy()", both of those are easy enough, and more importantly, they're intuitive: want a copy? Call the copy function!
A shallow copy, which can be very surprising. And since Lua has metatables and userdata, it's impossible to write a deep copy that works correctly in all circumstances.
There's no language I really truly love, but the one I find myself recommending as a result of good tradeoffs between simplicity and usability is Python.
My guess for why Lua doesn't provide a table copy function is because then they'd have to decide whether to provide shallow or deep copies, and deep copies have to do cycle detection and other such stuff. Then there's metatables, functions, and so on.
This is just another one of those situations where Lua is not catering to anyone by randomly catering to one group or another. No one is happy.
But the difficulties your son had are necessary. If it weren't those particular things it would have been others. You might want to make things as easy for him as possible, but that's not learning. If my memory is right, the Lua tutorial mentions early on that any variable that hasn't been assigned a value is nil. The typo, the confusion, and then the realization was the act of learning something on a deeper level than you had understood it before. Going further, when accessing that variable, weren't you indexing _G by accident? Indexing a table with a key that doesn't have a value associated with it returns nil, which makes sense. That thing that makes sense has consequences that can catch you off guard. Looking for a frictionless way to learn something is folly, because the friction comes from the thing you're trying to learn itself.
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
I know that from experience myself. I've moved on from Lua to using tiny virtual machines that run RISC-V instead, and so I choose to use Nim inside the VMs in the hopes that non-technical people will find that easy to grok, somehow. I don't have any anecdata to report yet because it's still early in the project. But, that's the only thing I consider when choosing the language.