第六課 深入函數

在Lua中,函數是一種“第一類值”(和數字、字符串等一樣,具有相同的權利),它們具有特定的詞法域(一個函數可以嵌套在另一個函數中,內部函數可以訪問外部函數中的變量)。函數式語言,函數式編程。
在Lua中有一個容易混淆的概念是,函數與其他所有值一樣 都是匿名的,即它們都沒有名稱。當討論一個函數名時(例如print),實際上是在討論一個持有某函數的變量。這與其他變量持有各種值是一樣的道理。可以以多種方式來操作這些變量。
a = {p = print}
a.p("Hello World")
print =math.sin
a.p(print(1))
sin = a.p
sin(10, 20)

Lua中最常見的就是 函數編寫方式,如:
function foo(x) return 2 * x end
只是一種“語法糖”而已,上面這句只是一下代碼 的一種簡化書寫形式:
foo = function (x) return 2 * x end
因此,一個函數定義實際上就是一條語句(更準確地說就是一條賦值語句),這條語句創建了一種類型爲“函數”的值。並將這個值賦予一個變量。可以將表達式“function(x) <body> end”視爲一種函數的構造式,就像table的構造式{}一樣。將這種函數構造式的結果稱爲一個“匿名函數”。雖然一般情況下,會將函數賦予全局變量,即給予其一個名稱。但在某些特殊情況中,仍會需要用到匿名函數。

table庫中的函數table.sort,對table中的元素進行排序。sort函數沒有提供排序準則(按升序或降序、按數字順序等),而是提供了一個所謂”次序函數“。這個函數接受兩個元素,並返回在有序情況下第一個元素是否應該排在第二個元素前面。
network = {
{name = "grauna", IP = "210.26.30.20"}
{name = "arraial", IP = "210.26.30.21"}
{name = "lua", IP = "210.26.30.22"}
{name = "derain", IP = "210.26.30.23"}
}
如果以name字段、按照反向的字符順序來對這個table排序的話,只需要這麼寫:
table.sort(network, function (a, b) return (a.name > b.name) end)
像sort這樣的函數,接受另一個函數作爲其實參的,稱其爲一個“高階函數”。

閉合函數closure
若將一個函數寫在另一個函數之內,那麼位於內部的函數便可以訪問外部函數中的局部變量,這項特徵稱之爲“詞法域”。
function sortbygrade(names, grades)
table.sort(names, function(n1, n2) return grades[n1] > grades[n2] end)
end
內部的匿名函數可以訪問外部函數sortbygrade的參數grades,即sortbygrade的局部變量。在這個匿名函數內部,grades既不是全局變量,也不是局部變量,將其稱爲一個“非局部的變量”。
從技術上講,Lua中只有closure,而不存在“函數”,因爲函數本身就是一種特殊的closure。
可以使用這樣的技術來創建一個安全的運行環境,即所謂的“沙盒( sandbox)”
do
local oldOpen = io.open
local access_ok = function(filename ,mode)
<檢查訪問權限>
end
io.open = function(filename, mode)
if access_ok(filename, mode) then
return oldOpen(filename, mode)
else
return nil, "access denied"
end
end
end
經過重新定義後,一個程序就只能通過新的受限版本來調用原來的那個未受限制的open函數了。

非全局的函數
函數不僅可以存儲在全局變量中,還可以存儲在table的字段中和局部變量中。
將函數存儲在table字段中,大部分Lua庫都使用了這種機制。(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 (參數)
<函數體>
end
或者(局部函數定義語法糖)
local function f(參數)
<函數體>
end
在定義遞歸的局部函數時,還有一個特別之處需要注意。
local fact = function (n)
if n == 0 then return 1
else return n * fact(n -1) --錯誤,這時局部的fact還未定義完,這裏
的fact只能是表示全局函數fact
end
end
修改爲:
local fact
fact = function (n)
if n == 0 then return 1
else return n * fact(n -1)
end
end
當Lua展開局部函數定義的語法糖時,並不是使用基本函數定義語法。而是對於局部函數定義:
local function foo (<參數>) <函數體> end
Lua將其展開爲:
local foo
foo = function (<參數>) <函數體> end
因此,使用這種語法來定義遞歸函數不會產生錯誤:
local function fact(n)
if n == 0 then return 1
else return n * fact(n - 1)
end
end
這個技巧對於間接遞歸的函數而言是無效的。在間接遞歸的情況中,必須使用一個明確的前向聲明。
local f, g
function g()
f()
end
function f()
g()
end
別把第二個函數定義爲“local function f”如果那樣的話,Lua會創建一個全新的局部變量f,而將原來聲明的f(函數g中所引用的那個)置於未定義的狀態。

正確的尾調用
Lua支持“尾調用消除”(類似goto的調用)。當一個函數調用是另一個函數的最後一個動作時,該調用纔算是一條“尾調用”。
function f(x) return g(x) end --g(x)的調用就是一條尾調用
f調用完g之後就再無其他事情可做了。因此,程序就不需要返回那個“尾調用” 所在的函數了。在“尾調用”之後,程序也不需要保存任何關於該函數的棧信息了。有一些語言實現可以得益於這個特點,使得在進行“尾調用”時不耗費任何棧空間。將這種實現稱之爲支持“尾調用消除”。
“尾調用”不會耗費棧空間,所以一個程序可以擁有無數嵌套的“尾調用”。
在調用一下函數時,傳入任何數字作爲參數都不會引起棧溢出。
function foo (n)
if n > 0 then return foo(n - 1) end
end
在Lua中,只有“return <func>(<args>)”這樣的調用形式纔算是一條“尾調用”。Lua在調用前對<func>以及其參數求值,所以它們可以是任何複雜的表達式。如:
return x[i].foo(x[j] + a * b, i + j)
尾調用類似goto,因此在Lua中“尾調用”的一大應用就是編寫“狀態機”。這種程序通常以一個函數來表示一個狀態,改變狀態就是goto到另一個特定的函數。










發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章