Learn Lua from JavaScript, part 2: Control flow and data structures

The guts of Lua: functions, loops, and tables.

By Tyler Neylon
June 15, 2016
Apollo 11 bootprint Apollo 11 bootprint (source: Wikipedia)

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 objects and arrays.

Functions

Here’s a pair of example functions in Lua:

Learn faster. Dig deeper. See farther.

Join the O'Reilly online learning platform. Get a free trial today and find answers on the fly, or master something new and useful.

Learn more
-- 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.

Post topics: Software Engineering
Share: