Chapter 4
A function chunk has the form
\ ( parameters ) body end (in standard Lua you must use the word
function in place of
\ ). The parameters are a comma-separated list of
identifiers, which are treated as local variables
in the body.
An expression for a function is
applied by following it by a comma-separated list of arguments
in parentheses (though, as we have seen, for single
arguments, in certain circumstances, these may be omitted).
Every function application contains an implicit multiple assignment,
as if the parameters were to the left of an equal sign and the arguments
were to the right. In fact, in Lua assignments can be multiple.
x,y,z = y,x
This switches the values of the variables
x and
y and assigns
nil to
z . In a multiple assignment the
expressions on the right hand side are all evaluated first, before
any of the assignments are made. If there are too many variables
on the left hand side the extra ones are assigned a nil value. If there
are not enough, the extra expressions on the right are ignored.
In Lua functions can return multiple arguments. Think of the
symbol
=> (in standard Lua the word
return ) as a sort of equal sign followed by the
right hand side of an assignment.
minmax = \(x,y)
if x<y then => x,y else => y,x end -- if
end -- function
min,max = minmax(foo,bar)
If an expression that returns multiple results is used
in the right hand side of a multiple assignment, then, unless
it is the last expression, only its first value is used.
a,b,c = 100,minmax(12,7)
print(a," ",b," ",c) --> 100 7 12
a,b,c = minmax(12,7),100
print(a," ",b," ",c) --> 7 100 nil
Enclosing an expression in parentheses ensures that only its
first result is returned.
Many of the functions in Lua's standard libraries that are
supposed to return a non-nil first result, signify that an
error has occurred by returning nil as the first result
with the error message as the second result .
When something goes wrong with the program, execution stops and an
error message is output. You can make this happen using the built in
function
error . It takes a string as an argument, which it appends to the error message. This is called
raising an exception. A useful built in function is
assert . It could
be defined as
assert = \(x,mesg) => x or error(mesg or "") end
Sometimes you need to do some cleaning up before raising an
error, such as closing a file or closing down a task. For this
purpose RiscLua provides a function
newtry . This takes a cleanup function as an argument and returns a
function that behaves like
assert but which calls
the cleanup function before raising an exception. If we write
try = newtry(cleanup)
then
try behaves like
try = \(x,mesg)
if x then => x end -- if
cleanup()
error(mesg or "")
end -- function
RiscLua also provides a function
protect . If
f is a function that does
not return a nil value then
protect(f) behaves exactly as
f does when no exceptions are raised, but returns a
nil value followed by an error message when one is.
When we later come to discuss how one calls software
interrupts (SWIs) in RiscLua, we have to advise that even though
the error-returning versions of SWIs are used, it is quite
possible for errors in SWIs to cause a crash. Standard Lua,
on the other hand, has been designed to be safe. Calling
SWIs is an inherently unsafe business, unfortunately.
For type-checking
the = \(s,x)
local t,fmt = type(x),"Expected a %s but got a %s"
=> t == s and x or error(fmt:format(s,t))
end -- function
is pretty. See its use below.
The symbol
... can be used as the
last parameter in a function value to denote a variable number
of parameters.
printf = \(s,...) =>
(the("string",s)):format(...) end -- function
Here is a function that returns the number of arguments it
is supplied with.
numvar = \(...) => select('#',...) end -- function
print(numvar(10,20,30,40)) --> 4
If the first argument of
select is a number, n, then
it returns all the arguments from the n-th onward.
Basic programmers are used to applying functions first and having
their definitions appear later in the program text. In Lua an
identifier's definition must precede its use. This means that to
understand a Lua program top-down you may have to read it backwards.
However, function definitions can be recursive.
fact1 = \(n)
assert(n > 0, "Bad argument for factorial")
if n == 0 then => 1 end -- if
=> n*fact1(n-1)
end -- function
There is a slight complication that you must beware of
when defining local functions recursively. Why does this not work?
fact2 = \(n)
assert(n > 0, "Bad argument for factorial")
local f = \(n,a)
if n == 0 then => a end -- if
=> f(n-1,n*a)
end -- function
=> f(n,1)
end -- function
The reason is that the second occurrence of
f in
the program is not in the scope of
f . Remember that a
local variable's scope does not begin until
after its declaration as local. The correct program is
fact2 = \(n)
assert(n > 0, "Bad argument for factorial")
local f
f = \(n,a)
if n == 0 then => a end -- if
=> f(n-1,n*a)
end -- function
=> f(n,1)
end -- function
In fact
fact2 is a better definition
of the factorial function than
fact1 . This is because
f is
tail-recursive - the function
f returns an
expression whose outermost level is an application of itself. The
Lua interpreter can exploit this fact to compile more efficient code
that uses a jump without having to save and restore state from the stack.
This is a standard optimization known from the early days of
programming language implementation.
As an exercise in distentangling scopes, here is an
old chestnut from the 1930s when mathematicians first
began to look into higher order functions.
Y = \(f)
local g = \(x) => x(x) end
=> g(\(x) => f(\(y)
local h = x(x)
=> h(y)
end)
end)
end
This function has a remarkable property: the expression
Y(f) evaluates to
f(Y(f)) . In other
words, it is a solution of the equation
f(x) == x
or a
fixed point of
f . Note that there
are two variables called
x in different scopes. Here is
a diagram showing how the scopes nest within each other. An
expression
((a,b,...)) denotes a scope in which
the variables
a,b,... are local.
((f,g))
|
---------------------------------
| |
((x)) ((x))
|
((y,h))
The function
fact1 above may be seen to be
a fixed point of the function
makefact = \ (f) => \ (n)
assert(n > 0, "Bad argument for factorial")
if n == 0 then => 1 end -- if
=> n*f(n-1)
end -- function
end -- function
That is to say
fact1 == makefact(fact1)
So we have a third, rather exotic, method of defining factorial
as the function
Y(makefact) . Note that
there is no recursion used here.