Learn Lua from JavaScript, part 1: An introduction to Lua

If you know JavaScript, learning Lua is a snap, as Tyler Neylon explains in this first in a series of three posts.

By Tyler Neylon
June 9, 2016
Moon craters Moon craters (source: Wikimedia Commons)

Lua is an elegant, portable, fast, and embarrassingly flexible language. It can run on any system that can compile C, which is probably why cross-platform frameworks like Corona SDK and the Löve game engine were built with Lua. It’s fast enough to be used for games—in fact, the original Angry Birds was built using Lua. And it integrates well with other languages, making it an excellent choice as the scripting language of Adobe Lightroom. Personally, I love Lua because it’s beautifully designed and a pleasure to work with.

If you already know JavaScript, I have great news: Lua will be ridiculously easy for you to learn. Lua and JavaScript have a lot in common. We’ll take full advantage of those commonalities to make the leap to Lua, while pointing out a few key differences along the way.

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

This is the first in a series of three posts that teach Lua based on knowledge of JavaScript. This post covers the basics: installation, variables, data types, operators, and expressions. The second post will cover control flow and data structures, while the third covers object-oriented behavior.

Most of the JavaScript used in this post follows the ECMAScript 5 standard from 2009, known as the ES5 standard. The newest JavaScript standard is known as both ES6 and as ES2015. I’ll occasionally mention features from ES6, but I’ll be careful to mark them as ES6 features so you’ll know which JavaScript snippets come from which version of the language. I won’t assume you know ES6 and will explain any non-obvious features I use from that standard.

Running Lua

Here’s the official Lua installation page.

If you’re using homebrew on a Mac, you can run brew install lua. On ubuntu, you can run sudo apt-get install lua5.2, although newer Lua versions are available if you’re willing to build from source. For Windows, install using LuaDist.

Building from source on Mac OS X or Linux is easy. The appropriate shell commands are below. Replace linux by macosx on the last line if you’re running this on Mac OS X:

curl -R -O http://www.lua.org/ftp/lua-5.3.3.tar.gz
tar zxf lua-5.3.3.tar.gz
cd lua-5.3.3
make linux test  # Change linux -> macosx if you're on a Mac.

Once the installation is complete, the lua command should be in your path. Run it to see a prompt like this:

$ lua
Lua 5.3.3  Copyright (C) 1994-2016 Lua.org, PUC-Rio
> █

Similar to node.js, you can use your favorite text editor to write a Lua script such as my_file.lua, and then execute it by running this command in your shell:

$ lua my_file.lua

Comments, whitespace, and semicolons

Multiline comments in Lua begin with the token --[[ and end with the token ]]. If the token -- is not followed by [[, then the rest of the line is a one-line comment. For example:

print('Why, hello!')  -- The `print` function prints a string.
--[[ And this is a
     multiline comment! ]]

Indentation is ignored by Lua. Whitespace characters are also ignored, excepting whitespace in strings and the newlines used to end one-line comments. End-of-line semicolons in Lua are optional and are typically excluded.

Variable types and scope

Like JavaScript, Lua variables are dynamically typed, and memory is automatically managed via garbage collection. Most of Lua’s types map nicely to a corresponding JavaScript type.

Types

JavaScript type Lua type Example Lua values
boolean boolean true or false
null or undefined nil nil
number number 3.141
string string ‘hi’ or “there”
object or array table {a = 1, [2] = false}
function function function () return 42 end
symbol (ES6) unique tables

It’s handy to classify boolean values in terms of falsiness; a value is called falsy when it evaluates to false in a boolean context. The only falsy values in Lua are nil and false. Compare this to JavaScript, where 0, the empty string '', and undefined are also falsy.

Lua numbers, historically, were typically stored as floating-point values—just like JavaScript numbers. Starting with Lua 5.3, Lua added the ability to work with numbers that are internally stored as integers, increasing the range of integral values that can be represented exactly.

Intuitively, a Lua number is stored as an integer when it begins life as an integer—for example, via an integer literal—and continues to be stored this way until an operation is performed on it that may result in a non-integer, such as division. The code examples below illustrate how Lua deals with several arithmetic expressions—some resulting in integers, and others in floating-point numbers.

-- Lua
n = 100
print(n)      --> 100;   internally stored as an integer.
print(n * 2)  --> 200;   this expression is stored as an integer.
print(n / 2)  -->  50.0; this expression is stored in floating-point.

Similar to JavaScript objects, Lua’s table type is a catch-all data structure. A table can work as either a hash table or an array. JavaScript object keys must be strings; Lua table keys can be any non-nil value. Lua tables are considered equal only if they are the same object, as opposed to having the same contents:

-- Lua
myTable = {}
print(myTable == myTable)  --> true
print(myTable == {})       --> false

Lua functions are first-class objects—they can be created anonymously, assigned to variables, and passed to or returned from functions. They implicitly become closures when they refer to any independent variable defined outside the scope of the function itself. Lua functions also perform efficient tail calls, meaning that the call stack doesn’t grow when a function ends by calling another function.

The two remaining types in Lua are userdata and thread. A userdata, intuitively, is an object that’s been implemented in C using Lua’s C API. A userdata typically acts like a table with private data, although its behavior can be customized to appear non-private. A Lua thread is a coroutine, allowing a function to yield values while preserving its own stack and internal state.

Scope and mutability

Lua variables are global by default. Lua’s local keyword is a bit like JavaScript’s var keyword, except that Lua scopes aren’t hoisted. In other words, you can think of Lua’s local keyword as similar to ES6’s let keyword, which makes a variable visible within the current code block:

-- Lua
phi         = 1.618034  -- `phi` has global scope.
local gamma = 0.577216  -- `gamma` has local scope in the current block.

Lua doesn’t offer explicit protection for constant values or hidden values. However, just as in JavaScript, you can create a new function at runtime which refers to variables declared outside the function, thus creating a closure. The variables referred to by a closure can effectively be private when they’re visible to the function but not to any other code. Lua functions will be covered in the next post.

Operators and expressions

Arithmetic operators like addition and multiplication are essentially the same in Lua and JavaScript. Both languages have a remainder operator, %, although JavaScript’s % will return negative values when the left operand is negative, while Lua’s % always returns nonnegative values. Lua can find numeric exponents using the ^ operator:

-- Lua
print(2 ^ 10)  --> 1024.0
print(-2 % 7)  --> 5

JavaScript doesn’t support operator overloading, but Lua does. Lua does this with special functions called metamethods, which will be covered in a later post. JavaScript has a ternary operator, while Lua doesn’t; but you can achieve a similar result in Lua with the idiom below, which relies on the fact that Lua’s short-circuited or and and operators return their last-evaluated value:

-- Lua

-- Lua’s ternary-operator-like idiom.
local x = myBoolean and valueOnTrue or valueOnFalse

-- An example: find the max of the numbers a and b.
local maxNum = (a > b) and a or b

-- This is similar to the Javascript:
-- var maxNum = (a > b) ? a : b;

That idiom works in all cases except when valueOnTrue is falsy. This is often not an issue as Lua numbers, strings, and tables are never considered falsy.

Comparison

A typical best practice in JavaScript is to favor the === operator over the == operator since JavaScript invokes a confusing set of implicit coercions in the case of ==. For ===, JavaScript only returns true when both values have the same type.

Lua’s only equality operator, ==, shares this type requirement with JavaScript’s === operator. The example below features Lua’s built-in tonumber function, which can parse a string representing a number:

-- Lua
print(6.0 * 7.0 == '42')            --> false, different types
print(6.0 * 7.0 == tonumber('42'))  --> true, both are numbers

Lua’s < and > operators return false when the operands have different types. They sort strings alphabetically in a locale-sensitive manner, and sort numbers in their typical numerical order.

Bitwise operators

Lua 5.3 introduced built-in bitwise operators, listed below. The operators in this table are present in both Lua and JavaScript.

Operator Meaning
& bitwise AND
| bitwise OR
~ bitwise NOT, unary
<< bitwise left-shift

Lua’s ~ operator, in a binary context, is an exclusive or just like JavaScript’s ^ operator. Here are some examples:

-- Lua
print(6 & 18) -->  2;    00110b AND 10010b = 00010b.
print(6 | 18) --> 22;    00110b  OR 10010b = 10110b.
print(6 ~ 18) --> 20;    00110b XOR 10010b = 10100b.

JavaScript distinguishes between the >> and >>> right-shift operators, with >> preserving sign and >>> always filling in zero bits. Lua’s >> operator acts like JavaScript’s >>> operator—that is, it fills in new bits with zeros.

That completes the basics of running Lua and understanding its data types and expressions. Next week’s post will cover a huge swath of the language, including Lua’s control flow keywords, functions, and the essentials of Lua tables.

Post topics: Software Engineering
Share: