Functions in Lua are first-class values with proper lexical scoping.
What does it mean for functions to be “first-class values”? It means that, in Lua, a function is a value with the same rights as more conventional values like numbers and strings. A program can store functions in variables (both global and local) and in tables, pass functions as arguments to other functions, and return functions as results.
What does it mean for functions to have “lexical scoping”? It means that functions can access variables of their enclosing functions. (It also means that Lua properly contains the lambda calculus.)
Together, these two features give great flexibility to the language; for instance, a program can redefine a function to add new functionality or erase a function to create a secure environment when running a piece of untrusted code (such as code received through a network). More importantly, these features allow us to apply in Lua many powerful programming techniques from the functional-language world. Even if you have no interest at all in functional programming, it is worth learning a little about how to explore these techniques, because they can make your programs smaller and simpler.
As we just saw, functions in Lua are first-class values. The following example illustrates what that means:
a = {p = print} -- 'a.p' refers to the 'print' function a.p("Hello World") --> Hello World print = math.sin -- 'print' now refers to the sine function a.p(print(1)) --> 0.8414709848079 math.sin = a.p -- 'sin' now refers to the print function math.sin(10, 20) --> 10 20
If functions are values, are there expressions that create functions? Sure. In fact, the usual way to write a function in Lua, such as
function foo (x) return 2*x end
is an instance of what we call syntactic sugar; it is simply a pretty way to write the following code:
foo = function (x) return 2*x end
The expression in the
right-hand side of the assignment
(function (x)
)
is a function constructor,
in the same way that body
end{}
is a table constructor.
Therefore, a function definition is in fact a statement
that creates a value of type "function"
and assigns it to a variable.
Note that, in Lua, all functions are anonymous.
Like any other value,
they do not have names.
When we talk about a function name, such as print
,
we are actually talking about a variable that holds that function.
Although we often assign functions to global variables,
giving them something like a name,
there are several occasions when functions remain anonymous.
Let us see some examples.
The table library provides the function table.sort
,
which receives a table and sorts its elements.
Such a function must allow unlimited variations in the sort order:
ascending or descending, numeric or alphabetical,
tables sorted by a key, and so on.
Instead of trying to provide all kinds of options,
sort
provides a single optional parameter,
which is the order function:
a function that takes two elements
and returns whether the first must come before
the second in the sorted list.
For instance, suppose we have a table of records like this:
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"}, }
If we want to sort the table by the field name
,
in reverse alphabetical order,
we just write this:
table.sort(network, function (a,b) return (a.name > b.name) end)
See how handy the anonymous function is in this statement.
A function that takes another function as an argument,
such as sort
,
is what we call a higher-order function.
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.
Nevertheless,
remember that higher-order functions have no special rights;
they are a direct consequence of the fact that
Lua handles functions as first-class values.
To further illustrate the use of higher-order functions, we will write a naive implementation of a common higher-order function, the derivative. In an informal definition, the derivative of a function f is the function f’(x) = (f(x + d) - f(x)) / d when d becomes infinitesimally small. According to this definition, we can compute an approximation of the derivative as follows:
function derivative (f, delta) delta = delta or 1e-4 return function (x) return (f(x + delta) - f(x))/delta end end
Given a function f
, the call derivative(f)
returns
(an approximation of) its derivative,
which is another function:
c = derivative(math.sin) > print(math.cos(5.2), c(5.2)) --> 0.46851667130038 0.46856084325086 print(math.cos(10), c(10)) --> -0.83907152907645 -0.83904432662041
An obvious consequence of first-class functions is that we can store functions not only in global variables, but also in table fields and in local variables.
We have already seen several examples of functions in table fields:
most Lua libraries use this mechanism
(e.g., io.read
, math.sin
).
To create such functions in Lua,
we only have to put together what we have learned so far:
Lib = {} Lib.foo = function (x,y) return x + y end Lib.goo = function (x,y) return x - y end print(Lib.foo(2, 3), Lib.goo(2, 3)) --> 5 -1
Of course, we can also use constructors:
Lib = { foo = function (x,y) return x + y end, goo = function (x,y) return x - y end }
Moreover, Lua offers a specific syntax to define such functions:
Lib = {} function Lib.foo (x,y) return x + y end function Lib.goo (x,y) return x - y end
As we will see in Chapter 21, Object-Oriented Programming, the use of functions in table fields is a key ingredient for object-oriented programming in Lua.
When we store a function into a local variable, we get a local function, that is, a function that is restricted to a given scope. Such definitions are particularly useful for packages: because Lua handles each chunk as a function, a chunk can declare local functions, which are visible only inside the chunk. Lexical scoping ensures that other functions in the chunk can use these local functions.
Lua supports such uses of local functions with a syntactic sugar for them:
local function f (params
)body
end
A subtle point arises in the definition of recursive local functions, because the naive approach does not work here. Consider the next definition:
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, this expression will try to call a global fact
,
not the local one.
We can solve this problem by first defining the local variable
and then the function:
local fact fact = function (n) if n == 0 then return 1 else return n*fact(n-1) end end
Now the fact
inside the function refers to the local variable.
Its value when the function is defined does not matter;
by the time the function executes,
fact
already has the right value.
When Lua expands its syntactic sugar for local functions, it does not use the naive definition. Instead, a definition like
local function foo (params
)body
end
expands to
local foo; foo = function (params
)body
end
Therefore, we can use this syntax for recursive functions without worrying.
Of course, this trick does not work if we have indirect recursive functions. In such cases, we must use the equivalent of an explicit forward declaration:
local f -- "forward" declaration local function g ()some code
f()some code
end function f ()some code
g()some code
end
Beware not to write local
in the last definition.
Otherwise, Lua would create a fresh local variable f
,
leaving the original f
(the one that g
is bound to) undefined.
When we write a function enclosed in another function, it has full access to local variables from the enclosing function; we call this feature lexical scoping. Although this visibility rule may sound obvious, it is not. Lexical scoping plus nested first-class functions give great power to a programming language, but many do not support the combination.
Let us start with a simple example. Suppose we have a list of student names and a table that maps names to grades; we want to sort the list of names according to their grades, with higher grades first. We can do this task as follows:
names = {"Peter", "Paul", "Mary"} grades = {Mary = 10, Paul = 7, Peter = 8} table.sort(names, function (n1, n2) return grades[n1] > grades[n2] -- compare the grades end)
Now, suppose we want to create a function to do this task:
function sortbygrade (names, grades) table.sort(names, function (n1, n2) return grades[n1] > grades[n2] -- compare the grades end) end
The interesting point in this last example is that
the anonymous function given to sort
accesses grades
,
which is a parameter to the enclosing function sortbygrade
.
Inside this anonymous function,
grades
is neither a global variable nor a local variable,
but what we call a non-local variable.
(For historical reasons,
non-local variables are also called upvalues in Lua.)
Why is this point so interesting? Because functions, being first-class values, can escape the original scope of their variables. Consider the following code:
function newCounter () local count = 0 return function () -- anonymous function count = count + 1 return count end end c1 = newCounter() print(c1()) --> 1 print(c1()) --> 2
In this code,
the anonymous function refers to a non-local variable (count
)
to keep its counter.
However, by the time we call the anonymous function,
the variable count
seems to be out of scope,
because the function that created this variable
(newCounter
) has already returned.
Nevertheless, Lua handles this situation correctly,
using the concept of closure.
Simply put, a closure is a function plus all it
needs to access non-local variables correctly.
If we call newCounter
again,
it will create a new local variable count
and a new closure,
acting over this new variable:
c2 = newCounter() print(c2()) --> 1 print(c1()) --> 3 print(c2()) --> 2
So, c1
and c2
are different closures.
Both are built over the same function,
but each acts upon an independent
instantiation of the local variable count
.
Technically speaking, what is a value in Lua is the closure, not the function. The function itself is a kind of a prototype for closures. Nevertheless, we will continue to use the term “function” to refer to a closure whenever there is no possibility for confusion.
Closures provide a valuable tool in many contexts.
As we have seen,
they are useful as arguments to higher-order functions
such as sort
.
Closures are valuable for functions that build other functions too,
like our newCounter
example or the derivative example;
this mechanism allows Lua programs to incorporate
sophisticated programming techniques from the functional world.
Closures are useful for callback functions, too.
A typical example here occurs
when we create buttons in a conventional GUI toolkit.
Each button has a callback function to be called
when the user presses the button;
we want different buttons to do
slightly different things when pressed.
For instance, a digital calculator needs ten similar buttons, one for each digit. We can create each of them with a function like this:
function digitButton (digit) return Button{ label = tostring(digit), action = function () add_to_display(digit) end } end
In this example, we pretend that Button
is a toolkit function
that creates new buttons;
label
is the button label;
and action
is the callback function
to be called when the button is pressed.
The callback can be called
a long time after digitButton
did its task,
but it can still access the digit
variable.
Closures are valuable also in a
quite different context.
Because functions are stored in regular variables,
we can easily redefine functions in Lua,
even predefined functions.
This facility is one of the reasons why Lua is so flexible.
Frequently, when we redefine a function,
we need the original function in the new implementation.
As an example, suppose we want to redefine the function sin
to
operate in degrees instead of radians.
This new function converts its argument
and then calls the original function sin
to do the real work.
Our code could look like this:
local oldSin = math.sin math.sin = function (x) return oldSin(x * (math.pi / 180)) end
A slightly cleaner way to do this redefinition is as follows:
do local oldSin = math.sin local k = math.pi / 180 math.sin = function (x) return oldSin(x * k) end end
This code uses a do block to limit the scope of
the local variable oldSin
;
following conventional visibility rules,
the variable is only visible inside the block.
So, the only way to access it is through the new function.
We can use this same technique to create secure environments,
also called sandboxes.
Secure environments are essential when running untrusted code,
such as code received through the Internet by a server.
For instance, to restrict the files that a program can access,
we can redefine io.open
using closures:
do
local oldOpen = io.open
local access_OK = function (filename, mode)
check access
end
io.open = function (filename, mode)
if access_OK(filename, mode) then
return oldOpen(filename, mode)
else
return nil, "access denied"
end
end
end
What makes this example nice is that, after this redefinition,
there is no way for the program to call the
unrestricted version of function io.open
except through the new, restricted version.
It keeps the insecure version as a private variable in a closure,
inaccessible from the outside.
With this technique,
we can build Lua sandboxes in Lua itself,
with the usual benefits: simplicity and flexibility.
Instead of a one-size-fits-all solution,
Lua offers us a meta-mechanism,
so that we can tailor our environment
for our specific security needs.
(Real sandboxes do more than protecting external files.
We will return to this subject in the section called “Sandboxing”.)
To give a more concrete example of functional programming, in this section we will develop a simple system for geometric regions.[11] The goal is to develop a system to represent geometric regions, where a region is a set of points. We want to be able to represent all kinds of shapes and to combine and modify shapes in several ways (rotation, translation, union, etc.).
To implement this system, we could start looking for good data structures to represent shapes; we could try an object-oriented approach and develop some hierarchy of shapes. Or we can work on a higher level of abstraction and represent our sets directly by their characteristic (or indicator) function. (The characteristic function of a set A is a function fA such that fA(x) is true if and only if x belongs to A.) Given that a geometric region is a set of points, we can represent a region by its characteristic function; that is, we represent a region by a function that, given a point, returns true if and only if the point belongs to the region.
As an example, the next function represents a disk (a circular region) with center (1.0, 3.0) and radius 4.5:
function disk1 (x, y) return (x - 1.0)^2 + (y - 3.0)^2 <= 4.5^2 end
With higher-order functions and lexical scoping, it is easy to define a disk factory, which creates disks with given centers and radius:
function disk (cx, cy, r) return function (x, y) return (x - cx)^2 + (y - cy)^2 <= r^2 end end
A call like disk(1.0, 3.0, 4.5)
will create a disk
equal to disk1
.
The next function creates axis-aligned rectangles, given the bounds:
function rect (left, right, bottom, up) return function (x, y) return left <= x and x <= right and bottom <= x and x <= up end end
In a similar fashion, we can define functions to create other basic shapes, such as triangles and non–axis-aligned rectangles. Each shape has a completely independent implementation, needing only a correct characteristic function.
Now let us see how to modify and combine regions. To create the complement of any region is trivial:
function complement (r) return function (x, y) return not r(x, y) end end
Union, intersection, and difference are equally simple, as we show in Figure 9.1, “Union, intersection, and difference of regions”.
Figure 9.1. Union, intersection, and difference of regions
function union (r1, r2) return function (x, y) return r1(x, y) or r2(x, y) end end function intersection (r1, r2) return function (x, y) return r1(x, y) and r2(x, y) end end function difference (r1, r2) return function (x, y) return r1(x, y) and not r2(x, y) end end
The following function translates a region by a given delta:
function translate (r, dx, dy) return function (x, y) return r(x - dx, y - dy) end end
To visualize a region, we can traverse the viewport testing each pixel; pixels in the region are painted black, pixels outside it are painted white. To illustrate this process in a simple way, we will write a function to generate a PBM (portable bitmap) file with the drawing of a given region.
PBM files have a quite simple structure.
(This structure is also highly inefficient,
but our emphasis here is simplicity.)
In its text-mode variant,
it starts with a one-line header with the string "P1"
;
then there is one line with the width and height of the drawing,
in pixels.
Finally, there is a sequence of digits,
one for each image pixel (1 for black, 0 for white),
separated by optional spaces and end of lines.
The function plot
in Figure 9.2, “Drawing a region in a PBM file”
creates a PBM file for a given region,
mapping a virtual drawing area (-1,1], [-1,1) to
the viewport area [1,M], [1,N].
To complete our example, the following command draws a waxing crescent moon (as seen from the Southern Hemisphere):
c1 = disk(0, 0, 1) plot(difference(c1, translate(c1, 0.3, 0)), 500, 500)
Exercise 9.1:
Write a function integral
that takes a function f
and returns an approximation of its integral.
Exercise 9.2: What will be the output of the following chunk:
function F (x) return { set = function (y) x = y end, get = function () return x end } end o1 = F(10) o2 = F(20) print(o1.get(), o2.get()) o2.set(100) o1.set(300) print(o1.get(), o2.get())
Exercise 9.3:
Exercise 5.4 asked you to write a function that
receives a polynomial (represented as a table)
and a value for its variable,
and returns the polynomial value.
Write the curried version of that function.
Your function should receive a polynomial
and return a function that,
when called with a value for x
,
returns the value of the polynomial for that x
.
See the example:
f = newpoly({3, 0, 1}) print(f(0)) --> 3 print(f(5)) --> 28 print(f(10)) --> 103
Exercise 9.4: Using our system for geometric regions, draw a waxing crescent moon as seen from the Northern Hemisphere.
Exercise 9.5: In our system for geometric regions, add a function to rotate a given region by a given angle.
[11] This example is adapted from the research report Haskell vs. Ada vs. C++ vs. Awk vs. ... An Experiment in Software Prototyping Productivity, by Paul Hudak and Mark P. Jones.
Personal copy of Eric Taylor <jdslkgjf.iapgjflksfg@yandex.com>