Comment by kragen
1 year ago
I feel like, except for small size, this post doesn't really get into what I like about Lua, which is pretty unfortunate. Lua deserves a better case than this.
I'm maybe not the best person to write this because I'm not really a huge Lua fan. Though on projects with others I'll use whatever language they're using, left to my own devices, I probably write about 3× as much JS as Lua, about 32× as much Python as Lua, and about 20× as much C as Lua.
But Lua has some really nice attributes.
It's a pretty reasonable very-high-level language comparable to Python, JS, Groovy, Perl, or Clojure. You can get a lot done in very little code. You don't have to crank out piles of boilerplate the way you do in Java or sometimes C. It comes with concise and readable syntax, flexible data structures, garbage collection, strong dynamic typing, list structure, hashmaps, dynamic method dispatch, inheritance, operator overloading, cooperative multithreading, eval, exception handling, namespaces, closures with lexical scoping, etc. Its documentation is first-class. Most of this is pretty much standard for the dynamic-language category nowadays; there's not much special in here to pick one language from the category over another.
Lua's biggest advantage is that LuaJIT is motherfucking alien technology from the future. I wrote a straightforward escape-time fractal renderer in https://gitlab.com/kragen/bubbleos/-/blob/master/yeso/mand.l... with a dynamic function invocation in the inner loop (to switch between fractals) and LuaJIT's trace compilation just compiles that right out. There's also a real-time animated raymarcher in that directory in LuaJIT, though the framerate and resolution are pretty bad. You can literally just write high-level code at like a Python or JavaScript level and, as often as not, it just goes faster than C. (Sometimes it doesn't. JIT compilers are unpredictable and sometimes disappointing.)
Now, probably somebody is going to reply to this and tell me that everybody runs JS in JIT compilers too, so this isn't a real difference. Unfortunately that's horseshit. I'm sorry, but V8 and SpiderMonkey are just not in the same league, though not because their implementors are less brilliant. They're just attempting a much harder job, because JS is a much hairier language. Consequently their performance is an order of magnitude worse.
(Even regular non-JIT Lua is a lot faster than most other very-high-level languages, almost to the level of JS JIT compilers.)
LuaJIT's C FFI is also just ... there's no expression in English sufficiently emphatic to express how great it is. The expression I'd normally use translates literally as "it's a very child of ten thousand whores," which hopefully conveys some of the emphasis if not the content. Here's the FFI binding for Yeso, the graphics library used for the above fractal demo: https://gitlab.com/kragen/bubbleos/-/blob/master/yeso/yeso.l.... Yeah, just by pasting the relevant lines from a C header file into your Lua program, you can call C code from a shared library as if it were Lua code. It's almost as easy as calling C code from C++!
After getting screwed over (along with everybody else) by the Python community's gratuitous and ongoing breakage of backward compatibility, coupled with public shaming to persuade more people to break backward compatibility, the Lua community's approach to backward compatibility is looking pretty appealing. The vibe is that old versions of the language live forever, and you should keep using them instead of upgrading, but new versions usually aren't compatible. However, it's much easier to make a Lua library compatible with every existing version of Lua 4 and Lua 5 than to make a Python library compatible with any version of both Python 2 and Python 3.
Being small, fast, and implemented purely in portable standard C makes it practical to run Lua even on larger microcontrollers like the ESP8266, even when they use weird CPU architectures. Even interactively. https://github.com/nodemcu/nodemcu-firmware
LuaJIT is much less portable than PUC Lua, and also more than twice the size, about 580kB on my machine. This is still orders of magnitude less than something like Python or the JRE.
Lua is a little more annoying than Python or node.js to develop interactively with, but it's overall pretty comparable. For code I'm writing myself (as opposed to getting from a library) the Lua version usually ends up being just a little bit bigger than the Python or JS version. Lua is more bug-prone, though, due to a number of language design flaws.
Lua is a very newbie-friendly language, I think even more so than JS or current Python. It's very successful in systems aimed at beginning programmers, like Minetest (Luanti), World of Warcraft, and LÖVE2D. The barrier to entry is very low; you don't have to be learning the GUI of a new IDE at the same time that you're learning the programming language and, possibly, how to program. The standard library is small rather than overwhelming. And the download for standalone Lua development is tiny.
I know +1's are frowned upon here, but, err, +1? But, to cater to one of your criticisms - namely that the surface syntax of lua might be a bit better. It's not bad, per se, but it doesn't quite match the elegance of the lua's semantics or it's flexibility.
In which case, the regularity of lua's semantics come to the rescue. It's much easier than most to develop an alternative syntax which compiles down to it.
Notice I said "alternative syntax", and not "other language which transpiles to lua". Because in the case of moonscript (or yuescript) or fennel - you're still writing lua. Using lua semantics. With no runtime overhead.
Yes, you get this to varying degrees in other ecosystems (javascript et al). But it seems to me that the friction within lua is even lower. Once you buy into "closures all the way down" & "use tables for everything", then no matter what surface syntax you're designing, it's still, pretty much, lua.
I've made changes to the Lua/LuaJIT lexer/Parser to have a C like syntax and a program to convert Lua syntax to LJS syntax you can see it in the repositories shown bellow, the C api and language semantics remain the same.
- https://github.com/mingodad/ljs
- https://github.com/mingodad/ljs-5.4
- https://github.com/mingodad/ljs-5.1
- https://github.com/mingodad/ljsjit
- https://github.com/mingodad/raptorjit-ljs
- https://github.com/mingodad/CorsixTH-ljs
- https://github.com/mingodad/snabb-ljs
- https://github.com/mingodad/ZeroBraneStudioLJS
Cool! Did you fix new variables being global by default? What kind of checking is applied to the type annotations like `: string`?
2 replies →
I think the syntax could probably be improved, yeah, even though it's not really bad. You could even fix default-global at that level! The nil semantics and 1-based indexing in ipairs are maybe a little tougher.