More about functions
Lesson 5.2
Functions that take any number of arguments, functions that remember values, and functions that call themselves.
Any number of arguments
Three dots ... as the last parameter accept any number of extra arguments. They are called varargs:
local function total(...)
local sum = 0
for _, n in ipairs({ ... }) do
sum = sum + n
end
return sum
end
print(total(1, 2, 3)) --> 6
print(total(10, 20, 30, 40)) --> 100
print(total()) --> 0
{ ... } puts the arguments into a table so you can loop over them. The _ is just a variable name; by habit it means “I don’t need this value”.
You can also pass ... straight on to another function, and mix it with normal parameters:
local function log(level, ...)
print("[" .. level .. "]", ...)
end
log("INFO", "Player joined:", "Alex") --> [INFO] Player joined: Alex
When an argument can be nil
{ ... } has a problem with nil in the middle: the length of a table with holes is unreliable. Two tools handle every case:
select("#", ...)gives the real number of arguments,nils included.select(n, ...)gives the arguments from positionnonwards.table.pack(...)makes a table and stores the count in the fieldn.
local function count(...)
return select("#", ...)
end
print(count(1, nil, 3)) --> 3
local args = table.pack("a", nil, "c")
print(args.n, args[3]) --> 3 c
print(select(2, "x", "y", "z")) --> y z
table.unpack does the opposite of table.pack: it turns a list back into separate values.
local pos = { 100, 64, -30 }
print(table.unpack(pos)) --> 100 64 -30
Closures
A function can use local variables from the code around it. It keeps them alive, even after that code has finished. A function together with the variables it remembers is called a closure:
local function make_counter()
local count = 0
return function()
count = count + 1
return count
end
end
local next_id = make_counter()
print(next_id()) --> 1
print(next_id()) --> 2
print(next_id()) --> 3
local other = make_counter()
print(other()) --> 1
Each call to make_counter creates a fresh count, so next_id and other count separately. Nobody else can touch count: it is private to the closure.
Closures are everywhere in event-driven code. A handler function can remember settings or state from where it was created:
local function make_cooldown(seconds)
local last = -math.huge
return function(now)
if now - last < seconds then
return false
end
last = now
return true
end
end
local can_use = make_cooldown(5)
print(can_use(0)) --> true
print(can_use(3)) --> false
print(can_use(6)) --> true
Recursion
A function can call itself. This is called recursion. Each call must move closer to a case that stops:
local function factorial(n)
if n <= 1 then
return 1
end
return n * factorial(n - 1)
end
print(factorial(5)) --> 120
Recursion fits naturally when data contains smaller data of the same kind, such as a folder with subfolders or a crafting recipe whose ingredients have their own recipes.
A local function can call itself because local function f declares f before the body. The longer form local f = function() ... f() ... end would not work: inside the body, f would still be the global f, which is nil.
Stack overflow
Every call that hasn’t returned yet takes some memory. Recursion without a stopping case ends with the error stack overflow. A loop is often the safer choice for very long sequences.
One special case: when a function ends with return f(...), Lua reuses the current call instead of stacking a new one. This is a tail call, and it can recurse forever without overflowing.
Functions in tables
Functions are often stored in tables, grouped by topic. You will see string.upper, math.floor, and in Celestial cl.items.register. Each is a function stored in a table. The Tables chapter shows how to build such tables, and Classes and inheritance how to give them methods.
Try it
Write make_multiplier(factor) that returns a function. The returned function takes a number and multiplies it by factor. Then write sum_all(...) that adds all its arguments and ignores any that aren’t numbers (use select so nils don’t break it).
Show a solution
local function make_multiplier(factor)
return function(x)
return x * factor
end
end
local triple = make_multiplier(3)
print(triple(7)) --> 21
local function sum_all(...)
local sum = 0
for i = 1, select("#", ...) do
local v = select(i, ...)
if type(v) == "number" then
sum = sum + v
end
end
return sum
end
print(sum_all(1, nil, "x", 4)) --> 5
Recap: ... takes any number of arguments, closures remember the variables around them, and recursion solves a problem by solving a smaller copy of it.