Functions
Syntax#
- funcname = function(paramA, paramB, …) body; return exprlist end — a simple function
- function funcname(paramA, paramB, …) body; return exprlist end — shorthand for above
- local funcname = function(paramA, paramB, …) body; return exprlist end — a lambda
- local funcname; funcname = function(paramA, paramB, …) body; return exprlist end — lambda that can do recursive calls
- local function funcname(paramA, paramB, …) body; return exprlist end — shorthand for above
- funcname(paramA, paramB, …) — call a function
- local var = var or “Default” — a default parameter
- return nil, “error messages” — standard way to abort with an error
Remarks#
Functions are usually set with function a(b,c) ... end
and rarely with setting a variable to an anonymous function (a = function(a,b) ... end
). The opposite is true when passing functions as parameters, anonymous functions are mostly used, and normal functions aren’t used as often.
Defining a function
function add(a, b)
return a + b
end
-- creates a function called add, which returns the sum of it's two arguments
Let’s look at the syntax.
First, we see a function
keyword. Well, that’s pretty descriptive. Next we see the add
identifier; the name. We then see the arguments (a, b)
these can be anything, and they are local. Only inside the function body can we access them. Let’s skip to the end, we see… well, the end
! And all that’s in between is the function body; the code that’s ran when it is called. The return
keyword is what makes the function actually give some useful output. Without it, the function returns nothing, which is equivalent to returning nil. This can of course be useful for things that interact with IO, for example:
function printHello(name)
print("Hello, " .. name .. "!");
end
In that function, we did not use the return statement.
Functions can also return values conditionally, meaning that a function has the choice of returning nothing (nil) or a value. This is demonstrated in the following example.
function add(a, b)
if (a + b <= 100) then
return a + b -- Returns a value
else
print("This function doesn't return values over 100!") -- Returns nil
end
end
It is also possible for a function to return multiple values seperated by commas, as shown:
function doOperations(a, b)
return a+b, a-b, a*b
end
added, subbed, multiplied = doOperations(4,2)
Functions can also be declared local
do
local function add(a, b) return a+b end
print(add(1,2)) --> prints 3
end
print(add(2, 2)) --> exits with error, because 'add' is not defined here
They can be saved in tables too:
tab = {function(a,b) return a+b end}
(tab[1])(1, 2) --> returns 3
Calling a function.
Functions are only useful if we can call them. To call a function the following syntax is used:
print("Hello, World!")
We’re calling the print
function. Using the argument "Hello, World"
. As is obvious, this will print Hello, World
to the output stream. The returned value is accessible, just like any other variable would be.
local added = add(10, 50) -- 60
Variables are also accepted in a function’s parameters.
local a = 10
local b = 60
local c = add(a, b)
print(c)
Functions expecting a table or a string can be called with a neat syntactic sugar: parentheses surrounding the call can be omitted.
print"Hello, world!"
for k, v in pairs{"Hello, world!"} do print(k, v) end
Anonymous functions
Creating anonymous functions
Anonymous functions are just like regular Lua functions, except they do not have a name.
doThrice(function()
print("Hello!")
end)
As you can see, the function is not assigned to any name like print
or add
. To create an anonymous function, all you have to do is omit the name. These functions can also take arguments.
Understanding the syntactic sugar
It is important to understand that the following code
function double(x)
return x * 2
end
is actually just a shorthand for
double = function(x)
return x * 2
end
However, the above function is not anonymous as the function is directly assigned to a variable!
Functions are first class values
This means that a function is a value with the same rights as conventional values like numbers and strings. Functions can be stored in variables, in tables, can be passed as arguments, and can be returned by other functions.
To demonstrate this, we’ll also create a “half” function:
half = function(x)
return x / 2
end
So, now we have two variables, half
and double
, both containing a function as a value. What if we wanted to create a function that would feed the number 4 into two given functions, and compute the sum of both results?
We’ll want to call this function like sumOfTwoFunctions(double, half, 4)
. This will feed the double
function, the half
function, and the integer 4
into our own function.
function sumOfTwoFunctions(firstFunction, secondFunction, input)
return firstFunction(input) + secondFunction(input)
end
The above sumOfTwoFunctions
function shows how functions can be passed around within arguments, and accessed by another name.
Default parameters
function sayHello(name)
print("Hello, " .. name .. "!")
end
That function is a simple function, and it works well. But what would happen if we just called sayHello()
?
stdin:2: attempt to concatenate local 'name' (a nil value)
stack traceback:
stdin:2: in function 'sayHello'
stdin:1: in main chunk
[C]: in ?
That’s not exactly great. There are two ways of fixing this:
-
You immediately return from the function:
function sayHello(name) if not (type(name) == "string") then return nil, "argument #1: expected string, got " .. type(name) end -- Bail out if there's no name. -- in lua it is a convention to return nil followed by an error message on error print("Hello, " .. name .. "!") -- Normal behavior if name exists. end
-
You set a default parameter.
To do this, simply use this simple expression
function sayHello(name) name = name or “Jack” — Jack is the default, — but if the parameter name is given, — name will be used instead print(“Hello, ” .. name .. ”!”) end
The idiom name = name or "Jack"
works because or
in Lua short circuits. If the item on the left side of an or
is anything other than nil
or false
, then the right side is never evaluated. On the other hand, if sayHello
is called with no parameter, then name
will be nil
, and so the string "Jack"
will be assigned to name
. (Note that this idiom, therefore, will not work if the boolean false
is a reasonable value for the parameter in question.)
Multiple results
Functions in Lua can return multiple results.
For example:
function triple(x)
return x, x, x
end
When calling a function, to save these values, you must use the following syntax:
local a, b, c = triple(5)
Which will result in a = b = c = 5
in this case. It is also possible to ignore returned values by using the throwaway variable _
in the desired place in a list of variables:
local a, _, c = triple(5)
In this case, the second returned value will be ignored. It’s also possible to ignore return values by not assigning them to any variable:
local a = triple(5)
Variable a
will be assigned the first return value and the remaining two will be discarded.
When a variable amount of results are returned by a function, one can store them all in a table, by executing the function inside it:
local results = {triple(5)}
This way, one can iterate over the results
table to see what the function returned.
Note
This can be a surprise in some cases, for example:
local t = {}
table.insert(t, string.gsub(" hi", "^%s*(.*)$", "%1")) --> bad argument #2 to 'insert' (number expected, got string)
This happens because string.gsub
returns 2 values: the given string, with occurrences of the pattern replaced, and the total number of matches that occurred.
To solve this, either use an intermediate variable or put ()
around the call, like so:
table.insert(t, (string.gsub(" hi", "^%s*(.*)$", "%1"))) --> works. t = {"hi"}
This grabs only the first result of the call, and ignores the rest.
Variable number of arguments
https://stackoverflow.com/documentation/lua/4475/variadic-arguments
Named Arguments
local function A(name, age, hobby)
print(name .. "is " .. age .. " years old and likes " .. hobby)
end
A("john", "eating", 23) --> prints 'john is eating years old and likes 23'
-- oops, seems we got the order of the arguments wrong...
-- this happens a lot, specially with long functions that take a lot of arguments
-- and where the order doesn't follow any particular logic
local function B(tab)
print(tab.name .. "is " .. tab.age .. " years old and likes " .. tab.hobby)
end
local john = {name="john", hobby="golf", age="over 9000", comment="plays too much golf"}
B(john)
--> will print 'John is over 9000 years old and likes golf'
-- I also added a 'comment' argument just to show that excess arguments are ignored by the function
B({name = "tim"}) -- can also be written as
B{name = "tim"} -- to avoid cluttering the code
--> both will print 'tim is nil years old and likes nil'
-- remember to check for missing arguments and deal with them
function C(tab)
if not tab.age then return nil, "age not defined" end
tab.hobby = tab.hobby or "nothing"
-- print stuff
end
-- note that if we later decide to do a 'person' class
-- we just need to make sure that this class has the three fields
-- age, hobby and name, and it will be compatible with these functions
-- example:
local john = ClassPerson.new("John", 20, "golf") -- some sort of constructor
john.address = "some place" -- modify the object
john:do_something("information") -- call some function of the object
C(john) -- this works because objects are *usually* implemented as tables
Checking argument types
Some functions only work on a certain type of argument:
function foo(tab)
return tab.bar
end
--> returns nil if tab has no field bar, which is acceptable
--> returns 'attempt to index a number value' if tab is, for example, 3
--> which is unacceptable
function kungfoo(tab)
if type(tab) ~= "table" then
return nil, "take your useless " .. type(tab) .." somewhere else!"
end
return tab.bar
end
this has several implications:
print(kungfoo(20)) --> prints 'nil, take your useless number somewhere else!'
if kungfoo(20) then print "good" else print "bad" end --> prints bad
foo = kungfoo(20) or "bar" --> sets foo to "bar"
now we can call the function with whatever parameter we want, and it won’t crash the program.
-- if we actually WANT to abort execution on error, we can still do
result = assert(kungfoo({bar=20})) --> this will return 20
result = assert(kungfoo(20)) --> this will throw an error
So, what if we have a function that does something with an instance of a specific class?
This is difficult, because classes and objects are usually tables, so the type
function will return 'table'
.
local Class = {data="important"}
local meta = {__index=Class}
function Class.new()
return setmetatable({}, meta)
end
-- this is just a very basic implementation of an object class in lua
object = Class.new()
fake = {}
print(type(object)), print(type(fake)) --> prints 'table' twice
Solution: compare the metatables
-- continuation of previous code snippet
Class.is_instance(tab)
return getmetatable(tab) == meta
end
Class.is_instance(object) --> returns true
Class.is_instance(fake) --> returns false
Class.is_instance(Class) --> returns false
Class.is_instance("a string") --> returns false, doesn't crash the program
Class.is_instance(nil) --> also returns false, doesn't crash either
Closures
do
local tab = {1, 2, 3}
function closure()
for key, value in ipairs(tab) do
print(key, "I can still see you")
end
end
closure()
--> 1 I can still see you
--> 2 I can still see you
--> 3 I can still see you
end
print(tab) --> nil
-- tab is out of scope
closure()
--> 1 I can still see you
--> 2 I can still see you
--> 3 I can still see you
-- the function can still see tab
typical usage example
function new_adder(number)
return function(input)
return input + number
end
end
add_3 = new_adder(3)
print(add_3(2)) --> prints 5
more advanced usage example
function base64.newDecoder(str) -- Decoder factory
if #str ~= 64 then return nil, "string must be 64 characters long!" end
local tab = {}
local counter = 0
for c in str:gmatch"." do
tab[string.byte(c)] = counter
counter = counter + 1
end
return function(str)
local result = ""
for abcd in str:gmatch"..?.?.?" do
local a, b, c, d = string.byte(abcd,1,-1)
a, b, c, d = tab[a], tab[b] or 0, tab[c] or 0, tab[d] or 0
result = result .. (
string.char( ((a<<2)+(b>>4))%256 ) ..
string.char( ((b<<4)+(c>>2))%256 ) ..
string.char( ((c<<6)+d)%256 )
)
end
return result
end
end