第一版是为 Lua 5.0 编写的。虽然在很大程度上仍然适用于后续版本,但有一些差别。
第四版针对 Lua 5.3,可在 Amazon 和其他书店购买。
购买本书,您还可以帮助 支持 Lua 项目


6 – 更多关于函数

Lua 中的函数是具有适当词法作用域的一等值。

函数成为“一等值”意味着什么?这意味着在 Lua 中,函数是一个值,与数字和字符串等常规值享有同等权利。函数可以存储在变量(全局和局部变量)和表中,可以作为参数传递,还可以由其他函数返回。

函数具有“词法作用域”意味着什么?这意味着函数可以访问其封闭函数的变量。(这也意味着 Lua 恰当地包含了 lambda 演算。)正如我们在本章中所见,这个看似无害的属性为该语言带来了强大的功能,因为它允许我们在 Lua 中应用函数式语言世界中的许多强大编程技术。即使您对函数式编程完全不感兴趣,也值得了解一下如何探索这些技术,因为它们可以使您的程序更小更简单。

Lua 中一个有点困难的概念是函数与所有其他值一样都是匿名的;它们没有名称。当我们谈论函数名称(例如 print)时,我们实际上是在谈论保存该函数的变量。与保存任何其他值的任何其他变量一样,我们可以通过多种方式操作此类变量。以下示例虽然有点可笑,但说明了这一点

    a = {p = print}
    a.p("Hello World") --> Hello World
    print = math.sin  -- `print' now refers to the sine function
    a.p(print(1))     --> 0.841470
    sin = a.p         -- `sin' now refers to the print function
    sin(10, 20)       --> 10      20
稍后我们将看到此功能的更多有用应用。

如果函数是值,那么是否有任何表达式可以创建函数?是的。事实上,在 Lua 中编写函数的通常方法,如下所示

    function foo (x) return 2*x end
只是我们称之为语法糖的一个实例;换句话说,它只是编写以下内容的一种漂亮方式
    foo = function (x) return 2*x end
也就是说,函数定义实际上是一个语句(更具体地说,是一个赋值),它将类型为"function"的值分配给一个变量。我们可以将表达式function (x) ... end视为一个函数构造器,就像{}是一个表构造器一样。我们将这种函数构造器的结果称为匿名函数。尽管我们通常将函数分配给全局名称,给它们一个类似于名称的东西,但在某些情况下,函数仍然是匿名的。让我们看一些例子。

表库提供了一个函数table.sort,它接收一个表并对其元素进行排序。此类函数必须允许对排序顺序进行无限变化:升序或降序、数字或字母顺序、按键排序的表,等等。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字段对表进行逆字母顺序排序,我们只需编写
    table.sort(network, function (a,b)
      return (a.name > b.name)
    end)
请看匿名函数在该语句中有多么方便。

将另一个函数作为参数获取的函数(例如sort)是我们所说的高阶函数。高阶函数是一种强大的编程机制,使用匿名函数来创建其函数参数是一种极大的灵活性来源。但请记住,高阶函数没有任何特殊权利;它们只是 Lua 能够将函数作为一等值来处理的一个简单结果。

为了说明将函数用作参数,我们将编写一个常见的高阶函数plot的一个朴素实现,它绘制一个数学函数。下面我们展示了此实现,使用一些转义序列在 ANSI 终端上绘制。(您可能需要更改这些控制序列以使此代码适应您的终端类型。)

    function eraseTerminal ()
      io.write("\27[2J")
    end
    -- writes an `*' at column `x' , row `y'
    function mark (x,y)
      io.write(string.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
      io.read()  -- wait before spoiling the screen
    end
有了该定义,您可以使用类似这样的调用来绘制正弦函数
    plot(function (x) return math.sin(x*2*math.pi) end)
(我们需要稍微整理一下数据,以将值放入适当的范围内。)当我们调用plot时,其参数f获取给定匿名函数的值,然后在for循环中重复调用该值以提供绘图的值。

由于函数在 Lua 中是一等值,因此我们不仅可以将它们存储在全局变量中,还可以存储在局部变量和表字段中。正如我们稍后将看到的,在表字段中使用函数是 Lua 的一些高级用法(例如包和面向对象编程)的关键要素。