9 闭包

在 Lua 语言中,函数是严格遵循词法定界的第一类值。
“第一类值” 意味着 Lua 语言中的函数与其他常见类型的值具有同等权限:一个程序可以将某个函数保存到变量中或表中,也可以将某个函数作为参数传递给其他函数,还可以将某个函数作为其他函数的返回值返回。
”词法定界“ 意味着 Lua 语言中的函数可以访问包含其自身的外部函数中的变量(也意味着 Lua 语言完全支持 Lambda 演算)。
上述两个特性联合起来为 Lua 语言带来了极大的灵活性。例如,一个程序可以通过重新定义函数来增加新功能,也可以通过擦除函数来为不受信任的代码(例如通过网络接收到的代码)创建一个安全的运行时环境。更重要的是,上述两个特性允许我们在 Lua 语言中使用很多函数式语言的强大编程技巧。即使对函数式编程毫无兴趣,也不妨学习一下如何探索这些技巧,因为这些技巧可以使程序变得更加小巧和简单。


9.1 函数是第一类值

如前所述,Lua 语言中的函数是第一类值。以下的示例演示了第一类值的含义:

a = {p = print}
a.p("Hello World")
print = math.sin
a.p(print(1))
math.sin = a.p
math.sin(10, 20)

如果函数也是值的话,那么是否有创建函数的表达式呢?答案是肯定的。事实上,Lua 语言中常见的函数定义方式如下:

function foo(x) return 2*x end

就是所谓语法糖的例子,它只是下面这种写法的一种美化形式:

foo = function (x) return 2*x end

赋值语句右边的表达式(function (x) body end)就是函数构造器,与表构造器 {} 类似。因此,函数定义实际上就是创建类型为 "function" 的值并把它赋值给一个变量的语句。
注意,在 Lua 语言中,所有的函数都是匿名的。像其他所有的值一样,函数并没有名字。当讨论函数名时,比如 print,实际上指的是保存该函数的变量。虽然我们通常会把函数赋值给全局变量,从而看似给函数起了一个名字。但在很多场景下仍然会保留函数的匿名性。下面来看几个例子。
表标准库提供了函数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 语言将函数作为第一类值处理所带来的结果的直接体现。
为了进一步演示高阶函数的用法,让我们再来实现一个常见的高阶函数,即导数。按照通常的定义,函数 f 的导数为 f'(x) = (f(x + d) - f(x))/d,其中 d 趋向于无穷小。根据这个定义,可以用如下方式近似地计算导数:

---@return function
---@param f function
---@param delta number
function derivative(f, delta)
    delta = delta or 1e-4
    return function (x)
        return (f(x + delta) - f(x)) / delta
    end
end

对于指定的函数 f,调用 derivative(f) 将(近似地)返回其导数,也就是另一个函数:

c = derivative(math.sin)
print(math.cos(5.2), c(5.2))
print(math.cos(10), c(10))

9.2 非全局函数

由于函数是一种”第一类值“,因此一个显而易见的结果就是:函数不仅可以被存储在全局变量中,还可以被存储在表字段和局部变量中。
我们已经在前面的章节中见到过几个将函数存储在表字段中的示例,大部分 Lua 语言的库就采用了这种机制(例如io.readmath.sin)。如果要在 Lua 语言中创建这种函数,只需将到目前为止我们所学到的知识结合起来:

Lib = {}
Lib.foo = function (x, y) return x + y end
Lib.goo = funtion(x, y) return x - y end

print(Lib.foo(2, 3), Lib.goo(2, 3))

当然,也可以使用表构造器:

Lib = {
    foo = function (x, y) return x + y end,
    goo = function (x, y) return x - y end,
}

除此之外,Lua 语言还提供了另一种特殊的语法来定义这类函数:

Lib = {}
function Lib.foo(x, y) return x + y end
function Lib.goo(x, y) return x - y end

正如我们将在第 21 章中看到的,在表字段中存储函数是 Lua 语言中实现面向对象编程的关键要素。
当把一个函数存储到局部变量时,就得到了一个局部函数,即一个被限定在指定作用域中使用的函数。局部函数对于包而言尤其有用:由于 Lua 语言将每个程序段作为一个函数处理,所以在一段程序中声明的函数就是局部函数,这些局部函数只在该程序段中可见。词法定界保证了程序段中的其他函数可以使用这些局部函数。
对于这种局部函数的使用,Lua 语言提供了一种语法糖:

local function f(params)
  body
end

在定义局部递归函数时,由于原来的方法不适用,所以有一点是极易出错的。考虑如下代码:

local fact = function(n)
    if(n == 0) then return 1
    else return n * fact(n - 1)
    end
end

当 Lua 语言编译函数体中的 fact(n - 1) 调用时,局部的 fact 尚未定义。因此,这个表达式会尝试调用全局的 fact 而非局部的 fact。我们可以通过先定义局部变量再定义函数的方式来解决这个问题:

local fact
fact = function (n)
    if(n == 0) then return 1
    else return n * fact(n-1)
    end
end

这样,函数内的 fact 指向的是局部变量。尽管在定义函数时,这个局部变量尚未确定,但到了执行函数时,fact 肯定已经有了正确的赋值。
当 Lua 语言展开局部函数的语法糖时,使用的并不是之前的基本函数定义。相反,形如

local function foo(params) body end

的定义会被展开成

local foo; foo = function (params) body end

因此,使用这种语法来定义递归函数不会有问题。
当然,这个技巧对于间接递归函数是无效的。在间接递归的情况下,必须使用与明确的前向声明等价的形式:

local f
local function g()
    --some code
    --f()
    --some code
end

function f()
    --some code
    --g()
    --some code
end

请注意,不能在最后一个函数定义前加上 local。否则,Lua 语言会创建一个全新的局部变量 f,从而使得先前声明的 f 变为未定义状态。


9.3 词法定界

当编写一个被其他函数 B 包含的函数 A 时,被包含的函数 A 可以访问其他函数 B 的所有局部变量,我们将这种特性称为词法定界。虽然这种可见性规则听上去很明确,但实际上并非如此。词法定界外加嵌套的第一类值函数可以为编程语言提供强大的功能,但很多编程语言并不支持这两者组合使用。
先看一个简单的例子。假设有一个表,其中包含了学生的姓名和对应的成绩,如果我们想基于分数对学生姓名排序,分数高者在前,那么可以使用如下的代码完成上述需求:

names = {"Peter", "Paul", "Mary"}
grades = {Mary = 10, Pual = 7, Peter = 8}
table.sort(names, function(n1, n2)
    return grades[n1] > grades[n2]
end
)

现在,假设我们想创建一个函数来完成这个需求:

function sortbygrade(names, grades)
    table.sort(names, function (n1, n2)
        return grades[n1] > grades[n2]
    end)
end

在后一个示例中,有趣的一点就在传给函数 sort 的匿名函数可以访问 grades,而 grades 时包含匿名函数的外层函数 sortbygrade 的形参。在该匿名函数中,grades 既不是全局变量也不是局部变量,而是我们所说的非局部变量。(由于历史原因,在 Lua 语言中非全局变量也被称为 Up-value)。
这一点之所以如此有趣是因为,函数作为第一类值,能够逃逸出它们变量的原始定界范围。考虑如下的代码:

function newCounter()
    local count = 0
    return function ()
        count = count + 1
        return count
    end
end

c1 = newCounter()
print(c1())    -->    1
print(c1())    -->    2

在上述代码中,匿名函数访问了一个非局部变量 count 并将其当作计数器。然而,由于创建变量的函数已经返回,因此当我们调用匿名函数时,变量 count 似乎已经超出了作用范围。但其实不然,由于闭包概念的存在,Lua 语言能够正确地应对这种情况。简单地说,一个闭包就是一个函数外加能够使该函数正确访问非局部变量所需的其他机制。如果我们再次调用 newCounter,那么一个新的局部变量 count 和一个新的闭包会被创建出来,这个新的闭包针对的是这个新变量:

c2 = newCounter()
print(c2())    -->    1
print(c1())    -->    3
print(c2())    -->    2

因此,c1 和 c2 是不同的闭包。它们建立在相同的函数之上,但是格子拥有局部变量的独立实例。

从技术上讲,Lua 语言中只有闭包而没有函数。函数本身只是闭包的一种原型。不过尽管如此,只要不会引起混淆,我们就仍将使用术语”函数“来指代闭包。
闭包在许多场合中均是一种有价值的工具。正如我们之前已经见到过的,闭包在作为诸如 sort 这样的高阶函数的参数时就非常有用。同样,闭包对于那些创建了其他函数的函数也很有用,例如我们之前的 newCounter 示例及求导数的示例;这种机制使得 Lua 程序能够综合运用函数式编程世界中多种精妙的编程技巧。另外,闭包对于回调函数来说也很有用。对于回调函数而言,一个典型的例子就是在传统 GUI 工具箱中创建按钮。每个按钮通常都对应一个回调函数,当用户按下按钮时,完成不同的处理动作的回调函数就会被调用。
例如,假设有一个具有 10 个类似按钮的数字计算器,我们就可以使用如下的函数来创建这些按钮:

function digitButton(digit)
    return Button{
        label = tostring(digit),
        action = function ()
            add_to_display(digit)
        end
    }
end

在上述示例中,假设 Button 是一个创建新按钮的工具箱函数,label 是按钮的标签,action 是当按钮按下时被调用的回调函数。回调可能发生在函数 digitButton 早已执行完后,那时变量 digit 已经超出了作用范围,但闭包仍可以访问它。
闭包在另一种很不一样的场景下也非常有用。由于函数可以被保存在普通变量中,因此在 Lua 语言中可以轻松地重新定义函数,甚至是预定义函数。这种机制也正是 Lua 语言灵活地原因之一。通常,当重新定义一个函数的时候,我们需要在新的实现中调用原来的那个函数。例如,假设要重新定义函数 sin 以使其参数以角度为单位而不是以弧度为单位。那么这个新函数就可以先对参数进行转换,然后再调用原来的 sin 函数进行真正的计算。代码可能形如:

local oldSin = math.sin
math.sin = function (x)
    return oldSin(x * (math.pi / 180))
end

另一种更清晰一点的完成重新定义的写法是:

do
    local oldSin = math.sin
    local k = math.pi / 180
    math.sin = function (x)
        return oldSin(x * k)
    end
end

上述代码使用了 do 代码段来限制局部变量 oldSin 的作用范围;根据可见性规则,局部变量 oldSin 只在这部分代码段中有效。因此,只有新版本的函数 sin 才能访问原来的sin 函数,其他部分的代码则访问不了。
我们可以使用同样的技巧来创建安全的运行时环境,即所谓的沙盒。当执行一些诸如从远程服务器上下载到的未受信任代码时,安全的运行环境非常重要。例如,我们可以通过使用闭包重定义函数 io.open 来限制一个程序能够访问的文件:

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 

上述示例的巧妙之处在于,在经过重新定义后,一个程序就只能通过新的受限版本来调用原来未受限版本的 io.open 函数。示例代码将原来不安全的版本保存为一个闭包的一个私有变量,该变量无法从外部访问。通过这一技巧,就可以保证简洁性和灵活性的前提下在 Lua 语言本身上构建 Lua 沙盒。相对于提供一套大而全的解决方案,Lua 语言提供的是一套”元机制“,借助这种机制可以根据特定的安全需求来裁剪具体的运行时环境。


9.4 小试函数式编程

再举一个函数式编程的具体示例。在本节中我们要开发一个用来表示几何区域的简单系统,其中取余即为点的集合。我们希望能够利用该系统表示各种各样的图形,同时可以通过多种方式组合和修改这些图形。
为了实现这样的一个系统,首先需要找到表示这些图形的合理数据结构。我们可以尝试着使用面向对象的方案,利用继承来抽象某些图形;或者,也可以直接利用特征函数来进行更高层次的抽象。鉴于一个几何区域就是点的集合,因此可以通过特征函数来表示一个区域,即可以提供一个点并根据点是否属于指定区域而返回真或假的函数来表示一个区域。
举例来说,下面的函数表示一个以点为圆心、半径 4.5 的圆盘:

function disk1(x, y)
    return (x - 1.0)^2 + (y - 3.0)^2 <= 4.5^2
end

利用高阶函数和词法定界,可以很容易地定义一个根据指定的圆心或半径创建工厂:

function disk(cx, cy, r)
    return function (x, y)
        return (x - cx)^2 + (y - cy)^2 <= r^2
    end
end

形如disk(1.0, 3.0, 4.5)的调用会创建一个与 disk1 等价的圆盘。
下面的函数创建了一个指定边界的轴对称矩形:

function rect(left, right, bottom, up)
    return function (x, y)
        return left <= x and x <= right and bottom <= y and y <= up
    end
end

注意到,这些函数的返回值都是 boolean 类型,所以推测回值方法是通过两个 for 传入需要绘制的区域坐标,然后调用函数进行筛选,根据返回的 true 或者 false 对相应的坐标进行绘制或者不绘制,从而得到我们需要的图形

按照类似的方式,可以定义函数以创建诸如三角形或非轴对称矩形等其他基本图形。每一种图形都具有完全独立的实现,所需的仅仅是一个正确的特征函数。
接下来让我们考虑一下如何改变和组合区域。我们可以很容易地创建任何区域的补集:

function complement(r)
    return function(x, y)
        return not r(x, y)
    end
end

并集、交集和差集也很简单。参见示例 9.1。

示例 9.1 区域的并集、交集和差集

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

以下函数按照特定的增量平移指定的区域:

function translate(r, dx, dy)
    return function(x, y)
        return r(x - dx, y - dy)
    end
end

为了使一个区域可视化,我们可以遍历每个像素进行视口测试;位于区域内的像素被绘制为黑色,而位于区域外的像素被绘制为白色。为了用简单的方式演示这个过程,我们接下来写一个函数来生成一个 PBM 格式的文件来绘制指定的区域。
PBM 文件的结构很简单。PBM 文件的文本形式以字符串“P1”开头,接下来的一行是图片的宽和高,然后是对应每一个像素、由 1 和 0 组成的数字序列(黑为1,白为0,数字和数字之间由可选的空格分开),最后是 EOF。示例 9.2 中的函数 plot 创建了指定区域的 PBM 文件,并将虚拟绘制区域 (-1, 1], [-1, 1) 映射到视口区域 [1, M], [1, N]中。

示例 9.2 在 PBM 文件中绘制区域

function plot(r, M, N)
    io.write("P1\n", M, " ", N, "\n")   --文件头

    for i = 1, N do
        local y = (N - i * 2) / N
        for j = 1, M do
            local x = (j * 2 - M) / M
            io.write(r(x, y) and "1" or "0")
        end
        io.write("\n")
    end
end

为了让示例更完整,以下的代码绘制了一个南半球所能看到的峨眉月:

function disk(cx, cy, r)
    return function (x, y)
        return (x - cx)^2 + (y - cy)^2 <= r^2
    end
end

function plot(r, M, N)
    io.write("P1\n", M, " ", N, "\n")   --文件头

    for i = 1, N do
        local y = (N - i * 2) / N
        for j = 1, M do
            local x = (j * 2 - M) / M
            io.write(r(x, y) and "1" or "0")
        end
        io.write("\n")
    end
end

function difference (r1, r2)
    return function (x, y)
        return r1(x, y) and not r2(x, y)
    end
end

function translate(r, dx, dy)
    return function(x, y)
        return r(x - dx, y - dy)
    end
end

c1 = disk(0, 0,1)
plot(difference(c1, translate(c1, 0.3, 0)), 100, 100)

结果为:

lua.exe "C:/Users/qw/Documents/Lua Scripts/test.lua"
P1
100 100
0000000000000000000000000000000000000000111111111111111000000000000000000000000000000000000000000000
0000000000000000000000000000000000011111111111111100000000000000000000000000000000000000000000000000
0000000000000000000000000000000011111111111111100000000000000000000000000000000000000000000000000000
0000000000000000000000000000001111111111111110000000000000000000000000000000000000000000000000000000
0000000000000000000000000000111111111111111000000000000000000000000000000000000000000000000000000000
0000000000000000000000000011111111111111100000000000000000000000000000000000000000000000000000000000
0000000000000000000000001111111111111110000000000000000000000000000000000000000000000000000000000000
0000000000000000000000111111111111111000000000000000000000000000000000000000000000000000000000000000
0000000000000000000001111111111111110000000000000000000000000000000000000000000000000000000000000000
0000000000000000000111111111111111000000000000000000000000000000000000000000000000000000000000000000
0000000000000000001111111111111110000000000000000000000000000000000000000000000000000000000000000000
0000000000000000011111111111111100000000000000000000000000000000000000000000000000000000000000000000
0000000000000000111111111111111000000000000000000000000000000000000000000000000000000000000000000000
0000000000000001111111111111110000000000000000000000000000000000000000000000000000000000000000000000
0000000000000011111111111111100000000000000000000000000000000000000000000000000000000000000000000000
0000000000000111111111111111000000000000000000000000000000000000000000000000000000000000000000000000
0000000000001111111111111110000000000000000000000000000000000000000000000000000000000000000000000000
0000000000011111111111111100000000000000000000000000000000000000000000000000000000000000000000000000
0000000000111111111111111000000000000000000000000000000000000000000000000000000000000000000000000000
0000000001111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000
0000000001111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000
0000000011111111111111100000000000000000000000000000000000000000000000000000000000000000000000000000
0000000111111111111111000000000000000000000000000000000000000000000000000000000000000000000000000000
0000000111111111111111000000000000000000000000000000000000000000000000000000000000000000000000000000
0000001111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000000
0000001111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000000
0000011111111111111100000000000000000000000000000000000000000000000000000000000000000000000000000000
0000011111111111111100000000000000000000000000000000000000000000000000000000000000000000000000000000
0000111111111111111000000000000000000000000000000000000000000000000000000000000000000000000000000000
0000111111111111111000000000000000000000000000000000000000000000000000000000000000000000000000000000
0001111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000000000
0001111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000000000
0011111111111111100000000000000000000000000000000000000000000000000000000000000000000000000000000000
0011111111111111100000000000000000000000000000000000000000000000000000000000000000000000000000000000
0011111111111111100000000000000000000000000000000000000000000000000000000000000000000000000000000000
0111111111111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000
0111111111111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000
0111111111111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000
0111111111111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000
0111111111111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000
1111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000
1111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000
1111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000
1111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000
1111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000
1111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000
1111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000
1111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000
1111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000
1111111111111100000000000000000000000000000000000000000000000000000000000000000000000000000000000000
1111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000
1111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000
1111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000
1111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000
1111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000
1111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000
1111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000
1111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000
1111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000000000000
0111111111111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000
0111111111111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000
0111111111111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000
0111111111111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000
0111111111111111000000000000000000000000000000000000000000000000000000000000000000000000000000000000
0011111111111111100000000000000000000000000000000000000000000000000000000000000000000000000000000000
0011111111111111100000000000000000000000000000000000000000000000000000000000000000000000000000000000
0011111111111111100000000000000000000000000000000000000000000000000000000000000000000000000000000000
0001111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000000000
0001111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000000000
0000111111111111111000000000000000000000000000000000000000000000000000000000000000000000000000000000
0000111111111111111000000000000000000000000000000000000000000000000000000000000000000000000000000000
0000011111111111111100000000000000000000000000000000000000000000000000000000000000000000000000000000
0000011111111111111100000000000000000000000000000000000000000000000000000000000000000000000000000000
0000001111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000000
0000001111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000000
0000000111111111111111000000000000000000000000000000000000000000000000000000000000000000000000000000
0000000111111111111111000000000000000000000000000000000000000000000000000000000000000000000000000000
0000000011111111111111100000000000000000000000000000000000000000000000000000000000000000000000000000
0000000001111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000
0000000001111111111111110000000000000000000000000000000000000000000000000000000000000000000000000000
0000000000111111111111111000000000000000000000000000000000000000000000000000000000000000000000000000
0000000000011111111111111100000000000000000000000000000000000000000000000000000000000000000000000000
0000000000001111111111111110000000000000000000000000000000000000000000000000000000000000000000000000
0000000000000111111111111111000000000000000000000000000000000000000000000000000000000000000000000000
0000000000000011111111111111100000000000000000000000000000000000000000000000000000000000000000000000
0000000000000001111111111111110000000000000000000000000000000000000000000000000000000000000000000000
0000000000000000111111111111111000000000000000000000000000000000000000000000000000000000000000000000
0000000000000000011111111111111100000000000000000000000000000000000000000000000000000000000000000000
0000000000000000001111111111111110000000000000000000000000000000000000000000000000000000000000000000
0000000000000000000111111111111111000000000000000000000000000000000000000000000000000000000000000000
0000000000000000000001111111111111110000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000111111111111111000000000000000000000000000000000000000000000000000000000000000
0000000000000000000000001111111111111110000000000000000000000000000000000000000000000000000000000000
0000000000000000000000000011111111111111100000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000111111111111111000000000000000000000000000000000000000000000000000000000
0000000000000000000000000000001111111111111110000000000000000000000000000000000000000000000000000000
0000000000000000000000000000000011111111111111100000000000000000000000000000000000000000000000000000
0000000000000000000000000000000000011111111111111100000000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000111111111111111000000000000000000000000000000000000000000000
0000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000

9.5 练习

  • 练习 9.1:请编写一个函数 integral,该函数以一个函数 f 为参数并返回其积分的近似值。

积分的含义就是图像与 x 轴在某个范围内围成的面积,在 delta 很小的时候,可以近似认为围成的形状是一个矩形,因此只需要使用 f(x) 的值与 delta 做乘积就得到了 delta 小段的面积,累加起来以后就是积分的一个近似值。

function integral(f,delta)
    delta = delta or 1e-4
    return function(beginning, ending)
        local area = 0
        for i = beginning, ending, delta do
            area = area + math.abs(f(i) * delta)
        end
        return area
    end
end

c = integral(math.sin)
print("Integral from 0 to 2π is " .. c(0, 2 * math.pi))
  • 练习 9.2:请问如下的代码段将输出怎样的结果:
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())

这有点类似 getter 和 setter 方法,对于不同的对象调用get或者set来获取或修改某些属性的值。

  • 练习 9.3:练习 5.4 要求我们以一个多项式和值 x 为参数、返回结果为对应多项式值的函数。请编写该函数的柯里化版本,该版本的函数应该以一个多项式为参数并返回另一个函数(当这个函数的入参是 x 时返回对应多项式的值)。考虑如下的示例:
function newpoly(coefficient)
    return function(x)
        local sum = 0
        for k, v in ipairs(coefficient) do
            sum = sum + v * x ^(k - 1)
        end
        return sum
    end
end

f = newpoly({3, 0, 1})  --表中靠前的数据阶数低
print(f(0))
print(f(5))
print(f(10))
  • 练习 9.4:使用几何区域系统的例子,绘制一个北半球所能看到的峨眉月。

我不知道什么叫北半球看到的峨眉月,所以就算了

  • 练习9.5:在集合区域系统的例子中,增加一个函数来实现将指定的区域旋转指定的角度。

比较难 以后有机会再补上。

你可能感兴趣的:(9 闭包)