Moses迭代器机制快速入门cycle、iterator、tabulate源码逐行解析指南【免费下载链接】MosesUtility library for functional programming in Lua项目地址: https://gitcode.com/gh_mirrors/mos/MosesMoses 是 Lua 函数式编程的工具库而cycle、iterator、tabulate这三个函数构成了它处理数据的迭代器三件套一个负责重复取旧数据一个负责持续造新数据一个负责把数据收进口袋。本文带你逐行读懂它们的源码掌握 Lua 迭代器协议的核心设计。为什么值得读这三段源码很多工具库的迭代器只是pairs的包装但 Moses 的这三个函数只用了几行代码就展示了 Lua 迭代器协议的全部精髓函数源码位置角色关键机制cyclemoses.lua循环复取已有表数据next游标 闭包计数器iteratormoses.lua按规则生成新数据有状态闭包种子值tabulatemoses.lua惰性流 → 数组for v in ...通用协议先花 30 秒复习一下 Lua 的迭代器协议。迭代器本质上就是每次调用返回下一个值的函数local chars (hello):gmatch(.) chars() -- h chars() -- e chars() -- lgmatch返回的就是一个闭包内部偷偷记着读到第几个字符了。Moses 的三个函数全部是这个思路的变体。第一步读懂 cycle——给表装上循环播放按钮cycle的完整实现只有 19 行function M.cycle(t, n) n n or 1 if n0 then return M.noop end local k, fk local i 0 while true do return function() k k and next(t,k) or next(t) fk not fk and k or fk if n then i (kfk) and i1 or i if i n then return end end return t[k], k end end end它返回一个迭代器函数每次调用吐出表里的一个值, 键对并可以循环n圈。逐行拆解① 参数守卫n n or 1 if n0 then return M.noop end没给圈数就按 1 圈处理圈数非正数则直接返回空操作函数noop迭代器一次都不会产出。② 闭包与三张记忆卡local k, fk local i 0这是整段代码的灵魂k、fk、i是闭包捕获的upvalue。外面那圈while true do ... end在第一次调用时执行一次把内部函数交出去后就再也不运行了——迭代器的全部状态都寄存在这三个变量里。k当前游标记住上次取到哪个键fk表里的第一个键是判断转回圈首的地标i已经完成了几整圈。③ 游标推进的神来之笔k k and next(t,k) or next(t)一行同时处理两种情况首次调用k是 nil短路求值跳过next(t,k)执行next(t)拿到第一个键之后每次next(t, k)从上一个键继续往前走。走到表尾后next返回 nilk变回 nil下一轮调用又自动从表头开始——回到原点不需要任何额外的取模运算。④ 记录圈首地标fk not fk and k or fk首次调用时把第一个键存进fk之后保持不变。它的作用只有一个当k再次等于fk说明刚转完一整圈。⑤ 计数器与停止条件i (kfk) and i1 or i if i n then return end return t[k], k每次回到圈首就让计数器 1数超过n就返回nilLua 迭代器结束的信号。用一张三键表验证一下节奏n 2第 1 圈: a → b → c (i 升到 1) 第 2 圈: a → b → c (i 升到 2) 第 3 圈: a (i 升到 3 2停止这个 a 不产出)所以for v,k in M.cycle(t, 2) do ... end恰好产出 6 个值。 官方测试 spec/table_spec.lua 正是用每个元素恰好出现 3 次来验证这个行为。 一个值得知道的细节官方教程 doc/tutorial.md 描述cycle不传n时可无限循环但当前源码把n兜底为 1实际不传参只会遍历一遍。阅读开源源码时以代码为准。第二步看懂 iterator——用种子值无中生有cycle是旧数据的循环搬运工iterator则是新数据的永动机function M.iterator(f, value, n) local cnt 0 return function() cnt cnt 1 if n and cnt n then return end value f(value) return value end end它接收一个变换函数f和一个初始种子value每次调用就执行一次value f(value)产出链条上的下一环f(value)→f(f(value))→f(f(f(value)))……local po2 function(x) return x * 2 end local iter M.iterator(po2, 1) -- 从 1 开始不断翻倍 iter() -- 2 iter() -- 4 iter() -- 8 iter() -- 16逐行看两个关键设计cnt是产出口数先计数、再判断是否超过n、最后才计算产出。第三个参数n限定最大产出次数超出后返回nil收工测试用例见 spec/func_spec.lua注意求值顺序value f(value)写在return value之前意味着种子值本身不会被产出第一次返回的已经是变换后的结果。这和tabulate的原样收割形成微妙反差是读懂协议的关键。这个模式叫种子迭代seed-based iteration凡是上一步是下一步的输入的场景——斐波那契数列、复利计算、状态机——都可以这样写全程不需要数组内存里只留一个value。第三步tabulate——把惰性流装进口袋前两个函数都在慢慢给什么时候一次性要全部tabulate只需 4 行function M.tabulate(...) local r {} for v in ... do r[#r1] v end return r end它接受任意符合 Lua 迭代器协议的函数gmatch、pairs、cycle、iterator都行循环收割直到迭代器返回 nil把值装进数组。local chars (letters):gmatch(.) M.tabulate(chars) -- {l,e,t,t,e,r,s}正是for ... in让tabulate成为通用收割机它不关心迭代器内部是游标、种子还是正则匹配只认调用、取值、nil 即止这一个契约。组合实战把三件套串成流水线单独看是三段小代码组合起来就是完整的处理管线local M require(moses) -- ① 复取把配置表连播 2 轮 for v, k in M.cycle({x1, y2}, 2) do print(k, v) end -- ② 生成 ③ 收割取前 5 个偶数打包成数组 M.tabulate(M.iterator(function(x) return x 2 end, 0, 5)) -- {2, 4, 6, 8, 10} -- ② 生成 ③ 收割任意迭代器都能被收集 M.tabulate((Lua):gmatch(.)) -- {L,u,a}这就是惰性lazy设计的价值iterator每被调用一次才算一次没有预先物化的数组可以无限长序列比如全部 2 的幂按需取用而tabulate作为物化开关在需要时把流固化成数组接上map、select、reduce等其余 99% 的 Moses API。doc/tutorial.md 里tabulate一节给出的示例正是这个组合思路。总结一张表记住设计精髓函数状态存在哪终止条件一句话记忆cycle游标k 首键fk 圈数i转满n圈旧数据的循环播放键iterator种子value 计数cnt产出满n次按规则无中生有tabulate无纯收割迭代器返回 nil惰性流变数组的口袋三者的共同套路只有一条用闭包把进度藏进 upvalue用nil作为终点信号。掌握了这一点你再读 Moses 里其他生成器如range、nsorted都会觉得眼熟。⚠️ 最后提醒迭代器是一次性用品tabulate和iterlen消费过的迭代器就见底了参考 spec/func_spec.lua 中chars()被调用后返回 nil 的测试。需要重复消费重新生成一个就行——这正是函数式工具库最优雅的地方。【免费下载链接】MosesUtility library for functional programming in Lua项目地址: https://gitcode.com/gh_mirrors/mos/Moses创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考