Lua
Lua is a beautiful and simple scripting language that is safe, easy, and fun for the whole family: easy to learn with simple syntax and no unnecessary rubbish.
It is lightweight and portable: as the fastest interpreted language it is also runnable on all OSes (computer and phone) and embeddable in many applications.
It is a multi-paradigm language, featuring support for functional and object-oriented (prototype-based) programming.
Despite differences in syntax, Lua and
JavaScript are remarkably similar.
Functional Programming
Functions are first-class citizens in Lua.
Like most functional languages, functions are values that can be passed between function calls, assigned to variables, and called recursively.
In Lua, functions can be passed anonymously, called recursively, created in closures, and used as keys for tables.
These are not unique to Lua, but the freedom to the usage of functions in Lua makes them pretty powerful and pretty darn cool.
Object Oriented (OO) Programming
Lua has basic support for OO-like programming, but not like common OO languages like Java and Ruby.
In Java, the class is a model of all the attributes its instances will have, and all instances of a class will have exactly those specified attributes.
Lua, however, relies on the Prototype-Based model, like JavaScript.
In Lua, all complex data types are tables (essentially a Map in Java, a Hash in Ruby, a Dictionary in Python and other languages), which are key-value pairs.
In prototype-based design, there are no classes per se.
Every object is its own bundle of data and can have whatever attributes it wants, though you can emulate the concept of a class by defining default values for various attributes.
This would mean that for every time you wanted to "extend" a class (which would just be another object), you would have to clone the entire object, so every attribute name and value would be put into your new object.
Thankfully, Lua has a built-in system to manage the inheritance-chain.
What Lua uses is called a metatable.
A metatable affects the behavior of the table itself rather than the behavior inside the table.
In Java, classes define the behavior inside an object: how the object manipulates and uses the data inside of it.
In Lua, tables can still do that, but the metatables affect how Lua manipulates and uses certain tables.
Using the _index
metamethod in a table's metatable, you can point Lua to a different table if the first table doesn't have a value Lua's looking for.
For example, take the following code:
Vehicle = { speed = 50, vroom = "vroom" }
Motorcycle = { vroom = "VROOM!" }
Car = { speed = 80 }
If you tried to get the values of Car.vroom
and Motorcycle.speed
you'd get nil
both times.
That's because each of the three tables are completely separate and different tables.
Lua doesn't know they're related to each other yet.
To do that, you'd need to do the following:
metavehicle = { __index = Vehicle }
setmetatable( Motorcycle, metavehicle )
In the first line, we're creating a regular table with one field, __index = Vehicle
(Vehicle
is referring to the Vehicle
table from the previous set of code).
In the second line, we set the metatable for Motorcycle
to metavehicle
.
Now if you were to do Motorcycle.speed
it would return 50
.
Because we did this for only Motorcycle
and not for Car
, doing Car.vroom
will still return nil
.
To add Car
to the family, simply run setmetatable( Car, metavehicle )
.
What's happening behind the scenes is rather interesting.
Before setting the metatable, when we tried to call
Motorcycle.speed
this happened:
- Lua looked in
Motorcycle
for speed
and couldn't find it
- Lua gave up and returned
nil
After setting the metatable for
Motorcycle
, if we tried to call
Motorcycle.speed
, the following happens:
- Lua looked in
Motorcycle
for speed
and couldn't find it
- Lua saw that
Motorcycle
had a metatable
- Lua looked in the metatable for
__index
and saw it was a table (Vehicle
)
- Lua looked in
Vehicle
for speed
and found it
- Lua got the value of
Vehicle.speed
and returned 50
Just by specifying an
__index
you can define inheritance.
It gets better.
A table with a metatable that has __index
can be pointed to by another metatable with its own __index
, so you can chain inheritances as you go further down.
Suppose we added a Ducati
as a child of Motorcycle
, we just do:
metamotorcycle = { __index = Motorcycle }
Ducati = {}
setmetatable( Ducati, metamotorcycle )
You can also have a table be its own metatable, with its __index
metamethod pointing to its parent class.
You can also have a table be a metatable for other tables and have its __index
metamethod point to itself.
-- Motorcycle as its own metatable
Motorcycle.__index = Vehicle
setmetatable( Motorcycle, Motorcycle )
-- Vehicle's __index pointing to itself
Vehicle.__index = Vehicle
setmetatable( Motorcycle, Vehicle )
There are a
ton of other metamethods that are too numerous to elaborate on here.
Metatables make Lua about as close to OO without being OO.
An Annoying Gotcha
Lua is almost the perfect language, but there is one thing that bugs me about it: 0
is not falsy (that is, it does not fail a conditional).
It would be convenient if 0
failed.
Adding that feature would make checks for empty strings and empty tables a tad more eloquent.
For example, it would be nicer for if #arg then parse() else error() end
to error if nothing was passed to the script in the command line.
You could also do if not #(io.read()) ...
to execute something if nothing was read in.
This decision is most likely because of Lua's background.
Though Lua was built on ANSI C, it had influences from Lisp (specifically Scheme, one of two main Lisp dialects/families).
Historically, in Lisp values were either
NIL
or not.
Thus, in Lua, values were either
nil
or not: the only thing that would evaluate to false was
nil
.
Then with
Lua 5.0 in 2003, booleans were added so that
false
wasn't exactly
nil
, and
true
wasn't exactly a meaningful data value, but both would evaluate to their approximates in conditionals.
In the addition of
false
it makes sense that
0
was just simply overlooked.
One argument I read against this was that it would break the canonical Lua ternary conditional operator when using a zero in it.
In this contrived scenario, suppose your code was x = math.random() < 0.5 and 0 or 1
.
That line would assign x
randomly to either 0
or 1
with equal weight; however, if 0
was falsy, the and
will always fail and x
will always be 1
regardless of the random number generator output.
That problem, though, is easily fixed by switching the outputs: x = math.random() < 0.5 and 1 or 0
.
This code would work perfectly fine because when Lua short-circuits or
operators, if all values are falsy it returns the last value; thus, if math.random() < 0.5
failed, it will fail again at 0
but return 0
anyway.
That problem is easily fixable in small projects, but not as easily for large libraries: especially ones that are years old and no longer regularly maintained.
To avoid breaking everything and pissing off much of the Lua community, the Lua devs are keeping 0
truthy.