目录
注:本系列为《Lua程序设计-第4版》 的读书笔记,其中的见解有不完善的地方,可以在评论区指出,原版请看图书
例1.需求:假设有一个表,其中包含了学生的姓名和对应的成绩,如果我们想基于分数对学生姓名排序,分数高者在前
例4:由于函数第一类值的原因,函数可以保存在变量中,因此Lua语言可以重新定义函数或预定义函数
例5:创建沙盒环境(sandbox), 当执行一些诸如从远程服务器上下载到未受信任代码时。
注:本系列为《Lua程序设计-第4版》 的读书笔记,其中的见解有不完善的地方,可以在评论区指出,原版请看图书
"第一类值"和"词法定界"
在Lua语言中,函数是严格遵循词法定界的第一类值
"第一类值" 意味着Lua语言中的函数与其他常见类型的值(例如数值和字符串)具有同等权限:
一个程序可以将某个函数保存到变量中或表中,也可以将某个函数作为参数传递给其他函数,还可以将某个函数作为其他函数的返回值返回。
"词法定界" 意味着Lua语言中的函数可以访问包含其自身的外部函数中的变量(也意味着Lua语言完全支持Lambda演算①)
①Lambda演算是指Lua语言中的一个函数A可以嵌套在另一个函数B中,内部的函数A可以访问外部函数B中的声明的变量
这两特性给Lua语言带来了极大的灵活性。例如,一个程序可以通过重新定义函数来增加新功能,也可以通过擦除函数来为不受信任的代码创建一个安全的运行时环境
一. 函数是第一类值
a = {p = print} --> 'a.p'指向'print'函数
a.p("Hello World") --> Hello World
print = math.sin -- 'print'现在指向sine函数
a.p(print(1)) --> 0.8414709848079
math.sin = a.p -- 'sin'现在指向print函数
math.sin(10, 20) --> 10 20
1.1 函数构造器的创建:
如果函数也是值的话,那么是否有创建函数的表达式呢?答案是肯定的。
Lua语言中常见的函数定义方式如下:
function foo(x) return 2*x end
-- 换个写法(语法糖②)
foo = function(x) return 2*x end
②语法糖:指那些没有给语言添加新功能但对程序员来说更”甜蜜“的语法
赋值语句右边的表达式(function (x) body end)就是函数构造器,与表构造器{}相似。因此,函数定义实际上就是创建类型为”function“的值并把它赋值给一个变量的语句
在Lua中,所有函数都是匿名的,例如 print 只是保存该函数的变量。
1.2 高阶排序函数③-- table.sort
表标准库提供了函数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"}
}
table.sort(nerwork, function (a,b) return (a.name > b.name) end)
③高阶函数:利用匿名函数作为参数处理的函数
举例 table.sort 是它作为高阶函数,很好地把函数作为第一类值处理所带来结果的直接体现
二. 非全局函数
2.1 将函数存储在表字段中的示例:
---------------- 1 ---------------------
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
---------------- 2 ---------------------
Lib = {
foo = function (x,y) return x + y end,
goo = function (x,y) return x - y end
}
---------------- 3 ---------------------
Lib = {}
function Lib.foo (x,y) return x + y end
function Lib.goo (x,y) return x - y end
2.2 局部函数的定义
当把一个函数存储到局部变量时,就得到了一个局部函数,即一个被限定在指定作用域中使用的函数。
局部函数对于包(package)的意义: 由于Lua语言将每个程序段作为一个函数处理,所以在一段程序中声明的函数就是局部函数,只在该程序段中可见。词法定界保证了程序段中其他函数可以使用这些局部函数(一个程序段类似于 表,该表中的函数可以相互调用)
local function f(params)
body
end
-- 展开--
local f; f = function (params) body end
三 词法定界
当编写一个被其他函数B包含的函数A时,被包含的函数A可以访问包含其的函数B的所有局部变量,这种特性被称为词法定界:
function B()
x = 1
y = function (x) return x + 1 end -- > y = 2
end
词法定界外加嵌套的第一类值函数可以为编程语言提供强大的功能
例1.需求:假设有一个表,其中包含了学生的姓名和对应的成绩,如果我们想基于分数对学生姓名排序,分数高者在前
names = {"Peter", "Paul", "Mary"}
grades = {Mary = 10, Paul = 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即不是全局变量也不是局部变量,即 非局部变量(也称为 上值(upvalue))
3.1 闭包的概念及应用(先了解上面的,再看闭包)
例2. 考虑如下代码
function newCounter()
local count = 0
return function()
count = count + 1
return count
end
end
c1 = newCounter() -- 函数已返回,变量count是否超出作用范围??
print(c1())
print(c1())
-- 新建一个变量再次调用newCounter----
--- 再次调用newCounter ,那么一个新的局部变量count 和一个新的闭包会被创建出来。新的闭包针对的这个新变量,所有C1 和 c2是不同的闭包,它们建立在相同的函数上,但拥有各自的局部变量count
c2 = newCounter()
print(c2()) --> 1
print(c1()) --> 3
print(c2()) --> 2
在上述代码中,匿名函数访问了一个非局部变量(count)并将其当作计数器。然而,由于创建变量的函数(newCounter)已经返回,因此我们调用匿名函数时,变量count似乎已经超出了作用范围,但是由于闭包概念的存在,Lua语言能够正确地应对这种情况。
由于函数是严格遵循词法定界的第一类值,这两特性的结合使用衍生出闭包的概念。闭包(closure)是由一个函数和该函数会访问到的非局部变量(或上值 upvalue)组成,即上述中newCounter函数的匿名函数 和 非局部变量count 共同组成了一个闭包函数。
闭包的组成:newCounter + 闭包函数 + 非局部变量Count
闭包的含义:一个函数外加能够使该函数正确访问非局部变量所需的其他机制(简单的说,就是能够让外部函数访问到非局部变量)
闭包的应用:
1. 作为 高阶函数的参数,比如像 table.sort函数的参数
2. 函数返回一个闭包
3. 闭包在回调函数的应用。典型的例子就是界面上的按钮的回调(下面的例子会说)
4. 创建一个安全的运行环境(沙盒),当执行一些未受信任的代码时。
例3. 闭包在回调函数的应用
需求:假设有一个具有10个类似按钮的数字计算器(每一个按钮代表一个十进制数字)
function digitButton(digit)
return Button{
lable = tostring(digit), -- lable是按钮的标签
action = function() -- action 是当按钮按下时被调用的回调函数
add_to_display(digit)
end
}
end
上述示例中,假设Button 是一个创建新按钮的工具箱函数,lable是按钮的标签,action 是当按钮按下时被调用的回调函数。回调可能发生在函数digitButton 早就执行完后,那时digit 已经超出作用范围,但闭包仍然可以访问它。
例4:由于函数第一类值的原因,函数可以保存在变量中,因此Lua语言可以重新定义函数或预定义函数
需求:重新定义函数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函数,其他部分代码无法访问
例5:创建沙盒环境(sandbox), 当执行一些诸如从远程服务器上下载到未受信任代码时。
-- 通过闭包重定义函数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 -- 只有符合函数access_ok 的文件才能被加载
return oldOpen(filename, mode)
else
return nil, "access denied"
end
end
end
四. 函数式编程小式
例6
需求:需要搭建一个表示任意区域的系统,我们能够利用该系统表示各种各样的图形,同时可以通过多种方式(旋转、并集、变换 )组合和修改这些图形
鉴于一个几何区域就是点的集合,因此可以通过特征函数来表示一个区域,即可以提供一个点/坐标并根据点是否属于该区域而返回真或者假的函数来表示一个区域
-- 下面函数表示一个点(1.0, 3.0)为圆心、半径为4.5的圆盘
function disk1(x, y)
return (x - 1.0)^2 + (y - 3.0)
end
-- 利用高级函数和词法定界,定义一个指定圆心和半径创建圆盘的工厂 --
function disk(cx, cy, r)
return function(x, y)
return (x - cx)^2 + (y - cy)^2 <= r^2
end
end
-- 上述的示例中 返回了一个闭包的形式,这样的好处可以 预定义一个指定圆盘,再通过闭包返回结果
-- initDisk = disk(1.0, 3.0, 4) -- 创建一个 点(1.0, 3.0),半径为4的圆
-- isDisk = initDisk(2.0, 2.0) -- 判断点(2.0, 2.0)是否属于 范围内
-- 创建一个指定边界的轴对称图形
function rect(left, right, top, bottom)
return function(x, y)
return left <= x and x <= right and bottom <= y and y <= top
end
end
-- 创建任何区域的补集
function complement(r)
return function(x, y)
return not r(x, y)
end
end
-- 创建任何区域的并集
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