1. 程式人生 > >lua 11 閉包,函式的使用

lua 11 閉包,函式的使用

轉自:http://book.luaer.cn/_41.htm

 

當一個函式內部巢狀另一個函式定義時,內部的函式體可以訪問外部的函式的區域性變數,這種特徵我們稱作詞法定界。雖然這看起來很清楚,事實並非如此,詞法定界加上第一類函式在程式語言裡是一個功能強大的概念,很少語言提供這種支援。

下面看一個簡單的例子,假定有一個學生姓名的列表和一個學生名和成績對應的表;現在想根據學生的成績從高到低對學生進行排序,可以這樣做:

  

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

print(newCounter()) --> function: 08935750 ?
print(newCounter()) --> function: 05B09710 ?

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