DEV Community

Tomasz Wegrzanowski
Tomasz Wegrzanowski

Posted on

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).

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!")
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

What we'd naively expect:

{"foo", "bar"}
{"foo", "bar"}
true
Enter fullscreen mode Exit fullscreen mode

However, what we instead get is:

table: 0x7fb9cee04080
table: 0x7fb9cee040e0
false
Enter fullscreen mode Exit fullscreen mode

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))
Enter fullscreen mode Exit fullscreen mode

Which gets us:

{1=foo, 2=bar}
{age=52, name=Bob, surname=Ross}
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

And as it turns out, extremely poorly:

hello
ŻółW
5
7
4
Enter fullscreen mode Exit fullscreen mode

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.

Top comments (11)

Collapse
 
romeerez profile image
Roman K • Edited

Thank you for writing!

Lua is a simple little scripting language for embedding. I'm sure Lua is faster and consumes less memory than any other scripting language. To be so lightweight it was designed to be as simple as possible. That's why it doesn't compare objects deeply like python - to not be so slow, that's why it has poor unicode support - don't pack it if you don't need it, if you need it - use a library.

As we explore Lua, you might discover a few reasons why it's losing popularity.

It's a pity that you didn't point to good parts of it.

Indexing from 1 - I was shocked to realize this is possible, it's like a language made for humans!

If I add string and number - I have number "1" + 2 = 3, if I concat numbers I'll get string, after JavaScript this feels ingeniously

Syntax - isn't it look nice? for i = 1, 10, instead of just copying C style, no redundant parentheses.

Function can return multiple values, like in Rust or Go, without creating temporary array.

Table - Occam's razor in action, just store all in one, why to choose.

I agree that preserving key order is sometimes useful, but it's not for free, and Lua was designed to be fast.

Prototyping with metatables is not only transparent and easy to grasp, but also has more powers than prototypes in JS, it was capable of what Proxy in JS can do long before Proxy appeared in JS.

nil is awesome to! While JS has null, undefined, delete keyword to unset a value, Lua has a single nil for every case. It's awkward only when we want to update database record with NULL, then need to use special NULL value exposed by library, or when encoding JSON, but otherwise having one choice is a win.

Lua allows to change current global environment, to call a function within custom environment, with one simple function call setfenv, is there something alike in any other scripting language?

Lua has special functions to access local variables and "closured" variables (called upvalues) of functions. Mostly useless feature, but unique, isn't it? So it's possible to write debugger of Lua right inside of Lua program!

To conclude, Lua is probably most performant, most lightweight, most simple scripting language, few hours is enough to learn Lua, 1-2 days is enough to learn it absolutely in every aspect. Language with nice syntax and more powerful than JS at least was 5 years ago.

Collapse
 
taw profile image
Tomasz Wegrzanowski

You're definitely wrong about one of these things, as official Lua is actually very slow compared with similar languages.
LuaJIT is quite fast, but it's many versions behind official Lua, so you get ecosystem fragmentation, with some people using official version and some people using LuaJIT version, and code not being compatible.

And even if it was fast, I don't think it's worth spending time on language that won't even print(a_table) or == two tables. Programmer time is extremely valuable.

Lua isn't dead yet, and it could fix a lot of such issues and call it Lua 6 or something, but nothing suggests that it has any interest in doing so.

Collapse
 
taw profile image
Tomasz Wegrzanowski

By the way lack of Unicode support issue is a massive pain point for game developers, who are Lua's main audience. Pretty much all games are released in multiple languages, so they must support Unicode, so Lua not supporting it is a huge problem. See this for example.

Thread Thread
 
romeerez profile image
Roman K • Edited

It's a random article where person complains on syntax, they don't say anything about huge problem of installing lua ut8 which is not included to Lua itself, and in the end they say c# shines.

How it's even possible to complain on -- comments? It looks cool.

Collapse
 
romeerez profile image
Roman K • Edited

What I'm to say, Lua has strong sides, has it's niche, it is easy to embed, fast, lightweight, simple. None of these you mentioned in the post except for easy to embed. It must be very fun to explore new languages, but what's the point if you aren't trying to see where they shine at?

openresty.com/en/
I'm not interesting at games at all, I'm very interesting in web platform, and OpenResty is what I've been playing with (years ago). Last release was couple of week ago - it's still alive!

This is Nginx + LuaJIT and it powers high-loaded web sites, here are some stats: https://www.wappalyzer.com/technologies/web-servers/openresty?utm_source=popup&utm_medium=extension&utm_campaign=wappalyzer

Comparing objects deeply violates "fast", programmer will try to find a better way to track changes, including unicode is essential this days, but still, not including it makes Lua bit more lightweigth. Lua doesn't act like Ruby because it's not a goal, it makes no sense to follow Ruby decisions in the area where Lua shines.

Collapse
 
pandademic profile image
Pandademic

It's worth noting that neovim can be configured in lua , and uses it somewhat as a replacement for vimscript.

Collapse
 
taw profile image
Tomasz Wegrzanowski

Yeah, nowadays most programs use an existing language for scripting instead of creating their own.

Collapse
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️

Imagine using # on a unicode string instead of utf8.len and then complaining that it doesn't work...

Collapse
 
taw profile image
Tomasz Wegrzanowski

Because it works in all the good languages? Anyway, most lua platforms (LuaJIT, any Lua 5.2 and earlier - most games just have whichever Lua was there at release and you cant' upgrade it) don't even have utf8.len, and Lua 5.3 which got utf8.len still can't do anything else like uppercasing unicode.

Collapse
 
darkwiiplayer profile image
𒎏Wii 🏳️‍⚧️

Because it works in all the good languages

No clue what you mean with that

most lua platforms don't even have utf8.len

Then they probably don't need it. There's no point in a game having unicode support when the engine is from two centuries ago and doesn't support more than ascii anyway.

Lua 5.3 [...] still can't do anything else like uppercasing unicode.

Yes, that's a good thing.

Collapse
 
matheusrich profile image
Matheus Richard

Worth noting that Elixir is also a programming language created by a Brazilian.