本文按照Lua4.0.1进行编写
Lua 中的函数是带有词法定界(lexical scoping)的第一类值(First-Class Values)。
第一类值意为, Lua 中的函数是一个值,如同传统的数值或字符串值一样。函数可以被存入局部或全局变量中,可以存入 Lua 表中,可以作为其他函数的参数被传递,还可以作为其他函数的返回值。
词法定界意为, Lua 函数可以存取其函数体包含的范围内的变量,同时也意为着 Lua 正确地包含了λ -Calculus(嵌套的函数可以访问它外部函数中的变量)。在稍后的章节我们将看到,这显然无害的特性给 Lua 带来强大的功能,因为它允许我们在 Lua 中应用来自于函数型语言的多种强大的编程技术。就算你对函数型编程毫无兴趣,了解一下如何开发这些技术是值得的,因为它们将令你的程序看起来更加简洁。
在 Lua 中,一个有关函数的难解的概念是,与其他类型的值一样,函数也是匿名的,它们并没有名字。当我们提到函数名(比如 print),实际上是指一个持有函数功能的变量,因此我们可以像操纵其他变量那样操纵函数变量。下面一个看起来有点傻的例子展示了这一概念:
a = {p = print}
a.p("Hello World") --> Hello World
print = sin -- 'print' 现在引用了正弦函数
a.p(print(1)) --> 0.01745240643728351
sin = a.p -- 'sin' 现在引用了print函数
sin(10, 20) --> 10 20
稍后我们还将看到更有用的相关应用。
既然函数是值,那么使用表达式也应该可以创建函数。实际上,我们在 Lua 中经常这样写:
function foo(x) return 2 * x end
这实际上是 Lua 中语法的特例,换句话说,下面的写法相对来说更为漂亮:
foo = function(x) return 2 * x end
实际上,函数定义是一个语句,更确切地说,是一个将类型为 Function 的值赋给一个变量得赋值语句。我们可以将表达式 function(x) ... end 视为一个函数构造器,就像使用{}作为一个表构造器一样。我们称这种函数构造器的结果为匿名函数,尽管我们经常用一个全局的名字来命名函数,但是在很多情况下我们使用函数会选择匿名的方式。让我们先看几个例子。
Lua 的 table 标准库提供一个名为 table.sort 的排序函数(LUA4.0.1为sort),它接受一个表作为参数并排序其中的元素。类似这种函数必须能够提供多种不同的排序方式:升序还是降序、数值顺序或者字母顺序、是否按照表的索引来排序,等等。 Lua 在 table.sort 中提供了一个可选的参数(它接受一个排序函数作为参数值),而不是直接提供所有可能的排序函数。这个作为参数存在地排序函数接受两个参数,并返回一个布尔值来指示在排序时两个参数中到底哪个应该排在另一个的前面。假设存在一个如下所示的记录表:
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"},
}
如果我们想按照字母降序的方式,对该表的 name 域进行排序,那么我们只需要像下面这样做:
sort(network, function(a,b)
return (a.name > b.name)
end)
-- 打印排序后的结果
for _, v in network do
print(v.name, v.IP)
end
你可以发现上例中使用匿名函数是如此的便利。
以其他函数作为参数的函数被称作高级函数(Higher-Order Function),如上面的提及的 table.sort。高级函数是一种强有力的编程机制,同时使用匿名函数作为它们的参数提供了很大的便利。但在 Lua 中,高级函数与普通函数没有特别的不同之处,它们的出现只不过是 Lua 将函数作为第一类值来对待的自然结果。
pi = 3.141592653589793
function eraseTerminal()
write("\27[2J")
end
-- writes an '*' at column 'x' , 'row y'
function mark(x, y)
write(format("\27[%d;%dH*", y, x))
end
-- Terminal size
TermSize = {w = 80, h = 24}
-- plot a function
-- assume that domain and image are in the range [-1,1]
function plot(f)
eraseTerminal()
for i = 1, TermSize.w do
local x = (i / TermSize.w) * 2 - 1
local y = (f(x) + 1) / 2 * TermSize.h
mark(i, y)
end
read() -- wait before spoiling the screen
end
函数定义完成后,你可以通过它来绘制正弦函数的图形:
plot(function(x) return math.sin(x * 2 * math.pi) end)
当我们调用 plot 函数,匿名函数将被赋值给它的参数 f,之后 f 参数对应的函数将在 for 循环中被重复调用,以完成绘图的工作。
因为函数在 Lua 中是第一类值,我们可以将其存入全局变量、局部变量或表域中。之后我们会看到,将函数存入表域是某些高级应用的前提,比如 Lua 包以及面相对象的编程。
|