第一版是針對 Lua 5.0 編寫的。儘管在很大程度上仍然適用於後續版本,但仍有一些差異。
第四版針對 Lua 5.3,可在 Amazon 和其他書店購買。
購買本書,您還可以協助 支援 Lua 專案


6 – 更多關於函式

Lua 中的函式是具有適當詞彙範圍的第一類值。

函式成為「第一類值」是什麼意思?這表示在 Lua 中,函式是一個值,具有與數字和字串等傳統值相同的權利。函式可以儲存在變數(全域和區域)和表格中,可以作為引數傳遞,也可以由其他函式傳回。

函式具有「詞彙範圍」是什麼意思?這表示函式可以存取其封裝函式的變數。(這也表示 Lua 適當地包含 lambda 演算。)正如我們將在本章中看到的,這個看似無害的特性為語言帶來了強大的功能,因為它允許我們在 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
只是一個我們稱為語法糖的範例;換句話說,這只是撰寫
    foo = function (x) return 2*x end
的一種漂亮方式。也就是說,函式定義實際上是一個陳述式(更具體地說,是一個指定),它將類型為 "function" 的值指定給變數。我們可以將運算式 function (x) ... end 視為函式建構函式,就像 {} 是表格建構函式一樣。我們將此類函式建構函式的結果稱為匿名函式。儘管我們通常將函式指定給全域名稱,為它們提供類似名稱的東西,但在函式保持匿名的情況下,有許多場合。讓我們看一些範例。

表格函式庫提供一個函式 table.sort,它會接收一個表格並對其元素進行排序。此函式必須允許在排序順序中進行無限變化:遞增或遞減、數字或字母,依據鍵值排序的表格,等等。sort 並未嘗試提供所有類型的選項,而是提供一個單一的選用參數,也就是排序函式:一個接收兩個元素並傳回第一個元素在排序中是否必須在第二個元素之前的函式。例如,假設我們有一個像這樣的記錄表格

     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)
看看匿名函式在該陳述式中有多麼方便。

一個將另一個函式作為引數的函式,例如 sort,就是我們所謂的高階函式。高階函式是一種強大的程式設計機制,而使用匿名函式來建立函式引數是一個極佳的靈活性來源。但請記住,高階函式沒有任何特殊權利;它們只是 Lua 能夠將函式視為一級值的能力的簡單結果。

為了說明將函式作為引數使用的範例,我們將撰寫一個常見高階函式 plot 的天真實作,它會繪製一個數學函式。以下我們顯示此實作,使用一些跳脫序列在 ANSI 終端機上繪製。(你可能需要變更這些控制序列才能將此程式碼調整為你的終端機類型。)

    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)
(我們需要稍微調整資料,才能將值放入適當的範圍。)當我們呼叫 plot 時,它的參數 f 會取得給定匿名函式的值,然後在 for 迴圈中重複呼叫它,以提供繪製的值。

由於函式在 Lua 中是一級值,我們不僅可以將它們儲存在全域變數中,還可以將它們儲存在區域變數和表格欄位中。正如我們稍後將看到的,在表格欄位中使用函式是 Lua 一些進階用途(例如套件和物件導向程式設計)的關鍵要素。