Learn Lua from JavaScript, part 2: Control flow and data structures
The guts of Lua: functions, loops, and tables.
This post covers a huge portion of Lua’s functionality. I’ll start with functions, move on to loop constructs and other control flow elements, and finish up by explaining how Lua’s only container type—the table
—can be a stand-in for both JavaScript object
s and array
s.
Functions
Here’s a pair of example functions in Lua:
-- Lua
function reduce(a, b)
return b, a % b
end
function gcd(a, b) -- Find the greatest common divisor of a and b.
while b > 0 do
a, b = reduce(a, b)
end
return a
end
print(gcd(2 * 3 * 5, 2 * 5 * 7)) --> 10
Let’s start with the reduce
function. The syntax for a Lua function is similar to that of a JavaScript function, except that the opening brace {
is dropped, and the closing brace }
is replaced with the end
keyword.
If the statement “return b, a % b
” were JavaScript, it would evaluate both expressions b
and a % b
, and then return the single value a % b
. In Lua, however, there is no comma operator—but return statements on the right side of assignments can work with multiple comma-separated values. In this case, Lua returns both values, so the statement
a, b = reduce(a, b)
is effectively the same as this line:
a, b = b, a % b
which is equivalent to these three lines:
tmp = b
b = a % b
a = tmp
Note that the reduce
function can be replaced by a single line of code; I wrote it the longer way above to provide an example of multiple return values being assigned to multiple variables.
Control flow
Here’s the mapping between JavaScript control flow and Lua control flow:
JavaScript | Lua |
---|---|
while (condition) { … } | while condition do … end |
do { … } while (condition) | repeat … until condition |
for (var i =start; i <=end; i++) { … } | for i =start, end do … end |
for (key in object) { … } | for key, value = pairs(object) do … end |
for (value of object) { … } (ES6) | for key, value = pairs(object) do … end |
if (condition) {…} [else {…}] | if condition1 do … [elseif conditition2 then …] [else …] end |
Lua doesn’t have a completely general for
statement like JavaScript; if you need a general for
loop that doesn’t fit one of Lua’s short forms listed in the table above, you can use this code pattern:
local i = startValue() -- Initialize.
while myCondition(i) do -- Check a loop condition.
doLoopBody()
i = step(i) -- Update any loop variables.
end
-- This is similar to JavaScript's:
-- for (var i = startValue; myCondition(i); i = step(i)) {
-- doLoopBody();
-- }
Flexible number of values
Lua can handle the simultaneous assignment of multiple values. For example:
local a, b, c = 1, 2, 3
-- Now a = 1, b = 2, and c = 3.
In an assignment involving multiple right-hand expressions, all of the right-hand expressions are evaluated and temporarily stored before any of the left-hand variables change value. This order of operations is useful in some cases. For example, here’s a one-line swap of two values:
a, b = b, a -- This swaps the two values, unlike the lines below.
a = b -- These two lines end up losing the original value of a.
b = a
If there are more values on the right side of an assignment than on the left, the right-most values are discarded:
local a, b = 100, 200, 300, 400
-- Now a = 100 and b = 200.
If there are more variables on the left side, then the extra right-most variables receive the value nil:
local a, b, c, d = 'Taco', 'Tuesday'
-- Now a = 'Taco', b = 'Tuesday', c = nil, and d = nil.
Return values from functions work similarly. In the code below, I’m creating anonymous functions and then immediately calling them by appending the extra set of parentheses at the end of the line:
local a, b = (function () return 1 end)()
-- Now a = 1, b = nil.
local a, b = (function () return 1, 2, 3 end)()
-- Now a = 1 and b = 2.
A function’s parameters are also flexible in that a function may be called with arbitrarily many arguments. Overflow arguments are discarded, while unspecified parameters get the default value nil:
function f(a, b)
print(a)
print(b)
end
f(1) --> 1, nil
f(1, 2) --> 1, 2
f(1, 2, 3) --> 1, 2
Tables
A JavaScript array
is a useful container type, and a JavaScript object
works both as a container and as the basis for class-oriented interfaces. Lua’s table
type covers all of these use cases.
A JavaScript object
and a Lua table
both act like hash tables with fast operations to look up, add, or delete elements. While keys in JavaScript must be strings, Lua keys can be any non-nil
type. In particular, integer keys in Lua are distinct from string keys.
The following snippets act differently because JavaScript converts all keys into strings, whereas Lua doesn’t:
// JavaScript
a = {}
a[1] = 'int key'
a['1'] = 'str key'
console.log(a[1]) // Prints 'str key'.
-- Lua
a = {}
a[1] = 'int key'
a['1'] = 'str key'
print(a[1]) -- Prints 'int key'.
Here’s an example of a Lua table literal:
-- Lua
table1 = {aKey = 'aValue'}
table2 = {key1 = 'value1', ['key2'] = 'value2',
[false] = 0, [table1] = table1}
Lua table literals are similar to JavaScript table literals, with the most obvious difference being the use of an =
character where JavaScript uses a colon :
. If a key is an identifier—that is, if the key matches the regular expression “[a-zA-Z_][a-zA-Z0-9_]*
”—then it will work as an undecorated key, as in {key = 'value'}
. All other keys can be provided as a non-nil
Lua expression enclosed in square braces, as in {[1] = 2, ['3'] = 4}
, where the first key is an integer and the second is a string.
If you use a missing key on a Lua table, it’s not an error—instead the value is considered nil
. This is analogous to JavaScript returning undefined
when you use a missing key on an object
.
Lua tables and JavaScript symbols
ES6 introduced a new type called a symbol
which can be used as the key of an object and is guaranteed to be unique. This solves the problem of name collisions among an object’s string keys. This code illustrates a potential problem case:
// JavaScript
var anObject = {};
// JS file 1 stores a value in anObject.
anObject.hopefully_unique = 100;
// JS file 2 stores its own value in anObject.
anObject.hopefully_unique = 200;
// JS file 1 tries to load its stored value.
console.log(anObject.hopefully_unique); // Oh no! This prints the wrong value.
The symbol
type addresses this by providing values that are guaranteed to be unique each time you create a new instance, as seen here:
// ES6
var anObject = {};
// In JS file 1.
var symbol1 = Symbol();
anObject[symbol1] = 100;
// In JS file 2.
var symbol2 = Symbol();
anObject[symbol2] = 200;
// JS file 1 tries to load its stored value.
console.log(anObject[symbol1]); // Happiness: this prints the expected value.
Lua provides similar functionality since tables may be used as keys, and every table is considered unique. This code is analogous to the previous symbol
example:
-- Lua
local aTable = {}
local key1 = {}
aTable[key1] = 100
local key2 = {}
aTable[key2] = 200
print(aTable[key1]) -- key1 is guaranteed to not clash with key2.
Arrays
The equivalent of a JavaScript array
is a Lua table whose keys are contiguous integers starting at 1. Some coders balk at 1-indexed arrays, but in my opinion this is more of an unusual feature than a source of trouble. Lua is internally consistent in using indices that begin at 1: characters within strings are also 1-indexed, and Lua’s internal C API uses a stack that begins with index 1. This consistency makes the shift from 0-based indexes over to 1-based indexes relatively painless.
This example illustrates some common array
-like Lua operations with their JavaScript equivalents in comments:
-- Lua
-- Array initialization, access, and length.
luaArray = {'human', 'tree'} -- JS: jsArray = ['human', 'tree']
a = luaArray[1] -- JS: a = jsArray[0]
n = #luaArray -- JS: n = jsArray.length
-- Removing and inserting at the front.
first = table.remove(luaArray, 1) -- JS: first = jsArray.shift()
table.insert(luaArray, 1, first) -- JS: jsArray.unshift(first)
-- Removing and inserting at the back.
table.insert(luaArray, 'raccoon') -- JS: jsArray.push('raccoon')
last = table.remove(luaArray, #luaArray) -- JS: last = jsArray.pop()
-- Iterate in order.
-- This loop style was added in ES6.
for index, value = ipairs(luaArray) do -- JS: for (var value of jsArray) {
-- Loop body. -- JS: // Loop body.
end -- JS: }
JavaScript’s for .. of
loops didn’t exist before ES6, so you may be more familiar with this ES5 loop style that performs the same function:
// JavaScript
for (var i = 0; i < jsArray.length; i++) {
var value = jsArray[i];
// Loop body.
}
This post has covered the basics of Lua functions, control flow, and data structures. Next week, the final post of this series unveils some of my favorite Lua mechanics that fit together elegantly to enable transparent and straightforward object-oriented code patterns.