I think this misses the point, at least for my case. It doesn't matter which is technically better. What makes more sense for humans is a matter of application (and even "non-dev" stuff varies as to which is better).
For me, my big concern about using Lua in my products is
- I expect other developers to be writing the Lua scripts
- I expect the developers to be frequently using 0-indexed languages
In this scenario, "being different" means you are likely to run into off-by-one errors. Think of that crufty Perl script you might have around at work where people avoid touching it because no one is comfortable enough with Perl. Having predictable semantics helps a lot in scenarios like this.
So, I don't do a lot of Lua programming - basically just having fun with Pico-8 on occasion - so I can't say I have a lot of experience with this. Every single other language I use starts at 0.
But what I have seen is that, starting from 1 felt weird to me for approximately 1 hour, tops. After that, it was no more or less of a switching challenge than switching between semicolon languages and non-semicolon languages. In other words, no big deal. And, once I got over it, I was pleasantly surprised to find out that I actually have fewer off-by-one errors in Lua than I do in other languages. Because the author is right: No matter how much professional experience I have starting with 0, starting with 1 is still the more innate way of thinking.
Also, my single biggest all time source of off by one errors is translating between mathematical notation and code in a 0-based language. That fact alone is cause for me to hope that Julia might one day supplant Python for data work.
You clearly have a different experience using 1-based index than me, it has caused me many off-by-one mistakes. Also, I almost never need to -1 with 0-based index, but in 1-based index its pretty common, specially if moving slices inside an array, the new index of the element is move_position + old_index - 1 (see example). Also see arguments by Djikstra [0].
Example:
# python
lov = ['l', 'o', 'v']
o_index = 1;
array = ['a', 'b', 'c', 'd', 'e', 'f']
move_position = 3
# elements moved here
array = ['a', 'b', 'c', 'l', 'o', 'v']
new_o_index = move_position + o_index
print(array[new_o_index]) # prints 'o'
-- lua
local lov = { 'l', 'o', 'v' }
local o_index = 2;
local array = { 'a', 'b', 'c', 'd', 'e', 'f' }
local move_position = 4
-- elements moved here
array = { 'a', 'b', 'c', 'l', 'o', 'v' }
local new_o_index = move_position + o_index - 1
print(array[new_o_index]) -- prints 'o'
Note that I'm not saying 0-based indexing is always absolutely better, but I generally prefer it and believe it usually makes more sense even outside of programming (including math), we're just not taught that way.
I'm glad that's true for you, but my personal experience suggests otherwise. Dijkstra also mentions the Mesa programming language which had all four of the conventions and that anything other than 0-based, half-open leads to errors. See: https://www.cs.utexas.edu/users/EWD/transcriptions/EWD08xx/E...
And for those claiming the world is 1-based, they're full of it. Machining measurement, for example, is ALL 0-based. We're talking a field that has multiple standards for everything and yet they converged on 0-based measurement.
The bigger problem, though, is that I even have to use array-based indexing to iterate something. That's just asking for bugs.
And, my favorite question for people who like 1-based is "Given index and length N, how do I create index-1?"
The answer, of course, is newindex = index - 1 % N.
The question is, of course, a trick because the answer is really: newindex = (index - 1) % N. Don't forget the parentheses or you have a bug.
Yeah, no. I'll stay in my 0-based languages thanks.
Side Note: I have other objections to Lua. The big one being that it's not really compatible between Lua installations. Everybody compiles different modules so Lua becomes neither small nor is it the same language between any two implementations.
> my favorite question for people who like 1-based is "Given index and length N, how do I create index-1?"
Create index-1? I don't think I've ever needed do that. What does come up all the time in practice in both JS and Lua is "how do I assign past the current end of an array?"
In javascript (if you don't use .push) its arr[arr.length] = x
In Lua its arr[#arr + 1] = x
Even having worked with 0-based indexes for over 20 years, I still doubletake reading that javascript code when it’s embedded in complex code before I can convince myself its inserting. But the lua code is intuitive.
Likewise "read the last element in an array":
JS: arr[arr.length - 1]
Lua: arr[#arr]
I much prefer the lua version in all these cases.
And simplicity is important here because the code gets much worse when the array isn't in a local variable, but instead is some complex expression. confabulate(foo.bar.baz[foo.bar.baz.length - 1]); <-- Simple statement. Absolute mess.
Why? "arr[i]" just means "skip i elements and get the next one," which is perfectly intuitive to me. I understand that familiarity is useful, but can you give an intuition for one-indexing that doesn't just appeal to what people are used to?
> the code gets much worse when the array... is some complex expression
Then... don't do that? Rust has "xs.last()", Python has "xs[-1]", and every language has variables. Anyway, even if that code is an "absolute mess" (which is a bit hyperbolic) it's hardly worse than the one-indexed equivalent, which would be
The problem I have is that at a glance this looks correct. Even after decades of programming, I would scroll right past this line in JS without noticing the off-by-1 error here. The only saving grace is that this error will occur consistently.
But yes, I'd take Rust's xs.last() in a heartbeat. Especially because you can assign through it with last_mut().
That said, I agree with others in this thread. People fixate on the 1 based indexing of lua but you get used to it pretty quickly once you spend time with the language. Lua's real problem is its sparse standard library and its weak package ecosystem. If history had run its course slightly differently, I could easily see lua ending up inside web browsers instead of javascript. And becoming much more popular as a result. Lua and javascript are remarkably similar languages semantically.
> The bigger problem, though, is that I even have to use array-based indexing to iterate something. That's just asking for bugs.
You absolutely do not, the standard Lua idiom is to use ipairs. The numeric for loop is there, of course, and the index of the pair (index, value) returned by ipairs will be 1 based. of course.
Iteration isn't why 1-based indexing is bad, it really shows its inadequacy when doing interval maths. I make abstract syntax trees in Lua, and a node in that tree with zero width at the 4th position is { left = 4, right = 3 } which is... awful.
Except that your children are >1460 days old and >2190 days old.
You count age from 0 in most English-speaking countries (there are places where "1 year old" means 0<=x<365 days old). When you are less than 1, we count with smaller units.
So, my N=3 sample (you and your two children) says we use 0-based counting.
Round(age)? So, they became one year old at 180 days? I doubt that.
And, as I pointed out, there are folks in the world who call their newborn "one year old". And they consider themselves quite sane, thanks.
They count from 1. We count from zero.
Back to the original issue ...
The problem you now have in programming is that it isn't enough that basing from 1 is equivalent. The vast majority of the programming world has converged on 0-based. 1-based has to demonstrate that it is better in order to compensate for the friction of existing in a 0-based world.
And 1-based simply isn't demonstrably better even if I concede that it isn't worse (and I do not concede that).
I generally agree there are benefits to both, but even the author uses a bad example. They talk of a baby becoming one year old after one full Earth revolution, but do not acknowledge that baby is basically 0 years old until that time. Sure, we'd say it's in its first year, but age-in-years is zero-based.
Part of this is using counting numbers for continuous values, part of it is philosophical, but there are a lot of mathematical properties of zero that make it really appealing (eg. being zero-based allows Python to have a very clear iterable[-1] interpretation).
The Lua standard library also uses -1 to mean "last element" in various functions; never found it to be an issue. In fact, it feels quite symmetric, going 1,2,3 from one side and -1,-2,-3 from the other.
Potential for one-offs is always there, but I am highlighting how _most_ of the math is simplified when there is a zero between positive and negative numbers :)
No, because Lua is 1-based :) The last element is l[#l] (#l is "length of l"). Negative indices aren't really used in arrays, but are used in the string library, so given s = "hello"; s:sub(1,4) gives you "hell" and s:sub(-2,-1) gives you "lo".
> Sure, that was my point: s:sub(#s-2,#s-1) != s:sub(-2,-1) — if # works on s at all :)
And why would they ever be the same? Writing "#s-2" you're treating -2 as a relative _offset_, not an index.
> Basically, some things are more "natural" with 1-based counting, but some aren't.
Yes, offsets are naturally zero-based, and indices (everywhere other than languages derived from the C tradition) are naturally 1-based. The reason why C and its descendants use zero-based indexes is because C mixes indexes with offsets (since offsets are the low-level primitive in machine language), so that given a pointer p, we have that p[i] and p+i are the same.
If instead C had not done that and used 1-based indexing like everyone else back then, I'm sure people today would be claiming how C is superior for providing both 1-based indexing with [] and 0-based offsets with +, since there are always scenarios where one leads to more natural expressions that the other, and how people mixing them up were clearly not suited for the subtleties of low-level programming in C, and should be instead using simpler languages with garbage collection and without pointer arithmetic. :)
> Also, my single biggest all time source of off by one errors is translating between mathematical notation and code in a 0-based language.
If you were working with polynomial coefficients, Fast Fourier Transforms and convolutions, or modulo arithmetic, you'd prefer zero based arrays. In my experience, the majority of places where math notation uses 1-based arrays are in applications where the index doesn't matter.
I prefer zero based arrays specifically because they are better for doing mathematics. Yeah, it sucks that a lot of matrix algorithms in text books use one based arrays, but I blame the text book for getting it wrong.
This is an aside, but I think that "index" is the wrong name. I wish that "index" meant you start counting at 1 (which everyone outside of CS does) and "offset" was the name for starting at 0.
That usage gets awkward though when you want to talk about the offset from one array index to another (and the array is 0-based). In actual (common) usage, the distinction between index and offset is the distinction between absolute and relative positions.
I think I will intentionally start using this usage. Maybe we can make it a thing. I mean we already have argument & parameter for similar things to make a distinction.
Djikstra argued in favour of 0-based indexing and against 1-based indexing. Let’s just agree that if you’re arguing for 1-based indexing, you’d better have a pretty fantastic argument.
I think for FORTRAN or Julia, using 1-based indexing is a good idea, because most mathematical algorithms are written in notation using 1-based indices. As someone who spent a semester debugging subtle off-by-one errors and small differences in results in his scientific computing classes because I foolishly opted for C rather than FORTRAN, understand that converting algorithms with nested summations and integrators from one convention to another is painful.
Lua should never have used 1-based indexing because it was meant to interface with C code which itself will have data structures that use 0-based indexing. Writing lua extensions for wireshark, for example, is incredibly painful just because of this.
Lua uses 1-based indexing because it started as a data description language, and data tends to use 1-based indexing.
It evolved to where the primary use case is at odds with this origin. I work with Lua every day and consider the indexing convention to be a genuine flaw. But it's useful to know where that came from, because it wasn't a mistake.
That makes a lot sense. History is a path-dependent function, I guess. Interesting that it was a data description language - in that context, 1-based indexing really would be called for, although maybe flexible indexing like Pascal’s would be even better.
Especially when it's used as an embedded language in a C/C++ app, or an additional Lua API alongside other APIs that exist already.
Having to convert back and forth between 0 and 1 based indices is very annoying, and error-prone. You can make helper methods to abstract some of it, but that only gets you so far, and it's annoying (at least I found it that way) having to context switch your brain depending on what part of the call stack you're looking at.
Not only that, but starting at 0 just works mathematically better when doing stuff like dividing into round/ceil/floor or a modulo operation. If your index starts at 1 these can become really unintuitive. Depending on what you're doing you'll have to add/subtract 1 after/before operations a lot.
The only upside I'm aware of is that the 'length' of your array is now a valid index, which is mostly not very useful unless you want to iterate backwards.
It doesn’t miss the point, because the point was that non-developer humans will do better with 1 based indexing.
Side note: As a former developer, turned regular human being who scripts from time to time, I don’t find switching between 0 based and 1 based indexing particularly intellectually taxing.
Yeah I have a bunch of complaints about the language that I developed while learning it and simultaneously teaching my 9 year old kid, but the 1-based indexing isn't one of them. That's totally fine, and certainly easier to grasp for a beginner.
But there's certainly other stuff that's just weird and bad, and I think just the fact that it's dynamically typed made it harder for him to build an accurate mental model -- sometimes he didn't even realize that an identifier that he had typed in earlier was a variable, let alone what kind of value it'd be holding.
Well, i started programming in Pascal (with 1-based indexing) and later switched to C. I remember that after switching to C off-by-one errors magically disappeared.
Wait till you need to use a formula which has somewhat complex uses of indexes like 2^(2n+1) and see how having deal with them in 1-base indexing in addition to 0 based indexing makes it add bit of unnecessary confusion.
It's not an intellectually challenging context switch, which is actually what makes it such a problem. You don't have to think about it, because it's so simple and easy, so sometimes you won't even think to do it.
I've written a fair bit of Lua in last 5 years and never once cared how arrays are indexed. `for _, value in ipairs(mytable)` works quite well. Looping by index hasn't been necessary.
Agreed. 1-indexing may be more intuitive for humans broadly, and this may have been a good argument before C became ubiquitous, but when it comes to programmers the landscape of what's intuitive is different; 0-indexing is a deeply ingrained habit. Very few norms are so universal across programming languages
That’s more dependent on language syntax IMO. When you have traditional C-style loops, maybe. When you use ranges, iterators etc. it doesn’t make any difference.
If Lua is range/iterator-heavy (I haven't used it), then yeah that would definitely ease the burden, but I wouldn't say it doesn't make any difference unless you never ever index anything directly
Brillant. This distinction nails it. Luckily it doesn't matter much in 99.9% of the cases where one just enumerates/filters/maps a collection and indexing is not involved.
I don't really see how 1-based indexing makes anything better. It just requires sprinkling magic +1 and -1 in various places.
Really only reason to use 1-based indexing I see is to make array index arithmetic needlessly annoying, to discourage users from doing any complex operations there..
Aren't those +1's and -1's just the mirror images of the -1's and +1's you have to do in a 0 based system?
This whole conversation is really silly. 1 based indexing is such trivial detail of lua that I'm not surprised so many words have been spent discussing it.
Doesn't it bother you as the person writing both the application and the original scripts, to be using both styles at once in the same product (sometimes to refer to the same data)? Nevermind the end-users' additions.
Why do so many languages restrict arrays to 0 or 1 based indexing? I much prefer the way it is done in Basic where you can choose what you like for each array or even better Pascal where you can use any ordinal type (including boolean) as the index and you can define subtypes of the ordinal types to use as index types.
Oddly enough, Lua was my first introduction to programming. I was into Valve's Half Life games and Garry's Mod, and Lua was typically the way that you'd make modifications to things (e.g. increase player movement speed). That would interact with the Source engine, which was in C++, but you could do a lot of stuff without ever touching C++ [1].
I didn't even really think of it as programming at the time. You start by just changing constants, like what if we make gravity = 2 instead of 9.8 m/s2? And then build up from there. I think is what a lot of these educational programming games try to replicate today.
After a while, I got confident enough to the point of trying to do things in LÖVE, which is a free and open-source 2D game engine that uses Lua [2]. What I learned there is the reason why I eventually studied computer science in college. That said, when I return to Lua, it feels a bit limited, but I suppose that's the point! I never really questioned it at the time.
"Lua is not object-oriented. That is it." Laughed out loud when I read this. It was funny to learn programming without the concept of classes, and then realize that Lua is the exception, not the rule.
Thanks for linking my article. I went on to go build Half-Life 2: Sandbox as an open-source competitor to Garry's Mod, and later Planimeter's Grid Engine, which is also based on LÖVE.[1]
Lua was mine too, also via games. I’d installed custom firmware on my PSP and after getting my fill of emulators and rampant piracy, I wanted to see if I could make a game. Getting the C-based toolchain working was far beyond 12yo me at the time, but there was a “Lua Player” homebrew application that would load scripts off the memory stick.
I didn’t get far with it and to be honest I remember very little, but it did set me off down the rabbit hole of learning to program, and now it’s both my career and a big passion.
> Lua is small enough that newbie developers can understand its internals well and keep it in their mind.
This is, by far, Lua's best "feature". If you know programming you can learn 90% of Lua in 15 minutes (metatables and the C stack interface takes more time).
For a language meant to be used by non-programmers this is more than precious, it is essential.
As someone that worked on LuaJIT for embeded devices (IE phones), I have some strong opinions / context.
Lua is designed to be small and portable and highly embeddable. It's not the greatest language in the world, but understanding it's interpreter and how to use it from your system side is pretty easy as these things go.
It's really REALLY well suited to be embedded into whatever system you need it in. The whole runtime is like nothing.
And it runs damn fast for an embedded language, there's like 10 opcodes or something? compared to pythons 100+ (at the time we did the comparison like 10 years ago).
Further context, I think we ported our android luaJIT to windows phone 7 in like, a day? It was super trivial.
Right, but reducing the number of ops wouldn't get you there, that just adds more branches in the critical path as the same amount of functionality needs to be overloaded.
My first real job after school was working on Lua (5.1a mostly) in an embedded context. It was much faster than iterating on the QT C++ application that the Lua was embedded in. It would, eventually, even allow us to do custom targeted push updates to our software (in 2009).
So I love Lua. I appreciate its speed and its flexibility. I think of it as Javascript's easy to use cousin. Straightforward to write and debug - you control the entire environment.
I also think this idea is deeply silly:
>People who want batteries want a ready-made powertool full of opinions. They are not looking towards Lua in the context that Lua wants to exist.
I would have loved ready-made powertools! I wouldn't include all the tools in my toolkit (and we already added some by building our own Lua with a few patches), but it sure would have saved me time writing them (and writing them badly as I was just out of school). I don't think a complicated, robust library would hurt Lua's mission and I'm sad to see this attitude in the community. I understand that Lua may be too small to support a "big" set of official libraries, but I think the idea that offering fewer options is better than more options is wrong.
But there are ready-made powertools. There are even really nice "standard"-like libraries such as penlight. Depending on the environment you're using, you can have a Lua distro that already contains all the most useful libraries already baked in. That stuff is out there already for those who want it.
In my opinion, one should assemble their own. You don't need to develop everything yourself, but you can grab the libraries you know you'll need/want and craft a little baseline Lua for you. LuaRocks can do that with some clever argument passing and fiddling with the search paths. Then, you're in control again, and your powertool is definitely yours.
People employed as programmers who are vocal in mailing lists, forums, blogs, etc. can have some very warped views on programming. Curiously, their statements often take a didactic tone. It seems they want to tell others what to do and what not to do. Sadly, these are not the folks who anyone should be attempting to "learn" from. They are antithetical to curiousity. Nice to hear something from someone who is not working as a programmer. This is a much clearer perspective, IMO. At the very least, regardless of anyone's opinion, it gives us a more balanced perspective.
I have always understood the reason why Lua keeps getting embedded into games and game engines is because it's a language that you can reasonably hope non-technical people to be able to contribute a little bit from.
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.
I've had the opposite experience. Teaching my 9 year old son Lua has been kind of painful, in large part because of language decisions that make learning things harder for beginners.
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?
I gave you an upvote because I think you don't deserve to go negative just for raising some of these concerns. Still, I'm going to examine some of them.
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.
> 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 the `_G` metatable to never allow the creation of keys
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".
> 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?
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.
Lua has a type of value called "userdata" that is just an opaque reference to a C resource. There's no way for Lua to know how do a deep copy of that resource, if it's possible at all. Tables can also have "metatables" that modify how they function. It's not obvious whether a deep copy should also create a deep copy of the metatable (and its metatable, and so on) or whether the copy should share the original metatable. There's good arguments either way.
It's worth noting, I'm mostly not defending Lua here. I enjoyed my time with it, and then when I put it down I realized how unhappy I was with it.
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.
Not sure about that third point. Ruby desugars += and similar compound operators into separate operate and assign steps. There's no need for an implementation of += in addition to +. I'm sure Lua could do the same thing.
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.
I think Lua accounted for the idea of desugaring, where PUC-Rio said that they didn't want the added complexity, but I don't think I could readily find that justification.
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.
It's not designed for the purpose of being beginner friendly, afaik.
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.
It's definitely surprising that globals default to nil, but it makes a little bit more sense in the embedding context where the global environment is controlled by the C code. Overall there are a lot of decisions (like the lack of a function to copy tables) that are based on the assumption that any given Lua code was written for and executing in an environment created by a programmer who is going to roll their own standard library, make any tweaks to the syntax they like, etc. And I agree that for that reason it's not in itself a great language for learners, but that's not exactly the same as being a great language for non-programmers.
> It's definitely surprising that globals default to nil, [..]
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 :-)
Since Lua 5.2 non-locals are loaded from the table/object _ENV. Every lexical context has either an implicit _ENV, or an explicit _ENV (e.g. nearest `local _ENV = ...`). The default value for implicit _ENV is the global table (i.e. _G, but not literally _G; it's initialized as if from lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_MAINTHREAD)). See https://www.lua.org/manual/5.4/manual.html#2.2
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.
At this point, I've worked 'a bit' with Lua, in addition to plenty of other coding experience, and it's still surprising to me.
> 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.
So just to be clear here, the things you're recommending a nine year old to do are: ignore tutorials in favor of reading directly from the language reference manual, and keeping in mind the language's C API parity.
> 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?
> the things you're recommending a nine year old to do are
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.
Sure, which is why I said "surprising" and not "inconsistent" or "weird". New Lua devs are likely to have to debug an issue caused by typing "ponits" instead of "points" well before they get to the point that they learn about the global table.
> And I agree that for that reason it's not in itself a great language for learners, but that's not exactly the same as being a great language for non-programmers.
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's definitely surprising that globals default to nil
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
hey, I'm the author of the article. Thanks for linking.
That being said, I'm always impressed with the fervour and effort that people put into complaining about Lua 1-based indexing. Folks, using a Meta Table, you can change the indexing to be 0-based. It is such a small thing. Dissing a language full of potential and flexibility because of that is overreacting.
Also, those wishing Lua was friendlier environment catering to the needs of young audiences should remember that Lua was not created for that market, but that an environment that does that can be built on top of Lua. You want teach your kid a fun language, teach them a Lisp. It is easy, regular, and fun. Go with Racket. The error messages are good. Or, help someone who is making a Lua-powered game development environment make their app more friendly towards younger audiences. That is not a language's fault.
The main part of that article is to approach Lua as a toolkit. Do you want zero-based arrays? Do you want errors to be triggered for undeclared variable access? You can built a Lua environment with all of that. Want batteries, you can add them too.
For those asking about table copy, lodash, etc. Lua is minimalistic. If you want to add a bit of weight, I recomend using penlight library:
As I mentioned. It is a toolkit. You can make it 0-based and then use it everywhere as zero based. You don't need to mix it. In my opinion, it is not worthy it. I just mentioned it to highlight that if you truly want that, you can do it. It was more to show that it is flexible language and that you can roll your own. Not that I advocate doing it.
That came up a few days ago, but in the context of Julia. I wrote a long response at the time [0]. I find it interesting that that's his title ("Why numbering should start at zero") but it really only makes sense as an argument (that starting at 0 is best) if you accept that this is the proper notation for ranges:
a <= x < b
If you accept that, then the rest of his argument follows. But if you use:
a <= x <= b
Then 1-based (where a = 1) indexing actually makes a lot of sense as well, because the other reasons for selecting his preferred range notation fall away. 1-based is only "weird" if you use his range notation because then you describe ranges as:
1 <= x < N+1
Where N is the length of the range. But, if you use the second form:
Djikstra's argument is, pound for pound, one of the densest things I have ever read.
If you spend some more time with it, and you really should, you will discover that most of it is in fact arguing that what he calls convention a) is the best choice.
So what you're saying here is circular; of course if you accept his argument, then you accept his conclusion, which is the title of the paper!
You really should accept his argument though. It's quite persuasive. You're really just saying that 1-based indexing is okay if you use convention c), as indeed, Lua does.
But you're not even touching on why he says not to. If you do, I'm confident you'll conclude, as the field in general has, that a) is the more powerful and general choice.
But I would like to do more than just gesture at Djikstra's magnificent argument and say "reread this!". The One True Wiki has a useful discussion of the subject: in particular, I find the observation that 0-based indexing unifies counting and measurement to be persuasive, and it's found nowhere in the paper in question.
I have read this EWD several times now, I think he makes a solid case for:
a <= x < b
over the other three forms in the general case. But he fails to make an argument for the special case of 0-based indexing vs 1-based.
When restricted to just the discussion of 1-based and 0-based:
1. The experience report doesn't seem to apply as it's about the general notation advantage, not the specific cases of 0- or 1-based indexes. We would need additional experience reports about those (which I suspect would tend to favor 0-based).
2. We don't need to worry about calculating the range size, because it's obvious for both cases. Yes, (a) is better if you're using 0-based indexes and it's awkward if you're using 1-based. But (c) is better if you're using 1-based and awkward if you're using 0-based. (a) leaves the range size of a 0-based index in the range description, and (c) leaves the range size of a 1-based index in the range description. The argument becomes a wash, neither is obviously better than the other on this basis.
3. The argument that (a) is better because you can see if two ranges is a persuasive one. But how do you have two adjacent ranges when they both have the same starting position? It's irrelevant to the case for either 0-based or 1-based arrays.
His final argument is that:
1 <= x < n+1
is more awkward than
0 <= x < n
But he doesn't even present the alternative:
1 <= x <= n
is less awkward than:
0 <= x <= n-1
Again, a non-argument because it assumes the outcome he wants, that 0-based is fundamentally better.
Now, all that said 0-based has other advantages. But Dijkstra fails to address those other advantages. Ultimately, though, as I said in the other discussion, your language shouldn't restrict your range description options to either.
> You really should accept his argument though. It's quite persuasive.
I don't think anyone should accept the argument because it is not persuasive. There are much better arguments for 0-based indexing than this EWD. We should scrap flawed arguments when we have much better ones available. The best part of the EWD is the description of which range notation is "better", but not the part about 0-based indexing.
In (c) with 1-based ranges the size of the range is present in the range. There is no calculating because the value is there. 1 <= x <= size
One argument for the notation (not 0 vs 1 based ranges) is that notation (a) offers easier detection of adjacent ranges. This is irrelevant to the 0 and 1 based range debate.
As a dev who’s only tried Lua for a small dev project, I have to agree with the no-batteries argument. It is so bare that i ended up implementing tons of function that i wished were there in the first place.
Not asking for lua to become the new javascript but a library like “lodash for lua” would be appreciated...
We should forget about Lua. We have better languages today. I did write code in Lua quite recently and was banging my head against lack of bitwise operators, which were added in the version later that the one that ships with the package I was extending. It is a language written in what seems to me to be a very organic manner with features added as and when they were needed, not per design, with predictable results of basic features that programmers today expect missing.
I was writing a game in Lua using LOVE. I used to really enjoy and appreciate Lua. I've even integrated it into some applications using the C libraries in the past. It's always served me well.
But during the development of the game, things got so unruly that we ended up having to abandon it. The code itself was written as good as it could possibly be given the engine and for Lua, but we had a lot of really poor-performance hacks for the 1-based indexing, for example.
Further, the lack of proper operator overloading prevented us from using certain libraries to bind to it, e.g. Kiwi, for UI layouts.
Not sure I'll ever use Lua as a scripting host in the future. There are a few alternatives I really think could suit the job better.
>You can grab the source for the version you want, and then never again look at Lua community.
Sure, but it's probably not the best move if you're planning on any collaboration or second-party input. Ambitious projects require more brainpower than one average programmer has. And that's not even considering multiplication of resources spent on similar things.
The gist of that article from LWN is that a lot of people see Lua as the perfect base for building "flying boats", but what lacks is some semi-official directions for the part of the community which is interested in such endeavors. In my opinion this wouldn't harm the language in any way.
What I am arguing on the post is that there are many "flying boats" built with Lua already. There are batteries-included distros of Lua. They don't need to be official, and in my opinion they shouldn't be official.
It's a statically typed dialect of Lua, which is compatible with all modern Lua versions.
The core compiler itself is one tl.lua file which can be added to any Lua project, and includes a Lua package loader that makes Lua's `require()` support .tl files, and there's also a tl CLI for generating .lua from .tl files.
I’ve discovered Lua only very recently as it has a good presence in some music making applications for scripting e.g. for the Steinberg HALion sampler/synth and for Native Instruments Creator Tools.
And more recently I bumped into the interesting VST/AU plugin called protoplug[1], which makes it easy to experiment with coding audio and midi event processing inside a DAW.
Lua static binary can be made to be less than ~150k, while providing very advanced high level dynamic language features you'd expect from JavaScript or PHP.
I used it to build a admin UI for an embedded device without an MMU. The only other alternative available was C/C++, which slowed the iteration cycle significantly.
Yeah it does things in a strange way, but when I used it, those quirks are vastly overshadowed by its benefits.
I’m thinking about trying to add Lua support to a small motor controller to provide some more advanced capability inside the control loop. But I don’t actually have any experience doing this, and I can’t even decide if I want to try to compile the bytecode on the device or do that off the device and push the blob to the device.
If anyone wants to be paid for porting https://github.com/nodemcu/nodemcu-firmware firmware to new ARM hardware + ethernet (not wifi) let me know. Other ongoing embedded work available.
Off device cross-compilation of the Lua interpreter binary, but no bytecode compilation -- it was interpreted Lua from what I can recall (10 years ago). Can't tell what's possible now, and I feel like a microcontroller might be too much for Lua.
Redis has Lua engine inside since 2.6, allows lots of fun things. One can implement a distributed set of finite state machines on a Redis server, and get a key-level consistensy for free due to Redis' single thread nature
Honestly it was an awesome experience both playing the game, seeing the game from a different pov, doing meaningful mods, and last but not least learning Lua. Still one of my favorite book even though it's outdated to some extent cause WoW and the whole backend changed so much.
I wish there was something similar today as well for the "new generation".
I don't know if I am misremembering it but I believe that in a talk called "How much does it costs?", Roberto (one of the Lua creators) touch a bit on why there is no ++ and --:
I'm a programmer who recently started teaching his 9 year old son Lua, so that he can use the Undertale fan game platform Create Your Frisk. It's gone okay, but there are things about the language that just seem weird and annoying and counterproductive.
For example, tables are very important in Lua as a sort of all-purpose data structure, that's cool and all, but why is there no built-in copy function? The author of this article seems to be hinting that this anti-batteries-included style grants some sort of freedom to do it myself, which is just kind of silly. I don't want to do it myself -- and there's a good chance I'd cock it up if I tried -- my son certainly isn't going to do it himself, and even if they had provided such a function, that doesn't actually stop anyone from doing it themselves anyway, if they want to.
Or, why are there not only no increment operators like ++, but even no operators like +=? "counter = counter + 1" isn't only longer to type, it's harder to quickly parse. Why is the standard style for multiword identifiers neither camelCase nor underscore_case (or whatever it's called), but instead just runthemalltogetherit'stotallyfine? Why is the default for the random number generator using the same seed every time you run the program, and why does os.time() provide time not in nanoseconds, or milliseconds, but solely in whole seconds? Why doesn't the runtime complain if I pass in a completely non-existent variable as an argument into a function?
While the teaching has gone fine overall, the language actually works against our purposes by making it harder for my son to build a mental model of what's happening. The last point in the previous paragraph, for example: it's much more confusing for a newbie coder to have to deal with a 'sudden' nil value that they weren't expecting, than to have a compiler or runtime just crash and say "hey, I don't recognize this variable at this line number". And yes, this isn't hypothetical, this actually happened while I was teaching him.
Same thing for the dynamic typing, and default global status of variables. The lack of 'declaring' variable types, or even that a thing is a variable at all, has meant that it's been more confusing for him to identify what kind of value a variable is holding, or even that an identifier is actually a variable at all. That variables are by default global has made it harder to teach him about passing in arguments -- he'll happily pass in some variable as an argument, and then use that variable name within the function, rather than the argument name defined in the function signature. These are the kinds of beginner-level problems that you forget about as an experienced programmer, but when you teach a kid, suddenly become very real.
As I was using the language and teaching with it, I just couldn't help but feel that more modern statically typed languages like Swift or Kotlin would be flatly superior for the task of instructing a young mind on the the most fundamental task of beginner programming: getting them to build an accurate mental model of "how this actually works, how does everything relate to each other". You would get the enforced structure of older static languages, but with more modern syntax and ease of use. To me, it's hard to see what Lua would offer as an advantage, really. But then again, I'm an Android developer, so maybe it's just my own experience biasing me.
The Roblox dialect of Lua supports += etc, but how they do it points to why basal Lua doesn't.
In Lua, you can say `a, b = foo(), bar()`. This can have all sorts of unintuitive consequences if there is an addition after, and one of the function calls mutates something the other one depends on. you can also have `a, b = foo()`, since it can return two values.
So Luau (the Roblox dialect) limits += statements to one lvalue, which is unsatisfying but at least a reasonable decision. I think it's worth having, but I get why Lua doesn't include that: they're quite committed to a minimal set of abstractions which compose cleanly.
There are similar answers for all of your questions but, more to the point: Lua doesn't try to be a teaching language. It's minimal, easy enough for a skilled programmer to acquire, and remarkably powerful for how tiny the whole thing is. But to really get somewhere with it, you want to grasp the entire language, and that works against it being a good first language.
That said, it isn't a bad first language either. It does demand more of you as a teacher: we can't expect a nine-year-old to copy-paste a table clone function out of the Lua wiki, or find strict mode in penlight, but it sounds like you're capable of both of those things.
Neither of those are hypotheticals, they're hurdles I cleared years ago and have to consciously remind myself exist in a clean install of the language!
A lot of those issues relate to the fact lua is dynamic and typeless. It can for sure be a pita when you have a simple typo in your variable and you don't find out until you run the app and hit that code path.
Yes, I agree. Though I don't remember python behaving this way, exactly, in terms of ignoring attempted use of non-existent variables.
> you don't find out until you run the app and hit that code path.
The real problem was that we didn't -- or rather, he didn't -- even find out then, since it just looked like the variable was somehow holding nil, instead of the variable not actually existing. It looked like there was some other reason for the variable accidentally being nil -- and when you're a total beginner, it could be nearly anything.
This setup is just...it's plainly more beginner-hostile than a compiler/runtime simply telling the coder that there's a problem, what the problem is, and what line it's at. So I don't understand the people talking up Lua like it's great for beginners.
But yeah, I definitely dislike dynamically typed languages in general. Having static typing with type inference ala Kotlin just seems to be much better all around.
It's less that Lua is great for beginners than that it's simple to get started with in the environments where it's embedded. It was originally a configuration language, and in a lot of ways it's still more optimized for "open up a text file and plug in some different values for the running program" than it is for implementing actual logic. (That's what I meant by "non-programmers" in the other comment.)
And static typing is hard to make work when your codebase is made up of a lot of independent snippets of code which run in an environment completely determined by an outside program. You'd need something akin to TypeScript's external type definition files.
Okay yeah, that's fair. It just seems like the advantage of "is easier to build larger programs in a coherent way" is more useful than "is easy for non-programmers to get started right at the beginning" for a lot of the contexts it's used in.
function no_globals()
setmetatable(_G, {
__newindex = function(t, k, v)
return error("attempt to set global '"..tostring(k))
end,
__index = function(t, k)
return error("attempt to reference missing global '"..tostring(k))
end,
})
end
can help catch assignment and usage of undefined (e.g. by not using 'local') variables. The downside is that this then also applies to function definitions. So be prepared to write 'local function xyz()'.
People keep pointing out workarounds to me. Thanks, but that's not really what I'm getting at.
My complaint is that, if the language is supposed to be good for beginners, the workarounds should be unnecessary, since beginners are unlikely to a) be able to find proper workarounds, or b) even be aware that there's a problem that a workaround would be needed to solve.
My son has no idea that invoking an un-initialized global variable immediately brings it into being with 'nil' is weird behavior for a language. If a less experienced programmer was the one helping him learn, they might not realize it either.
As I said in a sibling comment, Lua isn't designed to be good for beginners, it's simple rather than easy.
But I want to stand up for this, in particular. Because in most languages, environments are magic: they're an exceptional thing that you work with in a special way, you're not expected to understand them, just use them.
In Lua, an environment is... just a table! You can set an environment, read from it directly, assign it a metatable, and so on.
So this is great for a beginner, once he gets past the hurdle. Just like `table.field` returns `nil` if there's no field field, `field` itself returns nil if there's no `_G.field`. Because _G.field and field itself are the same thing.
> So this is great for a beginner, once he gets past the hurdle.
Is it? Because that still seems very error prone to me. You're not gonna suddenly stop making typos just because you understand that there's an invisible global table around all the time.
But hey, maybe it's super awesome later on, though I'm having trouble imagining scenarios where this behavior is all that useful over what most other languages do.
The birthday example is evidence against 1-based indexes, not for them: on the birthday when you're turning "1", it's your second visit to that date. Your first visit to the date of your birth, you're "0".
I think this misses the point, at least for my case. It doesn't matter which is technically better. What makes more sense for humans is a matter of application (and even "non-dev" stuff varies as to which is better).
For me, my big concern about using Lua in my products is
- I expect other developers to be writing the Lua scripts
- I expect the developers to be frequently using 0-indexed languages
In this scenario, "being different" means you are likely to run into off-by-one errors. Think of that crufty Perl script you might have around at work where people avoid touching it because no one is comfortable enough with Perl. Having predictable semantics helps a lot in scenarios like this.