Getting to know Lua (Part 3)
22 Jan 2018This post is in continuation of previous post Getting to know Lua (Part 2)
Session 3: Functions
Functions are the main mechanism for abstraction of statements and expressions in Lua
print "Hello World" <--> print("Hello World")
Mutiple returns
function foo0 () end -- returns no results
function foo1 () return 'a' end -- returns 1 result
function foo2 () return 'a','b' end -- returns 2 results
Variable Number of Arguments
function sum (...)
local s = 0
local arg = {...}
for i, v in ipairs(arg) do
s = s + v
end
return s
end
print(sum(13, 5, 3, 1, 5, 7)) -- 34
Named Arguments
The parameter passing mechanism in Lua is positional
One may think that passing naed arguments may be same as passing a number of arguments with names:
rename(old="old.txt", new="new.txt") -- invalid syntax
Lua has no direct support for this syntax. The idea here is to pack all arguments into a table
and use that table as the only argument to the function. The special syntax that Lua provides for function calls, with just one table constructor as argument, helps the trick:
rename{old="old.txt", new="new.txt"} -- works
More about Functions
Functions in Lua are first-class citizens with proper lexical scoping.
What does first-class citizens
mean? It means that a function is a value with the same rights as conventional values like numbers and strings. Functions can be stored in variables, passed as arguments or returned as results.
What does proper lexical scoping
mean? It means that functions can access variables of its enclosing functions. (It also means that Lua contains the lambda calculus properly.)
A function that gets another function as an argument, is called a higher-order function.
Taking an example from the book
network = {
{name = "grauna", IP = "210.26.30.34"},
{name = "arraial", IP = "210.26.30.23"},
{name = "lua", IP = "210.26.23.12"},
{name = "derain", IP = "210.26.23.20"},
}
table.sort(network,
function (a,b)
return (a.name > b.name)
end
)
Higher-order functions are a powerful programming mechanism and the use of anonymous functions to create their function arguments is a great source of flexibility. But remember that higher-order functions have no special rights; they are a simple consequence of the ability of Lua to handle functions as first-class values.
Closures
A function written within a function has full access to local variables of the parent function; this feature is called lexical scoping.
Lexical scoping, plus first-class functions, is a powerful concept in a programming language, but few languages support that concept.
Consider the following example:
function newCounter ()
local i = 0
print(i, "As")
return function () -- anonymous function
i = i + 1
return i
end
end
c = newCounter()
print(c()) --> 1
print(c()) --> 2
Now this is interesting!
Now, the anonymous function uses an upvalue, i, to keep its counter. However, by the time we call the anonymous function, i is already out of scope, because the function that created that variable (newCounter) has returned. Nevertheless, Lua handles that situation correctly, using the concept of closure. Simply put, a closure is a function plus all it needs to access its upvalues correctly.
Technically speaking, what is a value in Lua is the closure, not the function.
Function itself is just a prototype for closures.
Closures provide a valuable tool in many contexts. Closures are valuable for functions that build other functions too, like the newCounter example; this mechanism allows Lua programs to incorporate fancy programming techniques from the functional world. Closures are useful for callback functions, too.
Because functions are stored in regular variables, we can easily redefine functions in Lua, even predefined functions.
Non-Global functions
Beacuse functions are first-class, we can not only store functions in global variables but also in table fields and in local variables.
Math = {
sum = function (x, y) return x + y end,
sub = function (x, y) return x - y end
}
Some abnormalities arise while defining recursive local functions.
local fact = function (n)
if n == 0 then return 1
else return n*fact(n-1) -- buggy
end
end
When Lua compiles the call fact(n-1), in the function body, the local fact is not yet defined. Therefore, that expression calls a global fact, not the local one. To solve that problem, we must first define the local variable and then define the function:
local fact
fact = function (n)
if n == 0 then return 1
else return n*fact(n-1)
end
end
Proper Tail Calls
A tail call is a kind of goto dressed as a call
It happens when a function calls another function as its last action, and has nothing else to do.
function f (x)
return g(x) -- call to g is a tail call
end
In such situations, the program does not need to return to the calling function when the called function ends. Therefore, after the tail call, the program does not need to keep any information about the calling function in the stack.
Because a proper tail call uses no stack space, there is no limit on the number of “nested” tail calls that a program can make.
function foo (n)
if n > 0 then
return foo(n - 1) -- will never overflow the stack
end
end
The following implementation is not a tail call,
function f(x)
g(x)
return
end
The problem is that after calling g, f has to discard occasional results from g before returning.
return g(x) + 1 -- must do the addition, so not a tail call