列表推导式 |
|
与其他一些方法不同,以下方法将列表推导式作为库在纯 Lua 中实现(无需修补或标记过滤器)。它使用动态代码生成(loadstring)和缓存的技巧,类似于 ShortAnonymousFunctions。
此库已合并到 [Penlight] 中。
local comp = require 'comprehension' . new() comp 'x^2 for x' {2,3} --> {2^2,3^2} comp 'x^2 for _,x in ipairs(_1)' {2,3} --> {2^2,3^2} comp 'x^2 for x=_1,_2' (2,3) --> {2^2,3^2} comp 'sum(x^2 for x)' {2,3} --> 2^2+3^2 comp 'max(x*y for x for y if x<4 if y<6)' ({2,3,4}, {4,5,6}) --> 3*5 comp 'table(v,k for k,v in pairs(_1))' {[3]=5, [5]=7} --> {[5]=3, [7]=5}
-- comprehension_test.lua -- test of comprehension.lua -- Utility function for the test suite. -- Returns true/false whether arrays a and b -- are equal. This does a deep by-value comparison (supports nested arrays). local function eqarray(a, b) if #a ~= #b then return false end for i=1,#a do if type(a[i]) == 'table' and type(b[i]) == 'table' then if not eqarray(a[i], b[i]) then return false end else if a[i] ~= b[i] then return false end end end return true end local comp = require 'comprehension' . new() -- test of list building assert(eqarray(comp 'x for x' {}, {})) assert(eqarray(comp 'x for x' {2,3}, {2,3})) assert(eqarray(comp 'x^2 for x' {2,3}, {2^2,3^2})) assert(eqarray(comp 'x for x if x % 2 == 0' {4,5,6,7}, {4,6})) assert(eqarray(comp '{x,y} for x for y if x>2 if y>4' ({2,3},{4,5}), {{3,5}})) -- test of table building local t = comp 'table(x,x+1 for x)' {3,4} assert(t[3] == 3+1 and t[4] == 4+1) local t = comp 'table(x,x+y for x for y)' ({3,4}, {2}) assert(t[3] == 3+2 and t[4] == 4+2) local t = comp 'table(v,k for k,v in pairs(_1))' {[3]=5, [5]=7} assert(t[5] == 3 and t[7] == 5) -- test of sum assert(comp 'sum(x for x)' {} == 0) assert(comp 'sum(x for x)' {2,3} == 2+3) assert(comp 'sum(x^2 for x)' {2,3} == 2^2+3^2) assert(comp 'sum(x*y for x for y)' ({2,3}, {4,5}) == 2*4+2*5+3*4+3*5) assert(comp 'sum(x^2 for x if x % 2 == 0)' {4,5,6,7} == 4^2+6^2) assert(comp 'sum(x*y for x for y if x>2 if y>4)' ({2,3}, {4,5}) == 3*5) -- test of min/max assert(comp 'min(x for x)' {3,5,2,4} == 2) assert(comp 'max(x for x)' {3,5,2,4} == 5) -- test of placeholder parameters -- assert(comp 'sum(x^_1 + _3 for x if x >= _4)' (2, nil, 3, 4, {3,4,5}) == 4^2+3 + 5^2+3) -- test of for = assert(comp 'sum(x^2 for x=2,3)' () == 2^2+3^2) assert(comp 'sum(x^2 for x=2,6,1+1)' () == 2^2+4^2+6^2) assert(comp 'sum(x*y*z for x=1,2 for y=3,3 for z)' {5,6} == 1*3*5 + 2*3*5 + 1*3*6 + 2*3*6) assert(comp 'sum(x*y*z for z for x=1,2 for y=3,3)' {5,6} == 1*3*5 + 2*3*5 + 1*3*6 + 2*3*6) -- test of for in assert(comp 'sum(i*v for i,v in ipairs(_1))' {2,3} == 1*2+2*3) assert(comp 'sum(i*v for i,v in _1,_2,_3)' (ipairs{2,3}) == 1*2+2*3) -- test of difficult syntax assert(eqarray(comp '" x for x " for x' {2}, {' x for x '})) assert(eqarray(comp 'x --[=[for x\n\n]=] for x' {2}, {2})) assert(eqarray(comp '(function() for i = 1,1 do return x*2 end end)() for x' {2}, {4})) assert(comp 'sum(("_5" and x)^_1 --[[_6]] for x)' (2, {4,5}) == 4^2 + 5^2) -- error checking assert(({pcall(function() comp 'x for __result' end)})[2] :find'not contain __ prefix') -- environment. -- Note: generated functions are set to the environment of the 'new' call. assert(5 == (function() local env = {d = 5} setfenv(1, env) local comp = comp.new() return comp 'sum(d for x)' {1} end)()); print 'DONE'
为了说明运行时特性,请考虑以下代码
comp 'sum(x^2 for x if x % 2 == 0)'
这会生成以下 Lua 函数的代码
local __in1 = ... local __result = ( 0 ) for __idx1 = 1, #__in1 do local x = __in1[__idx1] if x % 2 == 0 then __result = __result + ( __x^2 ) end end return __result
请注意,没有构建中间列表。代码有效地避免了内存分配(除了函数本身的分配,但这只在第一次调用时由于缓存/记忆化而完成)。此外,没有引用全局变量。
从 [github] 下载。
注意:实现使用模块 LuaBalanced。
此模块是新的,可能仍然存在一些错误。
一个简单的扩展是提供更数学(或更像 Haskell)的语法
assert(comp 'sum { x^2 | x <- ?, x % 2 == 0 }' {2,3,4} == 2^2+4^2)
正如 Greg Fitzgerald 所建议的那样,一个引人注目的扩展是实现 SPJ 和 Wadler 提出的广义列表推导式 [2]。这为将此提升到下一个级别提供了一些明确的方向,而 Microsoft LINQ 中的相关工作 [3] 展示了这在实践中可能是什么样子。
列表推导式的“zip”扩展,使用论文中类似 Haskell 的符号
[ (x,y,z,w) | (x <- xs | y <- ys), (z <- zs | w <- ws) ] ,
只需要进行很小的更改。生成该的相应 Lua 函数如下
local __xs, __ys, __zs, __ws = ... local __ret = {} -- i.e. $list_init for __i=1,__math_min(#__xs, #__ys) do local x, y = __xs[__i], __ys[__i] for __j=1,__math_min(#__zs, #__ws) do local z, w = __zs[__j], __ws[__j] __ret[#__ret+1] = {x,y,z,w} -- i.e. $list_accum(__ret, x, y, z, w) end end return ret
(这里的“$”符号是用于扩展源代码的编译时宏的简写。)
支持排序或分组,例如,再次使用论文中的符号
[ the x.name, sum x.deposit | x <- transactions, group by x.name ] ,
可以通过生成类似这样的函数来实现
local __transactions = ... local __groups1 = {} local __groups2 = {} for __i = 1, #__transactions do local x = __transactions[__i] local __key = ( x.name ) -- i.e. $group_by_key __groups1[__key] = ( x.name ) -- i.e. $accum_the(__groups1[__key], $val1) __groups2[__key] = (__groups2[__key] or 0) + ( x.deposit ) -- i.e. $accum_sum(__groups2[__key], $val2) end local __result = {} -- i.e. $list_init for __key in __pairs(__groups1) do __result[__result+1] = {__groups1[__key], __groups2[__key]} -- i.e. $accum_list(__result, __v) end return __result
经过一些研究,将它完全实现似乎相当可行,尽管这将是对该模块的重大扩展(有人愿意尝试吗?)。
2009-12-01 - 修复 __call 方便操作符未缓存(由 Joshua Jensen 在邮件列表中指出)
列表推导也已在 MetaLua(clist)中实现。
列表推导也已在 LuaMacros 中实现。
ScriptMoonscript 有列表推导 - http://moonscript.org/reference/#comprehensions 。Moonscript 会编译成 Lua 代码。