简短匿名函数

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、...、P9fn 可以这样定义

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" 是一个函数,它检查其参数是否大于零。

有些人可能会建议支持任意数量的 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

字符串 lambda 的实现可与 PenlightLibraries 一起使用。支持两种形式,第一种类似于 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,但最终我们认为这会引入太多魔法。(这绝对适用于对字符串元表的任何修改,以使其直接可调用。)

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 end 补丁

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 GMT (差异)