lots of people complain about it being really weird for various reasons, but these are generally superficial
or using
do
..end
style blocks instead of{
..}
, which again is just a syntax choice and does not impact programming that muchit’s a tad bit more line noise, but not that terrible. I did design a language using
do
..end
blocks and it really doesn’t look that bad
but I think Lua is a pretty damn genius programming language.
the use of tables as The One Data Structure for Literally Everything strikes me as a 200 IQ choice I could never come up with myself
tables are extremely powerful in what they can do, because they’re more than just a way of structuring data - they also allow for interfacing with the language syntax through operator overloading
in fact object oriented programming in Lua is typically done by overloading the
[]
indexing operator.the way it works is that
a.b
is just syntax sugar fora["b"]
, which means you overload[]
to fall back to another table - and that way you can achieve prototype-based inheritance!local fallback = { b = 2 } local base = { a = 1 } -- The __index field can be both a function _and_ a table. -- { __index = the_table } is a shorthand for { __index = function (t, k) return the_table[k] end } setmetatable(base, { __index = fallback }) assert(base.b == 2)
I’ll be honest that I don’t like the standard library of Lua from a usability standpoint, but maybe it doesn’t need to be bigger. it’s similar to the principles of Go, where the language encourages using dumb constructs rather than super clever code with lots of abstraction.
Lua also knows very well how much syntax sugar to have to make writing code pleasant, but not to overdose it so much as to give you instant diabetes.
as an example, there’s function call syntax: you can pass it a string or table literal, which is just enough to enable some really nice DSLs without making the grammar too complex.
once upon a time I dreamed up a DSL for building GUIs using this sugar.
render { width = 800, height = 600, title = "Hello, world!", vertical_box { header1 "Hello, world!", header2 "This is an example GUI.", } }
the only missing thing then would be list comprehensions to be able to transform data into GUI elements, but even that can be ironed over using function literals:
render { width = 800, height = 600, title = "Hello, world!", vertical_box { header1 "Hello, world!", paragraph "This is an example GUI. Here's a horizontal list of numbers:", horizontal_box { function (t) for i = 1, 10 do t[i] = paragraph(tostring(i)) end end, } } }
interpret this code however you want, but damn it looks clean. again with no magic syntax!
I really wish Lua had at least a form of static typing though, since knowing about errors you make early is really helpful during development.
it regularly happened to me that a type error I made only occured at some point later during runtime; and then you have to track down a reproduction case and make a fix at the source. not fun.
there’s also the ugly case I had with a division by zero in the last rewrite of Planet Overgamma, which caused a NaN to propagate through physics and into rendering, causing a crash.
you can also compile TypeScript to Lua, which is insanely silly, but has the advantage of using a language that’s more familiar to a very wide group of people. I wouldn’t use it though because TypeScript and Lua are very different languages, and I’m afraid certain transforms would be unobvious - which would make interfacing with existing Lua code harder. I think I prefer the bolt-a-type-system-onto-Lua approach of Teal in that regard.
and it’s really a bummer that Lua is not that strict!
global variables by default are a pretty bad design choice in my opinion. having any form of uncontrolled globals hurts local reasoning and makes it harder to tell whatever your code is going to do.
but fortunately it is possible to freeze your global variables by overloading the indexing operators of
_G
- the table that represents the global scope.setmetatable(_G, { __index = function (t, k) -- Only tell the programmer about undeclared variables. We still want access to -- builtins like `require`. if t[k] == nil then -- The error message is purposefully generic because this will probably happen -- the most when misspelling variables. error("variable '"..k.."' was not declared in this scope") end return rawget(t, k) end __newindex = function (t, k, v) -- Assigning to global variables usually happens due to typos with local variables, -- so again - the error message is intentionally generic. error("variable '"..k.."' was not declared in this scope") end })
there are also some bits of syntax that arguably haven’t aged very well.
as much as people complain about cosmetics, I think there’s a particular design choice that has aged very poorly in the face of modern, functional programming - function literals.
these tend to be quite verbose in Lua which hurts readability in functional code:
local u = map(t, function (v) return v + 2 end)
compare that to JavaScript’s arrow functions
=>
, which I think are a prime example of good syntax sugar that encourages more function-oriented programming:let u = t.map(v => v + 2)