Lua函数是具有特定词法域的第一类值,与其他传统类型的值(string and number)具有相同的权利。
它可以保存在变量和table中,也可以把它当参数传递,也可以作为返回值。
在Lua中有个容易混淆的概念,函数与所有其它值一样都是匿名的,即他们没有名称。
当讨论一个函数名(print),实际上是讨论一个持有某函数的变量。
这与其他变量持有各种值一个道理,可以用多种方式来操作这些变量:
a = {p = print} a.p("Hello World") -->Hello World print = math.sin -->"print" 现在引用了sin函数 a.p(print(1)) -->0.841470 sin = a.p -->"sin" 现在引用了print函数 sin(10,20) -->10 20
通常我们这样定义一个函数:
function foo (x) return 2*x end
这种写法仅仅是下面方式的一种简写:
foo = function(x) return 2*x end
实际上,函数定义就是创建了一个function类型的值,并把它赋值给一个变量。
可以将上面的function(x) body end表达式叫做函数构造式,就像{}是一个table构造式一样。
将这种函数构造式的结果叫作匿名函数。一些场合会用到匿名函数:
table.sort可以接收不同的排序方式,升序、降序、数字顺序、字符排序......
table.sort没有这些方式的函数实现,需要通过传入order function参数。
order function接收两个l列表内元素为参数,当第一个元素需要排在第二个元素之前,返回真并有序返回这两个参数。比如有一个table:
network = { {name = "grauna", IP = "210.26.20.30"}, {name = "arraial", IP = "210.26.45.33"}, {name = "lua", IP = "210.45.34.56"}, {name = "derain", IP = "210.26.23.76"}, }
如果想以name字段按照反向的字符顺序进行排序,则:
table.sort(network,function (a,b) return (a.name > b.name) end) --有点不好理解,a.name > b.name是ture 然后function是要排序还是不进行排序。 --但是从测试结果去了解,是要让table.name按照d,c,b,a进行排序 --索性就把(a.name > b.name )当成想要实现的结果,这样好理解一点
把这样的sort函数称作高阶函数,然后用匿名函数来创建高阶函数需要的实参。
另一个用高阶函数的例子,一个函数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
对于特定函数f调用derivative(f)将(近似地)返回其导数:
c = derivative(math.sin) print(math.cos(5.2),c(5.2)) --> 0.46851667130038 0.46856084325086 print(math.cos(10),c(10)) --> -0.83907152907645 -0.83904432662041
6.1 闭合函数
把一个函数嵌入到另一个函数内,并且它具有访问闭合函数里的局部变量。
有以下代码:
names = {"Peter", "Paul" , "Mary"} grades = {Mary=10,Paul=7, Peter=8} table.sort(name,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
匿名函数function可以访问grades变量,grades变量是闭合函数sortbygrade的局部变量。
对于匿名函数,grades既不是全局变量,也不是局部变量,我们称它为upvalue——上值。
Why is this point so interesting? Because functions are first-class values,
and therefore they can escape the original scope of their variables.
这是原文,没有理解透彻。待以后再细细品尝。
要对upvalue的深入了解,参考云风博客里的一篇译文:the_implementation_of_lua_50.pdf
参考以下代码:的
function newCounter() local i = 0 return function() i = i + 1 return i end end c1 = newCounter() print(c1()) -->1 print(c1()) -->2
在这个代码中,匿名函数引用了一个非局部变量i去计数。
当在调用匿名函数时,newCounter函数已经返回,i变量超出作用范围。
对此,Lua用了一个叫做closure的概念去处理这种情况。
closure是一个函数加上所有它需要访问的非局部变量。
当再重新调用newCounter,它会创建一个新的局部变量i,从而得到一个新的closure。
c2 = newCounter() print(c2()) --> 1 print(c1()) --> 3 print(c1()) --> 2
此时,c1和c2是两个不同的closure函数,各自有独立的局部变量i。
从技术层面讲,Lua中只存在closure,不存在function。
function本身仅仅是closure的原形。
closure是一个很有用的工具,比如在高阶函数sort中作为参数传参。
对创建其他函数的函数也很有用(比如沙盒函数)。
这种机制使Lua程序可以融合那些在函数式编程世界中久经考验的编程技术。
closure对于回调函数也很有用。
一个典型的例子:当你调用传统的GUI 工具创建按钮时。每当用户按下按钮,就会触发一个
回调函数。你希望的是不同的按钮按下时,只做一点轻微不同的操作。
比如一个 十进制的计算器,需要10个相似的按钮,每个数字对应一个。
function digiButton (digit) return Button { label = tostring(digit), action = function() add_to_display(digit) end } end
该例子中,Button是一个创建按钮的函数,label是按钮标签,action是一个closur函数,每
次有按钮按下就会触发。即使在digitButton函数返回,digit变量超出范围。action任然可以
去访问这个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保存为一个私有变量,只有通过新版本的sin才可以访问到它。
可以使用该技术创建一个安全的运行环境,即所谓的sandbox——”沙盒”。
当运行一些不受信任的代码时会用到该功能。比如限制一个程序访问文件:
do local oldOpen = io.open local access_OK = function (filename ,mode) --check access authority end io.open = function(filename,mode) if access_OK(filename,mode) return oldOpen(filename,mode) else return nil,"access denied" end end end
这样,只能通过新授权的版本才能访问原来的不受限的open函数。
对此Lua提供了一套元机制,可以根据特定的安全需要来创建一个安全的运行环境。
6.2 非全局函数
大多数的Lua库(io.read,math.sin)使用了这样的机制,把函数放在table字段里。
在Lua中创建这样的函数,只需要将函数语法与table语法结合起来即可。
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
也可以这样写:
Lib = { goo = function (x,y) return x+y end, foo = 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() <body> end local g = function() f() --f在这里可见 end
对于这种局部函数的定义,Lua支持的一种特殊的语法糖:
local function f() <body> end
当Lua在展开局部函数定义的语法糖时:并不是使用基本函数定义的语法。
local function foo() <body> end ---->展开后 local foo;foo = function() <body> end
在定义递归函数时,下面的代码是不能运行的:
local fact = function(n) if n == 0 then return 1 else return n*fact(n-1) end end
原因是,当Lua编译fact (n-1)时,local fact 还没有定义。此时该表达式将会去调用全局的fact,而不是local fact。
解决办法:
local fact fact = function(n) if n == 0 then return 1 else return n*fact(n-1) end end ------------------或者---------------------- local function fact(n) if n == 0 then return 1 else return n*fact(n-1) end end
当函数运行时,fact已经有了确切的值,所以不会产生错误。
当间接使用递归时,你必须明确地使用前向声明。
local f , g function g() f() end function f() g() end
注意,别把第二个函数定义为local function f,否则 g 中调用的 f 处于未定义状态。
6.3 正确的尾调用
Lua函数的另一个特性,Lua支持尾调用消除。
当一个函数调用是另一个函数的最后一个动作,该调用就是一个尾调用。
有点类似于goto语句。
比如:
function f(x) return g(x) end
当g返回时,控制权直接返回到调用f的那个点上。
尾调用消除这个特点,使得在进行 “尾调用” 时不消耗任何栈空间。
所以一个程序可以有无数嵌套的“尾调用”
function foo(n) if n>0 then return foo(n-1) end end
该函数支持任意的数字,都不会出现栈溢出。
想要用尾调用的好处,就必须要保证是尾调用。
以下代码都不是尾调用。
function f(x) g(x) end --在调用g后,f还要丢弃g的返回值 return g(x) + 1 --g返回后,还要再加 return x or g(x) --g返回后,还要再或 return (g(x)) --g返回后,还要调整为一个返回值
在Lua中,只有“return func()” 才是尾调用,Lua会在调用前对func和它的参数求值。
比如下面的代码就是尾调用:
return x[i].foo(x[j] + a*b, i+j)