簡短匿名函式

lua-users home
wiki

有時 Lua 的匿名函式語法,像是 function() return ... end,對於簡短函式來說感覺有點冗長,像是函數式程式風格中的 lambda 函式。舉例來說,在 Lua 中,像 map 函式 [1] 可使用如下方式:

local y = map(function(p) return translate[p] end, x)

在某些語言中,例如 Perl,可寫得更簡潔,像是:

my @y = map { $translate{$_} } @x;
甚至是:
my @y = @translate{@x};

以下是一些可替換 Lua 的方式。

模式:字串化匿名函式

我們可以透過公用函式 fn 來改善 Lua,它能從較短的字串表示式中建立匿名函式。可能像是這樣使用:

local y = map(fn("L1[P1]", translate), x)

fn 的參數包含表示式的字串表示形式,定義了要建立的匿名函式的回傳值,以及在表示式中稱為 L1L2L3、...、L9 等的當地變數清單。匿名函式的參數使用 P1P2P3、...、P9 表示。fn 可使用下列方式定義:

function fn(s, ...)
  local src = [[
    local L1, L2, L3, L4, L5, L6, L7, L8, L9 = ...
    return function(P1,P2,P3,P4,P5,P6,P7,P8,P9) return ]] .. s .. [[ end
  ]]
  return loadstring(src)(...)
end

這是執行此範例所需的其他程式碼:

function map(f, t)
  local t2 = {}
  for k,v in pairs(t) do t2[k] = f(v) end
  return t2
end

local translate = {["hello"] = "hola", ["bye"] = "adi�s", ["sir"] = "se�or"}
local t = map(fn("L1[P1]", translate), {"hello", "sir"})
print(table.concat(t, " ")) --> hola se�or

然而,請注意,如果 fn 重複使用在同一個字串化表示式上(例如在迴圈中),那會沒效率,因為每次呼叫都會喚起 loadstring(需要產生程式碼)。這可透過 loadstring 編寫備忘錄(請參閱 FuncTables)來改善。這個基本模式(帶有備忘錄)用於 CodeGeneration 中。

如果匿名函式沒有當地變數,那麼語法會更短。例如,fn"P1 > 0" 是檢查參數是否大於 0 的函式。

有些人可能會建議支援任意數量的 LnPn 變數;但記得,此技術僅適用於簡短的一行式表示式。

此外,在某些情況下,可以將字串->函式轉換移至 map 函式中來取得:

local y = map("P1 * 2", x)

以下是另一種語法:

getmetatable("").__call =
  function(s, ...) return assert(loadstring("return " .. s))()(...) end
("function(x,y) print(x+y) end")(2,3)  -- prints 5

此處所述技術也稱為「字串 lambda」[1],它已實作於 JavaScript 和 Erlang 中

[1] http://debasishg.blogspot.com/2007/11/erlang-string-lambdas.html

這方法有用,但不是很好,因為無法直接在 lambda 內使用詞彙變數。(一個可能的解決方案是 StringInterpolation。)

--DavidManura,2007-02

可透過 PenlightLibraries 取得字串 lambda 的實作。支援兩種形式,第一種類似是由 MetaLua 實作的,第二種則類似 Scala lambda

> require 'pl'
> L = utils.string_lambda
> = L'|x| x+2' (1)
3
> = L'_+2' (0)
2
> ls = List{'one','two','three'}
> = ls:map(L'_:upper()')
{ONE,TWO,THREE}

結果使用記事化模式快取。 原先,Penlight 中任何預期有一函式存在的函式都會傳遞一個字串,然後嘗試將該字串剖析為一個字串 lambda,但我們最後認為這會製造出過多的 magic。(這絕對適用於對字串 meta 函數的所有修改,以使它們直接可呼叫。)

Steve Donovan,2012 年

Lua 原始碼篩選

以下做法僅使用 Lua 進行原始碼篩選,因此它是完全獨立的

assert(loadstring((([[--filtered
function pass(f) f() end
function fail(f)
  if pcall(f) then error 'fail expected' end
end
pass << x = 1 + 2 >>
fail << x = 1 + nil >>
print 'DONE'
]]):gsub('<<', '(function(a,b) '):gsub('>>', ' end)'))))()

第一行中「[[]」之後應具有某些文字(例如「--」),以確保第一行會計算在任何錯誤訊息的行數中。還有,這行的文字會顯示在錯誤訊息中,因此最好應傳達某些意義。

以下是語法和執行時間錯誤的外觀

lua: src.lua:1: [string "--filtered..."]:4: 'then' expected near 'thenn'
stack traceback:
        [C]: in function 'assert'
        src.lua:1: in main chunk
        [C]: ?

$ lua src.lua
lua: [string "--filtered..."]:6: attempt to call global 'Pass' (a nil value)
stack traceback:
        [string "--filtered..."]:6: in main chunk
        src.lua:9: in main chunk
        [C]: ?

這種方法的一個缺點可能是,語法高亮顯示會將整個主程式碼標示為字串(不過,它在 XEmacs 中會正確標示顏色)。您可以透過對外部檔案執行loadfile而不是loadstring來變通解決此問題。

--DavidManura

Metalua

MetaLua [2] 提供|x,y| x*y的語法,用於function(x,y) return x*y end

Lambda 函式補丁程式 [3] 透過補丁 Lua 剖析器提供相同的語法。

do 補丁程式

LuaPowerPatches 中的「do 補丁程式」使= do ... end成為= function() ... end的語法糖。

RiscLua

RiscLua 提供\的語法糖,用於function=>,用於return。因此

curry = \(f) => \(x) => \(y) => f(x,y) end end end

簡潔匿名函式補丁程式

LuaPowerPatches 中的簡潔匿名函式補丁程式使= [ ... ]成為= function() ... end的語法糖,並使= [| ... ]成為= function() return ... end的縮寫

佔位符

Boost 等佔位符式表達式 (例如 tablex.map(_1*_1,{1,2,3,4}) --> {1,4,9,16}) 說明於「佔位符式表達式」中,請參考 [4]LuaList:2009-03/msg00452.htmlLuaList:2009-04/msg00069.html

另請參閱


最新變更 · 喜好設定
編輯 · 歷史記錄
最後編輯時間:西元 2012 年 7 月 4 日 上午 10:37((差異)