Lua - a scripting language you can like

  • TODO: this page could really use an interactive Lua interpreter. can we have that?

    2024-03-08
  • Lua is a really cool language! did you know that?

    2024-03-08
  • lots of people complain about it being really weird for various reasons, but these are generally superficial

    2024-03-08
    • usually it’s cosmetic stuff, so these aren’t any arguments of technical merit, but…

      2024-03-08
    • stuff like indexing from 1 instead of 0, which is just a design choice and does not impact your programming that much

      2024-03-08
      • in fact, one could argue that regular programmers are weird for counting from zero thinking

        2024-03-08
      • the biggest impact this has is on rendering code, where you have to subtract 1 to position things relative to the origin - which is at (0, 0) (or (0, 0, 0) in 3D.)

        2024-03-08
    • or using ~= instead of !=, which is just a syntax choice, and you only have to get used to it once

      2024-03-08
    • or using do..end style blocks instead of {..}, which again is just a syntax choice and does not impact programming that much

      2024-03-08
      • it’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

        2024-03-08
    • TODO: this section could use some links to actual complaints or statistics or something. anecdotal evidence is not evidence.

      2024-03-08
  • but I think Lua is a pretty damn genius programming language.

    2024-03-08
    • 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

      2024-03-08
      • partly because it’s so fucking bold I can literally not imagine myself designing a language with a strong distinction between hash tables and arrays, and even tuples and records! but the designers of Lua had the restraint to just have One.

        2024-03-08
      • 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

        2024-03-08
        • in fact object oriented programming in Lua is typically done by overloading the [] indexing operator.

          2024-03-08
          • the way it works is that a.b is just syntax sugar for a["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)
            
            2024-03-08
    • 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.

      2024-03-08
      • though unlike Go, Lua has the goal of being small because it needs to be embeddable, especially given it’s used in very constrained environments in the real world. (microcontrollers!)

        2024-03-08
        • therefore there are technical, not just ideological reasons to keep the library small.

          2024-03-08
      • and I really like that from an embedder’s standpoint, it’s possible to completely disable certain standard library modules for sandboxing!

        2024-03-08
    • 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.

      2024-03-08
      • 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.

        2024-03-08
        • 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.",
              }
          }
          
          2024-03-08
          • JUST LOOK AT HOW CLEAN IT IS! with no need to invent magic syntax or anything!

            2024-03-08
          • 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!

            2024-03-08
      • there is also the incredibly useful sugar for indexing tables by string literals: instead of table["x"] you can write down table.x

        2024-03-08
        • and there is also the incredibly useful method call sugar table:func(), which gets transformed to table.func(table); and function definitions like function table:func() end are sugar for function table.func(self) end. ain’t that neat and simple, yet super useful?

          2024-03-08
          • if you don’t get the usefulness: this is needed because object oriented methods in Lua are implemented using regular functions; there is no magic this or self parameter. the parameter is explicit, there is just sugar for passing it into functions and declaring functions with it.

            2024-03-08
  • 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.

    2024-03-08
    • 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.

      2024-03-08
      • 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.

        2024-03-08
        • this is precisely where my hate for NaN propagation was born.

          2024-03-08
    • there’s Teal but last time I checked it didn’t have support for inheritance, which is heavily used by LÖVE, which is my go-to Lua graphics framework.

      2024-03-08
    • 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.

      2024-03-08
  • and it’s really a bummer that Lua is not that strict!

    2024-03-08
    • 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.

      2024-03-08
      • 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
        })
        
        2024-03-08
  • there are also some bits of syntax that arguably haven’t aged very well.

    2024-03-08
    • 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)
      
      2024-03-08
      • the lack of a pipelining operator |> is also an annoyance, albeit most modern imperative languages don’t have it either.

        2024-03-08
    • page implementing classes in Lua

      2025-02-08