Get started with Lua

The Lua language

Bounces uses Lua for live-coding iOS, tvOS, and MacOS applications. And if you are an  app developer, you are probably much more familiar with Swift or Objective-C than with the Lua language. If this is the case, no worry, this article has been especially written for you. It will give you an overview of the Lua language and highlight the features that make Lua a great language, although quite different from the compiled languages generally used on our platforms.

The goal is that, after reading it, you feel comfortable enough with Lua to start coding right away.

There are several ways to use the materials in this article: first, you can probably get a quick overview of Lua by reading it sequentially, even if some details in the provided code examples remain unclear at first. Then you can use it as a quick Lua reference, and use the table of content (on the left) to jump directly to a specific topic, on which you need details or information. Finally, as all provided code examples are valid Lua code, you can copy any of them in Bounces, execute it in step-by-step to get a full understanding of what it does, and modify it to try additional stuff.

The second part of this article presents the main Bounces-specific additions to Lua, that permit live-coding in your app, and enable the interaction between your Lua code and the underlying native SDK. If you are already familiar with the Lua language, you can jump directly to this second part.

An Introduction to Lua

This goal of this section is to give you a first overview of the Lua language, without entering too much into the details, so you should be capable, after reading this, to understand any reasonably simple Lua code and to write your own Lua code. Therefore, for the sake of simplicity, some advanced features of the language will be intentionally omitted here.

If you already have downloaded Bounces on your computer, you can try the code samples given here in a local Lua document.

A first taste of Lua

Let's get started with this simple Lua code sample:

-- This is a Lua comment
local n = 8
local t = {} -- t is a Lua table

-- Define a function
function cube (x)
    return x^3
end

-- Fill t with the n first cubes
for i = 1, n do
    t[i] = cube(i)
end

-- Return the cubes table
return t

This code sequence:

  • starts with a comment line,
  • declares two local variables n and t, and initialize them with some values: nwith the number 8, t with an empty Lua table (i.e. an associative array).
  • Then it declares a function cube that returns the cube of its parameter x,
  • it runs a for loop that fills table t with the cubes of the n first integers,
  • and finally it returns the table t to an eventual caller.

Nothing too complex so far, huh?

Notice that Lua doesn't require semicolons (or any other separator) at the end of statements. Semicolons are however accepted as a way to indicate a separation between statements, but they are rarely needed. In addition, Lua considers newlines and whitespace characters as equivalent, so you can layout your code as you want.

Data Types

Lua has a small set of data types:

  • Number: a numeric value like 42 or3.14159. In Lua 5.2, the version currently used in Bounces, all numbers are 64-bits floating point numbers. But don't freak out, 64-bit doubles have 52 bits for storing exact int values, so this is generally not an issue.
  • Boolean: true or false.
  • Nil: nil has the usual meaning of representing the absence of value.
  • String: Strings in Lua are immutable. Simple literal strings can be put into single or double quotes, so 'abc' and "abc" are equivalent. For defining multiline strings, you enclose them between long brackets, typically [[ and ]].

    s = "A simple string"
    
    s2 = [[
    A long text
    that extends
    on several lines]]
    
    s3 = [=[A multi-line string can also be enclosed
    between long brackets with additional '=' characters
    if it conttains a ]] pattern!]=]
    
  • Table: it is the data structure in Lua. As already mentioned, a Lua table is basically an associative array. We will discuss tables in more detail later in this document.
  • Function: functions are first-class values in Lua and they will also have their own dedicated paragraph later.
  • Thread and Userdata are advanced types, so we won't discuss them here.

Comments

In Lua, there are two types of comments:

  • line comments start with -- and extend up to the end of the current line;
  • block comments have a structure similar to multi-line strings: they are enclosed between patterns like --[[ and ]], or --[===[and ]===].

Having variable opening and closing patterns for block comments has the great advantage that block comments can easily be nested, provided inner block comments have non-conflicting closing patterns, as shown in the example below:

-- Below is a big commented block
--[=[
function cube (x)
    -- This two-lines comment block does not conflict with the enclosing comment
    --[[ local x3 = x*x*x
    return x3]]
    return x^3
end]=]

Note: Bounces takes care of nesting comments for you, so that commenting / uncommenting a block of code just behave like you expect.

Variables

Variable in Lua have the usual meaning: a place where you store values (or more priecisely references to values). Lua variables are not typed, i.e. a variable can store a value of any type. Variable names can be any combination of letters, digits and underscore (not starting with a digit).

Lua has two kinds of variables: global variables and local variables.

Local variables

Local variables are declared with the keyword local.

local myVariableWithALongName -- this variable is initialized to nil
local a, b, c = 1, 'this', 'that' -- You can declare and initialize several variables on a single line
local _ -- also a valid variable name

Local variables have a precise scope: a local variable is visible from its definition up to the end of the current code block:

local x = 1
local someCondition = true

if someCondition then
  local y = x -- y is local to the 'if' block, x is visible inside enclosed blocks
  y = y + 2
  x = math.sqrt(y) -- square root from the math standard library
end

-- y doesn't exist here anymore
-- but x does exist because we are still in its declaration block
print (x) --> 1.7320508075689 (√3)

Lua is flexible regarding local variable names: it is okay in Lua to declare a local variable with the same name as a previously declared variable in the same block or in an enclosing block. In this case, the new variable simply masks the previously defined one:

local x = 1

do
  local x = 3.5
  -- from here to the end of this do..end block, x is the new declared variable 
  x = x - 1 -- x value is now 2.5
end

print(x) --> 1 (we are back to the local x declared on line 1)

local function getFirstX () -- Yes, getFirstX is a local variable containing a function!
  return x -- x here is a reference to the local variable x in the enclosing block
end

local function incrementX (incr) -- function parameters are local variables as well, visible inside the function block
  x = x + incr -- the value of the local variable x in the enclosing block is changed
end

local x = 'hello' -- declares a new local variable name 'x' that masks the previous x, but does not remove it
print(x)   --> hello

print(getFirstX()) --> 1 (the value of the variable x declared on line 1)
incrementX(2)
print(getFirstX()) --> 3 (variable x declared on line 1 has be incremented)

print(x)   --> hello (the current variable x is still the one containing 'hello')

In the daily programming, variable masking is very handy for cutting and pasting blocks of code containing local variable definitions, but be aware that a local variable declaration can have unexpected effects on variables with the same name located after the declaration, in the current code block. :)

Bounces include special features for finely controlling the scope of Lua variables: for example, you can double-click on a variable name to highlight all uses of this variable in the code, in a perfectly Lua-syntax-compliant way.

Global variables

Global variables, also called free names, are not expressly declared in Lua. Actually Lua considers as a global variable any variable identifier that does not correspond to a local variable defined in the current block or in one of its enclosing block.

Global variables have a global scope: once set, a global variable is accessible from anywhere in your program. A never-set global variable has the value nil, and you can remove a global variable by setting it to nil again.

i = 42 -- assigns 42 o the global variable i
-- now i is available from everywhere in your program, except where a local variable with the same name is defined

print(i) --> 42
print(j) --> nil (j is a not-yet-set global variable)

Note: actually the set of global variables visible at any given point of a Lua program can be finely control via the use of Lua _ENV (environment) tables, but to keep things simple in this quick overview, we will consider for now that global variables are really … global.

Good practice

So which ones shall you use in your program? Local variables or global variables?

Like in most other programming languages, the good practice in Lua is to use local variables as much as possible, and to reserve global variables for standard libraries, except in rare cases where your program really needs to store some of its state at a global level.

In addition, local variables are much faster than global variables, so using local variables is also good for your program's performance.

Warning: because they are implicitly declared, it is a rather common mistake when writing Lua code, to accidentally define a global variable by mistyping a variable name. To help you detect easily this kind of error, Bounces displays local and global variables with different colors in the source code.

Operators and Expressions

Lua supports the classic set of arithmetic operators + - * / %, plus a less common exponentiation operator ^:

local a = 3.5
local b = 2 * a - 0.8
local c = a % 2 -- % is the modulo operator
local d = (a * 2) ^ 0.5 -- exponentiation works for any exponent: this calculates the square root of (a * 2)

print(a, b, c, d) --> 3.5    6.2    1.5    0.37796447300923

Comparison operators are the classic ==, >, <, >=, <=, and the less-classic non-equal operator ~= (tilde-equal). Their result is always a boolean value trueor false.

Logical operators in Lua are and, or, and not. Logical operators, like condition evaluation in control blocks, consider both false and nil as false, and anything else as true.
Note that this means in particular that 0 is considered as true by logical operators and blocks conditions, which you may find surprising at first, if you are used to languages of the C family!

and and or are shortcut operators: they return their first operand if false-or-nil (and) or non-false-nor-nil (or), without evaluating their second operand; otherwise, they evaluate and return their second operand.

local cond1 = not true --> false
cond1 = not 0          --> false (zero is considered as true)
cond1 = not nil        --> true  (nil is considered as false)

local myVar = cond1 and 'hello' or 'world' --> hello
cond1 = not cond1 --> false
myVar = cond1 and 'hello' or 'world' --> world

function f() return 123 end
myVar = cond1 and f() or 'world' --> world  (function f is not called here)

Other operators include the string concatenation operator .. and the length operator #.

local s = "he" .. "llo" --> hello
print (#s) --> 5

Assignment

Lua has a very good support for multiple assignment.

local a, b, c = 9, 16, "twenty-five" -- a --> 9, b --> 16, c --> "twenty-five"
-- an easy way to exchange a and b
a, b = b, a   -- a --> 16, b --> 9

-- no side effect, as all right-hand expressions are evaluated before doing the assignment
a, b = a + b, a - b  -- a --> 25, b --> 7

-- both sides of the assignment can have different nimber of expressions
a, b, c = a + b, a   -- a --> 32, b --> 25, c --> nil

-- assignment simply works with functions having multiple results
local function firstLetters() return 'a', 'b', 'c', 'd', 'e', 'f' end
a, b, c = firstLetters()  -- a --> 'a', b --> 'b', c --> 'c', other returned values are discarded

Lua doesn't have compound assignment operators (like +=), as those generally don't fit well with multiple assignment. This is not really annoying, especially if your code editor has a good code-completion feature.

Conditional execution

The if statement in Lua has a familiar structure:

if --[[condition1]] then     -- starts with a 'if' block,
    -- statements
elseif --[[condition2]] then -- optionally followed by one or more 'elseif' blocks,
    -- statements
else                         -- finally it can have an optional 'else' block
    -- statements
end                          -- and is always terminated by a 'end' keyword.

Examples:

local a = -1

-- if control block (simple version)
if a > 0 then
    print "a is positive"
end

-- if control block (full version, with one elseif block and an else block)
if a > 0 then
    print "a is positive"
elseif a < 0 then
    print "a is negative"
else
    print "a is null"
end

Note: by design choice, Lua doesn't include an equivalent of the switch statement found in languages of the C-family. Therefore, to write the equivalent of a switch statement in Lua, you generally use an if with an elseif block for each case, and the else block for processing the default case.

Loops

There are four types of loops in Lua: while loops, repeat ... until loops, and two kinds of forloops. Their structure is quite simple, as you can see in this exemple:

local a = -1

-- while statement
while a < 10 do
    a = a + 1
end

-- repeat until statement
repeat
    a = a - 1
    local b = 2 * a
until b == 2   -- You can use in the end condition a local variable declared inside the repeat until block

-- numeric for statement
for i = 1, 10 do
    -- i is a local variable whose scope is the for loop's internal block
    -- and whose value varies from 1 to 10 inclusive, with an (implicit) step equals to 1
    a =  a + i
    print(a)
end

-- generic for statement
for key, value in pairs(math) do
    -- iterates through the 'math' table, using an iterator ('pair' is a standard table iterator)
    if key == 'pi' then
        print ("Found pi!", value)
        break -- exits the for loop
    end
end

A break statement can be used to terminates the execution of a while, repeat, or for loop, skipping to the next statement after the loop.

Tables

Tables are the only data structure type provided natively by the Lua language.

In addition Lua provides an extension mechanism, named userdata, that allows to easily add new data types to the language. This extension mechanism is used extensively by Bounces for providing access to native Platform APIs, in a way fully consistent with the Lua table syntax. For this reason, having a good understanding of the table syntax in Lua is key for all Bounces users.

A Lua table is essentially an associative array, i.e. a collection of (key → value) pairs, in which each key is unique. Keys and values can be of any type, except nil.

Lua table is a very flexible data type. A Lua table can be used as a dictionary, as an array when keys are integers, or as a set (typically by filling the table with a collection of (key → true) pairs).

Like the rest of the Lua language, the syntax for using tables is simple and easy.

Let's start with an example showing array-oriented and set-oriented table use:

-- build a simple sequence
local primeNumbers = { 2, 3, 5, 7, 11, 13 } -- create an array-like table containing the given values

-- access to elements
print (primeNumbers [1])    --> 2    (table indexes start at one)
print (primeNumbers [5])    --> 11
print (primeNumbers [8])    --> nil  (no element at index 8)

-- Change the table
primeNumbers [8] = 19     -- set entry at index (i.e. for key) 8
print (primeNumbers [8])  --> 19
print (primeNumbers [7])  --> nil  (tables used as a sequence can have holes)
primeNumbers [7] = 17     -- fill the hole

-- you can iterate though a sequence using the 'ipairs' iterator: for index, value in ipairs(table) do ... end
-- (indexes are enumerated in order and enumeration stops at the first index without value)
for i, p in ipairs(primeNumbers) do
    print(string.format("prime [%d] is %d", i, p)) --> prime [1] is 2, prime [2] is 3...
end

-- create a set of the first prime numbers

local primesSet = {}  -- initialize primesSet as an empty table

for _, p in ipairs(primeNumbers) do  -- we don't care about the index here, so we name it '_'
    primesSet [p] = true
end

print (primesSet[3]) --> true  (key 3 is in the set)
print (primesSet[8]) --> nil   (key 8 is not in the set)

for i = 1, 20 do
    if primesSet[i] then  -- test if key i is in the set
        print (string.format("%d is a prime number", i)) --> 2 is a prime number...
    end
end

Naturally, tables can also be used to store structured data:

-- create and fill a 'cat' structure
local cat = {}
cat ['legs'] = 4        -- set key 'legs' with value 4
cat.isCarnivore = true  -- shorter notation for string keys, equivalent to cat['isCarnivore'] = true 
cat.family = "Felinae"

-- create a 'duck' structure as a table litteral (using the generic notation)
local duck = { ["legs"] = 2, ["isCarnivore"] = false, ["family"] = "Anatidae" }

-- create a 'ant' structure as a table litteral, using the shorter notation for string keys
local ant = { legs = 6, isCarnivore = false, family = "Formicidae" }

-- store the previous variables in a table
local animals = { ["cat"] = cat, duck = duck }  -- duck = duck?? 'duck' on the left of the equal sign is a string key, the one on the right is the duck variable defined above
animals.ant = ant
animals.lion = { legs = 4, isCarnivore = true, familly = "Felidae" } -- use a table literal as the value

-- access to table fields
print (animals.cat.isCarnivore) --> true
animals.cat.category = "Mammal"  -- sets a new key "category" in animals.cat with value "Mammal"

-- variables hold references to table; an assignment copies the reference, not the table
local cat2 = animals.cat
cat2.name = 'Felix'
print (cat.name)  --> Felix  (cat, cat2, and animals.cat are references to the same table)

-- By default, two tables are equal only if are the same object, not if they have the same contents
print (cat == cat2) --> true
local ant2 = { legs = 6, isCarnivore = false, family = "Formicidae" } -- same contents as variable ant above
print (ant == ant2) --> false

-- table keys are not limited to strings or integers, but can be anything except nil
local population = { [ant] = 'huge', [animals.lion] = 'small' } -- a table whose keys are tables!
population [cat] = 'large'
print (population[animals.ant]) --> huge  (animals.ant is a reference to the same table as the ant variable)

-- you can enumerate a table using the 'pairs' iterator: for key, value in pairs(table) do ... end
for name, animalInfo in pairs(animals) do
    if animalInfo.isCarnivore then
        print(string.format("%ss are carnivore", name))  --> lions are carnivore
                                                         --> cats are carnivore
    end
end

In addition to the basic features presented above, Lua tables are highly customizable, thanks to a mechanism named metatables. But discussing Lua metatables would be out of the scope of this getting started article.

Functions and methods

We have already met simple functions here and there, in our quick tour of Lua, but without really explaining how functions are defined and used in Lua. Now the time has come to explore them in more detail.

Functions are first-class values

Lua functions are first-class values. This mean that you can do with function values everything you do with other types of value: store them in variables or in tables, compare them with other values, use them in expressions, or pass them as function parameters. And in addition, you can of course call them.

What it also means is that Lua functions don't intrinsically have a name: the name used for calling a function is the name of the variable in which it is stored.

-- Define a function with 2 parameters a and b, and store it into a local variable min
local min = function (a, b)
              print "calling function min"
              return (a < b) and a or b
            end

-- This simpler function definition syntax is equivalent to: local max = function (a, b) ... end
local function max (a, b)
    print "calling function max"
    return (a > b) and a or b
end

min (1, 2) --> calling function min
max (2, 3) --> calling function max

-- Functions are values stored in variables, just like any other value
min, max = max, min -- exchange content of variables min and max

min (1, 2) --> calling function max
max (2, 3) --> calling function min

max = "not-a-function"
print (min, max)  --> function: 0x610000050410    not-a-function

Functions are closures

Lua functions are closures: they can capture and modify local variables accessible from the context in which they are defined. In this regard, Lua functions are similar to Objective-C blocks, Swift closures, or C++ lambdas.

In Lua, external local variables used by a function are named upvalues. Lua automatically detects upvalues used in functions and make them available in read and write mode when the code is executed, so you don't have to do anything special when using them.

local peerName = "Alice"

local function sendMessage (message)
    -- from here, we see variable peerName as an upvalue
    print (string.format("Sending message <<%s>> to %s", message, peerName))
end

sendMessage ("Hello") --> Sending message <<Hello>> to Alice

local function interceptMessage (attacker)
    local suffix = "-the-attacker"

    local function replacePeer () -- functions can be declared anywhere in the code (as first-class values)
        peerName = attacker .. suffix  -- Here, peerName, attacker, and suffix are upvalues defined at different levels
    end

    replacePeer()
end

interceptMessage ("Mallory")

sendMessage ("How are you?") --> Sending message <<How are you?>> to Mallory-the-attacker

Note: In Bounces, upvalues are displayed with a specific color, so you can immediately identify them and track their usage.

Functions can return multiple values

local function multi ()
    -- return a list of values (coma-separated)
    return "multiple", 3 + 2, "results", string.format("are %s", "easy")
end

local a, b, c, d = multi ()        -- multiple results can be used in assignments

print ("multi returned:", multi()) -- or as parameters in another function call
                                   --> multi returned:    multiple    5    results    are easy

Functions have flexible parameters

Not surprisingly, a Lua function definition contains a list of parameters, right before the function body. These parameters act as local variables inside the function, and they are initialized with the argument values passed by the caller, or with nil if the caller doesn't provide enough arguments.

local function f (a, b, c, d)
    print ("a:", a, "b:", b, "c:", c, "d:", d)
end

f (1, 2 , 3 , 4)  --> a: 1    b: 2    c: 3    d: 4

-- It calling f with fewer arguments, missing parameters are set to nil
f (1, 2)          --> a: 1    b: 2    c: nil  d: nil
f ()              --> a: nil  b: nil  c: nil  d: nil

-- If calling f with too many arguments, excess arguments are simply ignored
f (1, 2 , 3 , 4, 5, 6, 7, 8)  --> a: 1    b: 2    c: 3    d: 4

-- When calling a function with a single string literal or table literal argument, you can omit the parentheses
f "aaaa"         --> a: aaaa  b: nil  c: nil  d: nil
f { 1, 2 }       --> a: table: 0x618000068800  b: nil  c: nil  d: nil

-- Parameters flexibility is also convenient when passing a function as a parameter to an other function

local function doSomethingAndCall (callback)
    local result, extraInfo, detailedInfo
    -- Do something and set result, extraInfo, and detailedInfo
    -- ...
    result, extraInfo, detailedInfo = "ok", { extraInfo1 = "abcd" }, { detailedSomething = "efgh" }

    -- Call the callback function with 3 parameters
    callback (result, extraInfo, detailedInfo)
end

-- if we are only interested in the result, we can provide a callback function with a single parameter
doSomethingAndCall (function (result) print (result) end) --> ok

Lua function can have vararg parameters, and use them in a simple and elegant way.

-- To add vararg to a function, add ... at the end of the parameters list
local function f (a, b, ...)
    local c, d, e = ...  -- assign the first 3 variable args to local variables c, d, and e

    print ("a:", a, "b:", b, "c:", c, "d:", d, "e:", e)

    if c ~= nil then
        f(a, ...) -- use the variable args as arguments in a function call
    end

    return "varags", ... -- return variable arguments 
end

f (1, 2 , 3 , 4)  --> a: 1   b: 2   c: 3    d: 4    e: nil
                  --> a: 1   b: 3   c: 4    d: nil  e: nil
                  --> a: 1   b: 4   c: nil  d: nil  e: nil

Methods are table functions

Lua tables can store values of any type, and in particular they can store function values. Storing functions in tables can have various purposes. A table can be used to group a set of related functions, with keys being the names used to access these functions. For example, Lua standard libraries follow this pattern, and the math library is a table containing functions like sin, cos, tan

Another common reason of storing functions in a table is for object-oriented-programming (OOP). In that case, functions are put in the table to implement the behavior of the object represented by this table. Therefore, they need to have access to the table, generally provided as a parameter. Such functions stored in a table and taking this table as a parameter, are methods.

Lua provides a specific syntax for defining and calling methods, as shown in this code sample:

-- create a table 
local counter = { count = 0 }

-- add  a function, with a table parameter, as a field of this table, 
counter.increment = function (t) 
                        t.count = t.count + 1 
                    end

-- this function definition syntax is equivalent to: counter.decrement = function(t) ... end
function counter.decrement (t)
    t.count = t.count - 1
end

-- let's call the counter's functions on the counter itself
counter.increment(counter) --> counter.count: 1
counter.increment(counter) --> counter.count: 2

-- using the method notation would do the same in a simpler and more elegant way
counter:increment()        --> counter.count: 3 (notice the ':' here)
counter:decrement()        --> counter.count: 2

-- the ':' notation can also be used when declaring a method; it adds an implicit first parameter 'self' to the function
function counter:setValue(newCount)
    self.count = newCount   -- 'self' refers to the current table or object
end

-- call this method
counter:setValue (100)      --> counter.count: 100

In summary:

  • you call a method with the method syntax expression:methodName(param1, param2, ...), which is equivalent to calling it as expression.methodName(expression, param1, param2, ...), with the small (but sometimes significant) difference that with the method notation, the object reference expression is evaluated only once.

  • and you define a method using the syntax:

    function expression:methodName(param1, param2, ...)
        -- self is an implicit parameter, referencing the current object
        -- ...
    end
    
    -- equivalent to the following table function definition syntax:
    function expression.methodName(self, param1, param2, ...)
        -- self, the reference to the current object, shall be the first parameter
        -- ...
    end
    
    

Code chunks and modules

Lua chunks are functions

Lua calls a chunk any Lua source file or string passed to the Lua compiler. A Lua code chunk is compiled as an anonymous function, and what you write in the Lua source file or code string is simply the body of this function.

As a function body, a Lua chunk can define local variables and can return values. In addition a chunk has a (generally unused) vararg parameter and a single upvalue _ENV, that by default gives access to Lua global variables.

All code samples in this article are valid Lua chunks, so you can run them in Bounces or in any other Lua-compatible environment.

In addition, here is an example of a Lua chunk with a return statement, intended to be called from another Lua chunk:

-- This code chunk creates a counter object, sets its methods, and returns it
local counter = { count = 0 }

function counter:increment (delta)
    delta = delta or 1 -- set a default delta value in case this function is called without argument
    self.count = self.count + delta 
end

function counter:decrement (delta)
    delta = delta or 1
    self:increment (-delta)
end

function counter:setValue (newCount)
    self.count = newCount
end

-- return the counter object to caller of the chunk 
return counter

Lua code execution

Executing a Lua chunk is a two-step process: first the Lua compiler transforms the chunk's source code into a function, then the resulting function can be executed.

Lua provides standard library functions, like load and loadfile, that compile a string or file into a function. Let's see how to use them in an example:

-- Get a function for executing a Lua source code string
local sayHello = load ([[print "Hello World"]]) -- using long brackets delimiters for this string avoids to worry about internal single- or double-quotes

-- call this function to execute the source code string
sayHello()  --> Hello World
sayHello()  --> Hello World

-- Suppose that the counter code chunk from the previous example is stored in a file named "counter.lua"
local counterChunkFunction = loadfile("counter.lua")

-- counterChunkFunction is a Lua function whose body is the top-level block in the file "counter.lua"
-- this function creates a counter and returns it

local counter1 = counterChunkFunction() -- create a counter
local counter2 = counterChunkFunction() -- create an other counter

counter1:increment (3) -- counter1.count --> 3
counter1:increment ()  -- counter1.count --> 4

counter2:decrement()   -- counter2.count --> -1

Lua Modules and the require function

A Lua module is basically a code chunk that you want to execute only once, even if it is used in various places of your program. Such a run-only-once behavior is generally desired when importing libraries or object class definitions.

In Lua, you load a module by calling the require function.

require takes a single parameter: the name of the required module. If the given module has not been loaded yet, require compiles it, executes the module's chunk function, and returns the result of this function. On the other hand, if the given module has already been loaded, require simply returns the memorized result of the module's chunk function, without calling it again.

To make this clear, let's see how our counter chunk behaves, when loaded as a module using require:

-- Here the counter code chunk from the example above is stored in a file named "counter.lua", at a location suitable for the require function

local counter1 = require "counter" -- the module name does not include the '.lua' extension
                                   -- require returns the result of the code chunk's function (here a counter table)
counter1:increment (3) -- counter1.count --> 3
counter1:increment ()  -- counter1.count --> 4

-- require "counter" always return the result of the chunk's function first run

local counter2 = require "counter" -- the 'counter' code chunk is not executed the second time
counter2:decrement()   -- counter2.count --> 3
print (counter1 == counter2) --> true

The rules that require uses to find a module by knowing its name are quite different for Bounces and for the standard Lua implementation:

  • Bounces implements project-based module search, well-suited for app development. See Lua modules in Bounces for more details.
  • the standard Lua implementation has a more CLI-oriented approach, based on known file paths and dynamic libraries. Refer to the Modules section of the Lua reference manual for a detailed description.

Going further

At this point, you should have a quite good understanding of the Lua language, except for a few advanced features. If you were already familiar with Lua, you might still have learned one thing, or two.

So, where to go from here?

Read about Bounces additions to Lua

If you are interested in Bounces, the two other articles in this Get started with Lua series are highly recommended readings.

The next article, Get started with Lua - Bounces additions to Lua, presents a few significant additions that Bounces brings to Lua. The main of these additions is the Bounces object framework, a nice and powerful object model integrated right into Lua. The Bounces object framework has a key role in Bounces, as it enables dynamic code update and native objects bridging. And, as you will learn in this article, it is also very easy to use! 😎

The third article of this series, Get started with Lua - Bounces native bridge, is an indispensable overview of the native bridge, the software layer allowing you to transparently mix Lua and native code in your application. In this article, you will learn how to use native objects in your Lua code, how you can use C structs, enums, and most other types in Lua, and how easy it is to make your Lua objects visible (and callable) from the native code.

Learn more about Lua

Of course, this article does not intend to be a complete Lua course, and to keep it at a reasonable length and level of complexity, I chose to omit advanced features of the language.

Among these advanced features, probably the most important are the customization capabilities that are an integral part of the Lua language. lua.org says about these:

Lua is powerful (but simple):
A fundamental concept in the design of Lua is to provide meta-mechanisms for implementing features, instead of providing a host of features directly in the language. For example, although Lua is not a pure object-oriented language, it does provide meta-mechanisms for implementing classes and inheritance. Lua's meta-mechanisms bring an economy of concepts and keep the language small, while allowing the semantics to be extended in unconventional ways.

Beside customization, a few other interesting features of the Lua language have omitted in this article, and may be the subject of a future advanced Lua article:

  • Lua environment and the _ENV variable,
  • Error handling and the pcall function,
  • Coroutines, and the Lua threading model,
  • Memory management in Lua.

Additional resources I recommend if you want to know more about Lua:

Post a Comment