分享
 
 
 

Programming in Lua翻译--More about Functions

王朝other·作者佚名  2006-01-09
窄屏简体版  字體: |||超大  

原文参考: http://www.lua.org/pil/index.html

翻译本文章是个人爱好Lua所至,转载请注明出处和作者.版权归原作者所有,未经允许不得将文章用于商业目的,否则造成的一切后果由该组织或个人承担,本人不承担任何法律及连带责任.请自觉遵守.

6.More about Functions

Lua中的函数是带有词法定界(lexical scoping)的第一类值(first-class values).

第一类值指:在Lua中函数和其他值(数值,字符串)一样,函数可以被存放在变量中,也可以存放在表中,可以作为函数的参数,还可以作为函数的返回值.

词法定界指:函数可以访问他内部嵌套的函数中的变量.这一特性给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

这实际上是利用Lua提供的"语法上的甜头"(syntactic sugar)的结果,下面是原本的函数:

foo = function (x) return 2*x end

函数定义实际上是一个赋值语句,将类型为function的变量赋给一个变量.我们使用function (x) ... end 来定义一个函数和使用{}创建一个表一样.

table标准库提供一个排序函数,接受一个表作为输入参数并且排序表中的元素.这个函数必须能够对不同类型的值(字符串或者数值)按升序或者降序进行排序.Lua不是尽可能多地提供参数来满足这些情况的需要,而是接受一个排序函数作为参数(类似C++的函数对象),排序函数接受两个排序元素作为输入参数,并且返回两者的大小关系,例如:

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)

以其他函数作为参数的函数在Lua中被称作高级函数,高级函数在Lua中并没有特权,只是Lua把函数当作第一类函数处理的一个简单的结果.

下面给出一个绘图函数的例子:

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)

将在屏幕上输出一个正弦曲线.

将第一类值函数应用在表中是Lua实现面向对象和包机制的关键,这部分内容在后面章节介绍

6.1 闭包

当一个函数内部嵌套以一个函数定义时,内部的函数体可以访问外部的函数的局部变量,这种特征我们称作词法定界.虽然这看起来很清楚,事实并非如此,词法定界加上第一类函数在编程语言里是一个功能强大的概念,很少语言提供这种支持.

下面看一个简单的例子,假定有一个学生姓名的列表和一个学生名和成绩对应的表;现在想根据学生的成绩从高到低对学生进行排序,可以这样做:

names = {"Peter", "Paul", "Mary"}

grades = {Mary = 10, Paul = 7, Peter = 8}

table.sort(names, function (n1, n2)

return grades[n1] > grades[n2] -- compare the grades

end)

假定创建一个函数实现此功能:

function sortbygrade (names, grades)

table.sort(names, function (n1, n2)

return grades[n1] > grades[n2] -- compare the grades

end)

end

例子中包含在sortbygrade函数内部的sort中的匿名函数可以访问sortbygrade的参数grades,在匿名函数内部grades不是全局变量也不是局部变量,我们称作外部的局部变量(external local variable)或者upvalue.(upvalue意思有些误导,然而在Lua中他的存在有历史的根源,还有他比起external local variable简短).

看下面的代码 :

function newCounter ()

local i = 0

return function () -- anonymous function

i = i + 1

return i

end

end

c1 = newCounter()

print(c1()) --> 1

print(c1()) --> 2

匿名函数使用upvalue i保存他的计数,当我们调用匿名函数的时候i已经超出了作用范围,因为创建i的函数newCounter已经返回了.然而Lua用闭包的思想正确处理了这种情况.简单的说闭包是一个函数加上它可以正确访问的upvalues.如果我们再次调用newCounter,将创建一个新的局部变量i,因此我们得到了一个作用在新的变量i上的新闭包.

c2 = newCounter()

print(c2()) --> 1

print(c1()) --> 3

print(c2()) --> 2

c1,c2是建立在同一个函数上,但作用在同一个局部变量的不同实例上的两个不同的闭包.

技术上来讲,闭包指值而不是指函数,函数仅仅是闭包的一个原型声明;尽管如此,在不会导致混淆的情况下我们继续使用术语函数代指闭包.

闭包在上下文环境中提供很有用的功能,如前面我们见到的可以作为高级函数(sort)的参数;作为函数嵌套的函数(newCounter).这一机制使得我们可以在Lua的函数世界里组合出奇幻的编程技术.闭包也可用在回调函数中,比如在GUI环境中你需要创建一系列button,但用户按下button时回调函数被调用,可能不同的按钮被按下时需要处理的任务有点区别.具体来讲,一个十进制计算器需要10个相似的按钮,每个按钮对应一个数字,可以使用下面的函数创建他们:

function digitButton (digit)

return Button{ label = digit,

action = function ()

add_to_display(digit)

end

}

end

这个例子中我们假定Button是一个用来创建新按钮的工具,label是按钮的标签,action是按钮被按下时调用的回调函数.(实际上是一个闭包,因为他访问upvalue digit).digitButton完成任务返回后,局部变量digit超出范围,回调函数仍然可以被调用并且可以访问局部变量digit.

闭包在完全不同的上下文中也是很有用途的.因为函数被存储在普通的变量内我们可以很方便的重定义或者预定义函数.通常当你需要原始函数有一个新的实现时可以重定义函数.例如你可以重定义sin使其接受一个度数而不是弧度作为参数:

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

这样我们把原始版本放在一个局部变量内,访问sin的唯一方式是通过新版本.

利用同样的特征我们可以创建一个安全的环境(也称作沙箱,和java里的沙箱一样),当我们运行一段不信任的代码(比如我们运行网络服务器上获取的代码)时安全的环境是需要的,比如我们可以使用闭包重定义io库的open函数来限制程序打开的文件.

do

local oldOpen = io.open

io.open = function (filename, mode)

if access_OK(filename, mode) then

return oldOpen(filename, mode)

else

return nil, "access denied"

end

end

end

6.2 非全局函数

Lua中函数可以作为全局变量也可以作为局部变量,我们已经看到一些例子:函数作为table的域(大部分Lua标准库使用这种机制来实现的比如io.read;math.sin).这种情况下,必须注意函数和表语法:

1.表和函数放在一起

Lib = {}

Lib.foo = function (x,y) return x + y end

Lib.goo = function (x,y) return x - y end

2.使用表构造函数

Lib = {

foo = function (x,y) return x + y end,

goo = function (x,y) return x - y end

}

3.Lua提供另一种语法方式

Lib = {}

function Lib.foo (x,y)

return x + y

end

function Lib.goo (x,y)

return x - y

end

当我们将函数保存在一个局部变量内时,我们得到一个局部函数,也就是说局部函数像局部变量一样在一定范围内有效.这种定义在包中是非常有用的:因为Lua把chunk当作函数处理,在chunk内可以声明局部函数(仅仅在chunk内可见),词法定界保证了包内的其他函数可以调用此函数.下面是声明局部函数的两种方式:

1.方式一

local f = function (...)

...

end

local g = function (...)

...

f() -- external local `f' is visible here

...

end

2.方式二

local function f (...)

...

end

有一点需要注意的是在声明递归局部函数的方式:

local fact = function (n)

if n == 0 then return 1

else return n*fact(n-1) -- buggy

end

end

上面这种方式导致Lua编译时遇到fact(n-1)并不知道他是局部函数fact,Lua会去查找是否有这样的全局函数fact.为了解决这个问题我们必须在定义函数以前先声明:

local fact

fact = function (n)

if n == 0 then return 1

else return n*fact(n-1)

end

end

这样在fact内部fact(n-1)调用是一个局部函数调用,运行时fact就可以获取正确的值了.

但是Lua扩展了他的语法使得可以在直接递归函数定义时使用两种方式都可以.

在定义非直接递归局部函数时要先声明然后定义才可以:

local f, g -- `forward' declarations

function g ()

... f() ...

end

function f ()

... g() ...

end

6.3 正确的尾调用(原文:Proper Tail Calls)

Lua中函数的另一个有趣的特征是可以正确的处理尾调用.(一些作者使用术语尾递归[原文:proper tail recursion],虽然并未涉及到递归的概念) .

尾调用是一种类似在函数结尾的goto调用,当函数最后一个动作是调用另外一个函数时,我们称这种调用尾调用.例如:

function f (x)

return g(x)

end

g的调用是尾调用.

例子中f调用g后不会再做任何事情,这种情况下当被调用函数g结束时程序不需要返回到调用者f;所以尾调用之后程序不需要在栈中保留关于调用者的任何信息.一些编译器比如Lua解释器利用这种特性在处理尾调用时不使用额外的栈,我们称这种语言支持正确的尾调用.

由于尾调用不需要使用栈空间,那么尾调用递归的层次可以无限制的.例如下面调用不论n为何值不会导致栈溢出.

function foo (n)

if n > 0 then return foo(n - 1) end

end

需要注意的是:明确什么是尾调用.

一些调用者函数调用其他函数后也没有做其他的事情但不属于尾调用.比如:

function f (x)

g(x)

return

end

上面这个例子中f在调用g后,不得不丢弃g地返回值,所以不是尾调用,同样的下面几个例子也不时尾调用:

return g(x) + 1 -- must do the addition

return x or g(x) -- must adjust to 1 result

return (g(x)) -- must adjust to 1 result

Lua中类似return g(...) 这种格式的调用是尾调用.但是g和g的参数都可以是复杂表达式,因为Lua会在调用之前计算表达式的值.例如下面的调用是尾调用:

return x[i].foo(x[j] + a*b, i + j)

可以将为调用理解成一种goto,Lua中尾调用的用处在于有关状态机的编程.状态机的应用要求函数记住每一个状态,改变状态只需要goto(or call)一个特定的函数.我们考虑一个迷宫游戏作为例子:迷宫有很多个房间,每个房间有东西南北四个门,每一步输入一个移动的方向,如果该方向存在即到达该方向对应的房间,否则程序打印警告信息.目标是:从开始的房间到达目的房间.

这个迷宫游戏是典型的状态机,每个当前的房间是一个状态.我们可以对每个房间写一个函数实现这个迷宫游戏,我们使用尾调用从一个房间移动到另外一个房间.一个四个房间的迷宫代码如下:

function room1 ()

local move = io.read()

if move == "south" then return room3()

elseif move == "east" then return room2()

else print("invalid move")

return room1() -- stay in the same room

end

end

function room2 ()

local move = io.read()

if move == "south" then return room4()

elseif move == "west" then return room1()

else print("invalid move")

return room2()

end

end

function room3 ()

local move = io.read()

if move == "north" then return room1()

elseif move == "east" then return room4()

else print("invalid move")

return room3()

end

end

function room4 ()

print("congratilations!")

end

我们可以调用room1()开始这个游戏.

如果没有正确的尾调用,每次移动都要创建一个栈,多次移动后可能导致栈溢出.但正确的尾调用可以无限制的尾调用,因为每次尾调用只是一个goto到另外一个函数并不是传统的函数调用.

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有