100 Languages Speedrun: Episode 04: Lua

Lua is a small programming language from Brazil, and the possibly the only tech that came out of Brazil that made significant impact worldwide.

The main feature distinguishing Lua from other languages is that it's really well adapted to being embedded in existing applications, and it's especially popular for video games (here's just a partial list-scripted_video_games)).

In principle you can embed just about any virtual machine for any existing language like Tcl, Python, JavaScript, or whatever else you like. This tends to be much more complicated than embedding Lua. Nowadays JavaScript is increasingly pushing Lua out of its main niche, but if you want to get into game development or modding, some basic Lua is still an useful skill to have. As we explore Lua, you might discover a few reasons why it's losing popularity.

Hello, world!

You probably won't be overly surprised by this code:

print("Hello, World!")

Here's Fibonacci, doesn't look too weird other than the -- for comments and range loop syntax:

-- Fibonacci function
function fib(n)
  if n < 3 then
    return 1
  else
    return fib(n - 1) + fib(n - 2)
  end
end

for i = 1,30 do
  print(fib(i))
end

And the FizzBuzz:

function fizzbuzz(n)
  if n % 15 == 0 then
    return "FizzBuzz"
  elseif n % 5 == 0 then
    return "Buzz"
  elseif n % 3 == 0 then
    return "Fizz"
  else
    return n
  end
end

for i = 1,100 do
  print(fizzbuzz(i))
end

Tables

Lua has a single data structure called "table" that serves as both an array/list and a dictionary/hash/object.

Let's see how it works in practice:

local x = {"foo", "bar"}
local y = {"foo", "bar"}

print(x)
print(y)
print(x == y)

What we'd naively expect:

{"foo", "bar"}
{"foo", "bar"}
true

However, what we instead get is:

table: 0x7fb9cee04080
table: 0x7fb9cee040e0
false

That's right! Lua does not have equality working on complex types (the same blight shared by JavaScript), and it doesn't even have builtin console.log.

Let's write our own inspect

Well, it's not overly difficult to write our own inspect. It's not amazing, doesn't do any pretty-printing, and it can get into infinite loops if data links to itself, and so on, but it should serve our purposes for now.

function inspect(value)
  if type(value) == "table" then
    local result = ""
    for k, v in pairs(value) do
      if result ~= "" then
        result = result .. ", "
      end
      result = result .. tostring(k) .. "=" .. inspect(v)
    end
    return "{" .. result .. "}"
  else
    return tostring(value)
  end
end

local x = {"foo", "bar"}
local y = {name="Bob", surname="Ross", age=52}

print(inspect(x))
print(inspect(y))

Which gets us:

{1=foo, 2=bar}
{age=52, name=Bob, surname=Ross}

What did we learn?

  • type(value) returns type of whatever we pass - which is "table" for most complex types
  • strings can be concatenated with .., there's no string interpolation
  • != is spelled ~=
  • order of keys in a table is not preserved
  • array numbering starts from 1!

That last one might be a bit of a shock. Back in the days, programming languages were split between 0-based and 1-based indexing. Lua is about the last remnant of these times, 0-based indexing having won.

By the way Perl hilariously had $[ which was a special variable determining array indexing, which you could set to 42 for all it cared. They removed this feature at some point. It was actually not completely insane, it was designed to help porting awk scripts to Perl. Maybe I'll get to that story at some point.

Unicode

Let's see how well Lua deals with Unicode:

a = "Hello"
b = "Żółw"
c = "💩"

print(a:lower())
print(b:upper())
print(#a)
print(#b)
print(#c)

And as it turns out, extremely poorly:

hello
ŻółW
5
7
4

Unfortunately :lower() and :upper() don't know anything about Unicode, and # returns number of bytes, not length of the string (string.len(a) is just like #a, returning number of bytes).

Should you use Lua?

Honestly for new programs, not really, but it's still worth knowing the basics if you're interested in game development. It still has significant presence in game scripting. As you've seen, even doing very simple things we kept running into problems due to weaknesses of the language.

Lua also seems to have significant issues with community fragmentation. High-performance LuaJIT implementation only supporting fairly old version of Lua 5.1, while the main language moved on to 5.3 already. As Lua code tends to be embedded in some engine (usually game engine), a lot of code depends on various functionality provided by the engine and won't run elsewhere. LuaRocks has 3000 packages, which is tiny compared with 130k ruby gems, or 1.3M npm packages, even if all the rocks ran on every Lua, which they don't.

Right now Lua looks like a language on the way out, but things could still turn around. And unlike most other software - video games see use decades after their release, and with them their Lua code.

Code

All code examples for the series will be in this repository.

Code for the Lua episode is available here.