Suppose I had to learn Lua in a hurry. What would you suggest I read and/or do?
I have some Lua scripts to fix and/or extend. Despite being a moderately competent programmer, and having a total understanding of the environment in which the Lua engine is embedded, I've never looked at Lua code.
Just to be clear, I'm looking for things like good cheat-sheets, explanations of the major gotchas in Lua, and short reference guides that get to the point without a lot of waffle or overly basic tuition on general programming.
Rather than linking you to a generic guide, here's something that's hopefully more tailored to your needs (being a competent programmer).
First off:
Single-line comments begin with --
, and multiple-line comments are surrounded by --[[ comment ]]
.
There are two types of variables: local
and global
.
local
variables are lexically scoped, that is:
local function abc()
local x = 5
local my_f = function()
x = 7.3
end
my_f() -- executes my_f
print(x) -- prints '7.3'
end
abc() -- executes abc
print(x) -- print 'nil'
As seen above, the default value for an unset variable is nil
(unlike in compiled languages where it is not valid to operate on an undeclared variable).
Lua is dynamically-typed, but has a very restricted set of readily available types. The types are: number, string, boolean, nil, function, table, thread, userdata, and light userdata.
Number, as seen above, stores a double precision floating point number (can represent any 32bit integer, but not every 64bit integer).
String is similar to strings in most other languages. The available literals for string values are:
s1 = "my 'text' here"
s2 = 'my \'text\' here'
s3 = [[is this three blackslashes? \\\ yes it is]]
Strings can be appended using the concatenation operator: ..
, for example:
print("yo".." ".."dawg") -- prints 'yo dawg'
Strings in Lua are immutable internally. Comparing and assigning them is cheap, but concatenation and manipulation is slower. But you don't typically have to worry about the performance of this - simply don't overdo string concatenation in places where you need your code to be really fast.
From Lua's point of view, a String is simply a byte-sequence; Lua has no conception of Unicode, but you can basically just store UTF-8 encoded data inside a Lua string (and also zero-bytes).
Booleans are as expected: true
or false
.
The flow control keywords that are boolean dependent attempt to evaluate given values as either true
or false
, for example:
if s1 == s2 then print("yay!") end -- the == operator returns true or false appropriately
if s2 ~= s3 then print("cheers") end -- in Lua, ~= is the 'not equal to' operator
if s1 then print("yep") end -- all strings evaluate to true
if nil then print("uh oh") end -- nil evaluates to false
The only values that evaluate to false
are nil
and false
itself.
The other available comparison operators are ==
, ~=
, <
, >
, <=
and >=
.
nil
is a special type; some consider it not a type, but rather the lack of a type (and hence the lack of a value). It's entirely semantics, and should just be treated appropriately to its behavior. (It was bloody difficult to decide whether to write it as nil
or nil...)
Functions are treated as values in Lua - making for an interestingly wonderful level of dynamic programming. Every file executes in an invisible 'wrapper' function. local
variables declared in the file remain in the file's scope only.
Every function has an 'environment'; by default, this environment is _G
. When a variable not declared in the current scope is accessed, a variable will be created in the environment, for example:
function xyz() -- this is also created in _G, due to the lack of the 'local' keyword
x = 8
end
print(x) -- prints '8'
Any function that executes after this that also has its environment set to _G
will be able to access this variable.
In any conflict between identically named local
and global
variables, the local
variable will always by chosen. Similarly with any conflicts between identically named local
variables available in the current scope: the deepest/closest variable available will be chosen, for example:
z = 100
local z = 22
function abc()
local z = 6
print(z) -- prints '6'
print(_G.z) -- prints '100'
end
abc()
print(z) -- prints '22'
Note: function abc() end
is simply syntactic sugar for abc = function() end
.
Functions can be passed values and can return values - as expected; I would love someone to show me a viable functional language without being able to pass values directly to and from functions.
Arguments within a function are treated as local
. To demonstrate the workings:
abc = function(x, y, z)
print(x+y*z)
end
abc(1.1, 1, 7) -- prints '8.1'
This can be explained with some pseudocode:
abc = function( ARGUMENT_STACK )
local x = FIRST_ARGUMENT_PASSED
local y = SECOND_ARGUMENT_PASSED
local z = THIRD_ARGUMENT_PASSED
print(x+y*z)
end
abc(1.1, 1, 7)
Values can be returned using the return
statement, which pushes values into the return stack; these values can be assigned to variables by the caller, or simply discarded, for example:
abc = function(x, y, z)
return x+y*z, 22
end
q, w = abc(1, 2, 3)
print(q, w) -- prints '7 22'
Lua is very accepting - it does not check the length of the argument stack (abc(1, 2)
), nor does it check the types of the values.
All values of Plain Old Data types, such as number, string and boolean, are passed as anonymous copies, that is: the value is copied (efficiently) and any changes to the local argument variable will not affect the value of the original variable (unlike references in C++ or similar), for example:
local z = 8
abc = function(x)
x = 22
end
abc(z)
print(z) -- prints '8'
The same occurs for return values.
Values of non-POD types are passed as references, that is: they hold the same value as the variable used to pass it.
The most fundamental non-POD type is table
.
It is also the most versatile.
A table is simply a key-value map; it takes a value as a key, hashes it, takes a value as the value, and stores appropriately. A blank table can be created with the literal {}
. A table can be indexed (by key) with t[value]
, where t
is the table, or by the syntactic sugar t.identifier
which translates to t["identifier"]
. Table indexes are treating by Lua as variables, allowing for easy assignment, for example:
myTable = {} -- using difference name style just to prove everything doesn't have to be lowercase! :)
myTable["abc"] = 7
print(myTable["abc"]) -- prints '7'
(I hope you don't get confused by 'value' as in "a variable's value" and "a key-value pair"!)
The key and value can hold values of ANY type. As with functions, values of POD types will be passed as copies while non-POD types are passed as references.
Yes.. that does mean you can use tables as keys. The thing to remember is that every table (and values of other non-POD types) will serialise to its own hash, determined by its location in the memory; this applies to the ==
and ~=
operators as well.
The literals used for table creation can also be used as a form of easy initialisation, for example:
t = {
["xyz"] = 123,
abc = "what",
LaunchPlaySuperSuperFar = function() --[[ TODO ]] end,
}
print(t.xyz) -- prints '123'
There is a convenient feature with the initialising form that makes tables easy to use as arrays: if you omit the key, it will automatically assign an automatically increasing numeric key starting at 1, for example:
t = {"a", "b", "W! HAHA DIDN'T EXPECT THAT!"}
print(t[2]) -- prints 'b'
-- unfortunately t.2 doesn't work... or perhaps fortunately, it's a bit weird looking
Note that the automatic array initialising can be freely mixed with the standard form, for example:
t = {"xyz", Banana = 3, Apple = 7, "Oh no!", Orange = 1338}
--- t[1] == "xyz", t[2] == "Oh no1", t.Banana == 3, etc
This may seem confusing and slightly pointless, but it can be useful when constructing more advanced objects, such as a class system (to be discussed later).
#s
gives the length of an array (does not count non-numeric keys).
Note: Function environments, such as _G, are simply tables. In Lua 5.1, you can set the environment of a function with
setfenv
[3] (as for Lua 5.2, I suggest you do some research), for example:
yes = "no"
t = {}
t.yes = "yes"
abc = function()
print(yes)
end
setfenv(abc, t)
abc() -- prints 'yes'
Side-note: The type of a value can be retrived in string format using
type
[4], for example: type(123.4) == "number"
The fact that array (actually just numerically keyed table) initialising starts at 1 leads to a very very very very VERY important point: array indexes, in Lua, start at 1 (One, Uno, Single) - NOT 0 (Zero, Zilch). If you are from a C++ background or similar, you must learn this.
Lua is an imperative language; therefore it must have flow control.
The control statements available are if
, else
, elseif
, do
, while
, repeat
, for
, for in
and context-dependent statements such as return
and break
.
Most of the statements that contain blocks of code have their end marked by end
.
Despite being a flow control paradigm, functions are not really considered 'statements'; this is entirely semantics, however. The declaration of a function body is a block, and is ended with end
; it's entirely up to you on how you conceptualise it.
The if
statement should be the most familiar, and not just because it was introduced earlier. Its syntactic form and operating behaviour are as to be expected.
if "a" == "b" then
print("If this prints, we have a problem!")
elseif 7 > 3 then
print("Lua appears to have done elementary math.")
else
print("Lua didn't go to school, it would seem.")
end
The do
statement is the most basic. It simply executes code in a new scope, for example:
do
local x = 7
print(x) -- prints '7'
end
print(x) -- prints 'nil'
The block of code in a do statement is guaranteed to run in the location it is declared. (When I first learnt Lua, I thought it was a threaded block of code... I was wrong.)
The while
statement simply executes a block of code repeatedly until the condition evaluates to false
, for example:
a = 0
while a < 4 do
a = a+1
end
print(a) -- prints '4'
The while
statement will evaluate the condition when it is first encountered; if the condition evaluates to false immediately, the block of code will not run.
Sometimes, this is not desirable, hence there is the repeat
statement.
s = "you banana you"
number_of_vowels = 0
index = 1
repeat
if string.match(string.sub(s, index, index), "[aeiou]") then
number_of_vowels = number_of_vowels+1
end
index = index+1
until index > string.len(s) -- #s would also give the length of the string
There are two types of for
loops; the first is quite basic, and is mainly syntactic sugar.
t = {"a", "b", "c"}
do
local i = 1
while i <= #t do
print(t[i])
i = i+1
end
end
This can be compressed into:
t = {"a", "b", "c"}
for i = 1, #t, 1 do
print(t[i])
end
The format is for variable = minimum, inclusive_maximum, step
, where interval is how much to add to the variable each time (can be non-integer and/or negative.. just remember to flip the minimum and maximum). Wrapping the while
loop above in a do
block is required as the variable will be local to the loop; it will not use any local
or global variable available in its scope.
The format of the second type is as follows:
for var1, var2, var3, ... in iterator_function, arg1, arg2, arg3, ... do
-- code
end
This type can be difficult to understand - in fact, its workings may be considered as 'internal' which is not what you wished to discuss.
A simplified explanation:
iterator_function is called with its arguments and it returns var1, var2 and var3 the code is run with the values of var1, var2, and var3 iterator_function is called with var1, var2 and var3 as its arguments, and returns var1, var2 and var3 the code is run with the values of var1, var2 and var3 rinse and repeat until the value of var1 is nil (may be first time, in which block will never run)
To make this significantly easier, Lua provides
pairs
[5] and
ipairs
[6]; these provide iterator_function
s and their arguments for iterating over tables, for example:
t = {a = 1, b = 2, c = 22}
for k, v in pairs(t) do
print(k.." is "..v)
end
--[[ Result will be:
a is 1
b is 2
c is 22
]]
ipairs
is for operating over array tables.
Note:: In the standard Lua implementation, ipairs
is extremely inefficient compared to for i = 1, #t do
. I don't have the research available, but I know it to be true.
In any of the loop-based flow control statements above, the break
statement can be used to cease the execution of the deepest loop and continue execution.
The return
statement acts in a similar way. It returns from the current function.
Lua has boolean logic.. but like how values can evaluate to booleans, boolean logic can evaluate to values, for example:
y = true
x = y and 5 or -7
print(x) -- prints '5'
This is the same as:
y = true
if y then
x = 5
else
x = -7
end
print(x) -- prints '5'
If you are familiar with a language that uses the condition ? valueA : valueB
syntax, then an understanding of this should come easier to you.
You can extend the statements, for example:
y = false
z = false
x = y and 1 or z and 2 or 3
print(x) -- prints '3'
z = true
x = y and 1 or z and 2 or 3
print(x) -- prints '2'
This allows for massive compound chaining of logic.
Unfortunately, this may quick become an incomprehensible mess of and
s, or
s and poor indentation; I find the following indentation style is helpful:
local colour = (
string.match(str, "^pale") and (
string.match(str, "red$") and Colour(255, 120, 120)
or string.match(str, "blue$") and Colour(120, 120, 255)
or Colour(200, 200, 200)
)
or string.match(str, "^dark") and (
string.match(str, "red$") and Colour(120, 0, 0)
or string.match(str, "blue$") and Colour(0, 0, 120)
)
or str == "red" and Colour(255, 0, 0)
or str == "blue" and Colour(0, 0, 255)
or str == "white" and Colour(255, 255, 255)
or error("Unknown colour!")
)
(Just an example.. I'm sure the functionality could be much better implemented)
Error messages in Lua are quite helpful. By default, they provide you with a descriptive error message, a line number, and a stack traceback (:breadcrumb trail of function calls).
test.lua:43: 'end' expected (to close 'function' at line 29) near '<eof>'
This particular error message is possibly the most helpful ever; when I discovered it I thought "Genius!". (My head practically exploded when I tried Eclipse IDE).
As they say "Keep your code close and your error messages closer" - learn them and you'll find the path of Lua much nicer to tread.
For practical use of Lua, you need to be aware of the standard functions; a full list of the functions available in the standard Lua 5.1 libraries can be found in the Lua 5.1 manual [7].
It would be extremely tedious to document a set of important functions you may find useful, even if a small subset and the information brief.
I found a script I made in less than 20 minutes for a challenge. It identifies statistics on a set of names regarding how easy they are to type on standard 0-9 keyed phones (with "abc", "def", etc). It's available here [8] (see bottom for results).
I apologise about the poor sample quality... it should still be useful it you look at the functions I use to perform certain operations.
You should do some research into additional libraries and batteries-included packages, such as Penlight [9] and stdlib [10]. I suggest you take a look at Lua for Windows [11]; it has an extensive range of commonly known libraries, from GUI and IO libraries to XML parsers and IDEs. It's quite helpful for someone starting the language.
As for object orientation, it's mostly up to you; the dynamic model that Lua uses allows for you to use tables to represent very complex objects.
To assist with this, Lua provides metatables and userdata. I won't go into depth here, but they very powerful and provide the means to implement every type of object orientation model imaginable, albeit less efficiently than with a specially design language.
I find metatables to be a very empowering tool, and I would not use Lua if it lacked them. If you are looking to use Lua for serious development, I suggest you look into them.
I want to include more information about them in this introduction, but I lack the time and effort (perhaps someone else could expand on them, please).
In my programming days, I've found Lua most useful. It's an incredible language that can be applied to any situation.
Depending on what you wish to use Lua for, you'll find it leads you on a different path.
Wherever it takes you, I hope this introduction was a good start for your future endeavors.
Good luck in the land of Lua! :)
[1] http://luabinaries.sourceforge.net/If you are looking for a simple, compressed list (cheat sheet) of what is available in Lua 5.1, take a look at the Lua Short Reference [1].
It really is short (4 pages printed), but apart from some other languages, it is a complete reference to the language, i.e. syntax, semantics and base library are all explained in the 4 pages. This is what I like about Lua :)
There are also a bunch of tutorials on lua-users.org: Tutorial Dictionary [2].
[1] http://lua-users.org/wiki/LuaShortReferenceI personally find Lua Unofficial FAQ [1] very useful for those gotcha questions.
[1] http://www.luafaq.org/If you are the learning-while-programming type, then you can have a look at the Lua Missions [1] (a.k.a. Lua Koans).
Disclaimer: I'm the Lua Missions author.
[1] https://github.com/kikito/lua_missionsSpend a day or two experimenting, then do the assignment with one eye on the reference. Usually works for me. :)
I myself got fairly up to speed on Lua within roughly a month by skimming through "Programming in Lua" (PiL) textbook, 2nd ed., looking for quick reference on anything needed in the Lua Reference Manual, and by experimenting in the Lua REPL (having first aliased it to rlwrap lua
). I used the hard copy form of the books mentioned, because it suits me better, but the 1st edition of PiL and the Reference Manual are also available online
here
[1] and
here
[2].