Programming in Lua, 2nd edition - Chapter 6: More About Functions

本文探讨了Lua语言中函数的各种高级用法,包括函数作为变量、闭包、非全局函数及尾部调用等内容。通过实例展示了如何利用这些特性进行高效编程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

 

Chapter 6: More About Functions

 

 

函数可以被存在变量和表里面,可以被作为参数传递,可以被作为函数的返回值。

 

 

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

 

 

函数的另一种写法

 

function foo (x) return 2*x end       -- 实际上这种写法是一个语法糖

 

foo = function (x) return 2*x end      -- 本质写法

 

 

表达式function(x) body end 是一个函数构造器,就像表构造器{} 一样。

 

 

匿名函数的应用:将表的元素按字典序排序

(类似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"},

}

 

table.sort(network, function (a,b) return (a.name < b.name) end)

 

for i in pairs(network) do print(network[i].name) end

 

 

高级应用

 

导数的非正式定义:函数f 在点x 上的导数是(f(x + d) - f(x)) / d d 趋于无穷小时的值

 

function derivative (f, delta)

delta = delta or 1e-4

return function (x)

return (f(x + delta) - f(x))/delta

end

end

 

c = derivative(math.sin)

print(math.cos(10), c(10))

--> -0.83907152907645 -0.83904432662041

 

 

6.1 Closures

 

包含在另一个函数里的函数

 

包含在另一个函数里的函数可以访问包含它的那个函数的局部变量,这个特性称为lexical scoping

 

一个表以另一个表作为条件排序

 

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

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

 

function sortByGrades(names, grades)

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

              return grades[n1] > grades[n2]  -- 分数高的在前

       end)

end

 

sortByGrades(names,grades)

for i in pairs(names) do print(names[i]) end

 

匿名函数访问了局部变量,既sortByGrades 的参数

 

 

上面例子的匿名函数内,grades 既不是全局变量也不是局部变量,我们称为non-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

 

每调用一次newCounter() 都会创建一个新的non-local variable

 

c2 = newCounter()          -- 记数器2 新值

print(c2()) --> 1

print(c1()) --> 3          -- 记数器1 还保留原来的值

print(c2()) --> 2

 

 

匿名函数里面既不是全局变量又不是局部变量的变量(非局部变量)会一直保留其值??

 


 

Closures 对回调很有用

 

function digitButton (digit)

return Button{ label = tostring(digit),

action = function ()

add_to_display(digit)

end

}

end

 

 

重定义函数

 

math.sin 重定义成使用度,而不是弧度

 

do

local oldSin = math.sin    

local k = math.pi/180

math.sin = function (x)      -- 重定义math.sin

         return oldSin(x*k)       -- 这里的k 就是一个non-local variable

       end

 

       print (math.sin(90))            -- 这里的math.sin 用度

       math.sin=oldSin

end

 

print (math.sin(90))                -- 这里的math.sin 用弧度

 

 


 

沙盒:将不安全版本函数做为私有变量存在闭包里面,外部不再可以访问

 

对于不安全的不信任代码,将它放进沙盒里面

 

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 的旧版本已经作为局部变量被放在do.end 里面,外部只能访问新版本。

 

 


 

6.2 Non-Global Functions

 

非全局函数

 

作为表中一个元素的函数我们已经见过了,如io.read, math.sin,其创建方法如下:

 

Lib = {}

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

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

 

 

Lib = {

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

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

}

 

 

Lib = {}

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

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

 

 

当我们将函数存在局部变量里面,我们就获得了一个局部函数

 

定义一个局部函数

 

local f = function (<params>)

<body>

end

 

或使用语法糖式的写法

 

local function f (<params>)

<body>

end

 

 

 

递归局部函数的定义方法

 

local function fact (n)

       if n == 0 then

              return 1

       else

              return n*fact(n-1)

       end

end

 

 

local fact

fact = function (n)

if n == 0 then

return 1

else

return n*fact(n-1)

end

end

 

实际上前面那个语法糖或展开成以下形式

local foo

foo = function (<params>) <body> end

 

错误的定义

 

local fact = function (n)

         if n == 0 then

                   return 1

         else

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

         end

end

 


 

非显式递归函数的定义不能使用语法糖:

 

local f, g -- 'forward' declarations

function g ()

<some code> f() <some code>

end

function f ()

<some code> g() <some code>

end

 

 

6.3 Proper Tail Calls

 

如果函数的最后一个动作是调用另一个函数,这个调用就是尾部调用

 

function f (x) return g(x) end

 

对函数g 的调用是一个尾部调用。

 

Lua 的一个有趣特性是tail-call elimination

 

尾部调用消除使得上面的例子中,函数g 不是返回f ,而是直接返回调用f 的那个点。这样就不必为f 在栈中保留任何返回信息。

 

永运不会发生栈溢出的函数

 

function foo (n)

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

end

 

下面这个函数不是尾部调用消除的:

 

function foo (n)

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

end

foo(50000)  -- 栈溢出

 


 

这些函数也不是尾部调用消除的

 

function f (x) g(x) end

 

f 返回前要丢弃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

 

 

tail-call 就是 go to

 

tail-call用于状态机

 

应用程序可以将每个状态用一个函数表示

 

改变一个状态就是从一个函数进入另一个函数。

 

考虑一个简单的maze game 游戏maze 有许多房间,每一个房间可能有四个门:北、南、东,西。在每一步中,游戏者进入一个移动方向,如果这个方向上有门及与之相连的房间则进入相应的房间。否则,程序打印一条警告信息。游戏的目的是从一个初始房间移动到目标房间

 

这个游戏是一个典型的状态机,当前房间就是一个状态

 

我们可以为每一个房间实现一个与之相应的函数。我们使用tail-call 从一个房间移动到另一个房间。程序6.1 实现了有四个房间的小型maze 游戏。

 

游戏从调用room1 开始,进入起始房间:

 

room1()

 

如果没有尾部调用消除,游戏者的每一次移动都会创建一个新栈帧。经过大量移动后,就可能造成栈溢出。使用了尾部调用消除就不会有移动次数的限制,因为每次移动实际上只是执行了一个goto 进入另一个函数,这不同于常规函数调用。

 


 

程序6.1 A maze game:

 

function room1 ()

local move = io.read()

if move == "south" then

return room3()

elseif move == "east" then

return room2()

else

print("invalid move")

return room1()          -- 无效的移动,留在当前房间(既再次进入当前房间)。

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("congratulations!")  -- 进入了目标房间,游戏结束。

end

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值