前言
先表明,我是个正在学习 Lua 的新手,在遇到些学习上的问题,经过思考尝试,让后写下我对相关问题知识的认识。
从菜鸟教程上学习 Lua 迭代器过程中,遇到了些障碍。虽然根据菜鸟教程中的解释:
下面我们看看泛型 for 的执行过程:
- 首先,初始化,计算 in 后面表达式的值,表达式应该返回泛型 for 需要的三个值:迭代函数、状态常量、控制变量;与多值赋值一样,如果表达式返回的结果个数不足三个会自动用 nil 补足,多出部分会被忽略。
- 第二,将状态常量和控制变量作为参数调用迭代函数(注意:对于 for 结构来说,状态常量没有用处,仅仅在初始化时获取他的值并传递给迭代函数)。
- 第三,将迭代函数返回的值赋给变量列表。
- 第四,如果返回的第一个值为nil循环结束,否则执行循环体。
- 第五,回到第二步再次调用迭代函数
但是在自己更具理解,试着编写一些测试 Lua 无状态迭代器代码时,出现的结果却让人捉急。
可先阅览 Lua 官方文档中对迭代器的描述: Lua 5.3 文档 -3.3.5
(能看懂官方解释,基本上本文就不必往下看了)
经历
初次编写
开始根据菜鸟教程编写了一个无状态迭代器,当然运行结果也正常。
1 | function square(iteratorMaxCount,currerntNumber) |
这段代码的行为其实很简单,就是打印了10以内所有的平方数。
根据菜鸟教程描述的 for 的执行过程可知,suqare 就是迭代器函数,10是状态常量,0则是控制变量。而当运行时,会先根据 square 以及参数,得到迭代器的三个元素(迭代函数,状态常量,控制变量),以后就会利用这三个元素来进行迭代的过程。
但这里菜鸟教程对这里有些轻描淡写,并没说清楚真正各个参数是如何被使用,尤其是 square 的参数和返回值之间的联系或者是否有联系,却没讲清楚。
问题的出现
后来,我在想经常性在 for 中用两个变量来接收,能不能只用一个 临时变量来接收迭代结果,并且迭代器函数能否只有一个参数,一个返回值。
于是有了下面的代码:
1 | function aa(a) |
以我最初对迭代器的理解,应该会执行10次运算,然而:
1 | 2 |
无限循环打印 2 。
此时就开始纳闷了,这个迭代器的内部原理究竟是什么,aa() 被做了,其中的参数 a 又做了什么处理,返回的 a 除了赋值给了 i,然后又去了哪里。
为了解决这些疑问,我尝试了很多种不同的组合形式,比如将参数设置为2个,返回值设置成多个,参数调换位置等,但很多时候最后的表现结果却和预期差的很远。
没有办法,百度不给力,那就只能去看 Lua 的官方文档了。
果不其然,官方的解释非常好理解,还给了示例。
官方解释
参照官方文档-3.3.5的解释:
for statement like
1 | for var_1, ···, var_n in explist do block end |
is equivalent to the code:
1 | do |
上面的迭代器遍历可以看作是下面的过程:
可见一开始由 do 语句块将包裹起来,其中会创建三个变量,而这三个变量 f,s,var 正好对应着迭代器三元素:f-迭代器函数,s-状态常量,var-控制变量。其值由 explist 的返回值获得。这也就证明了为何 for 迭代器 in 后面的最终函数必须以:“迭代器函数,状态常量,控制变量“ 的形式/结构来编写。
后来在语句块中编写一个 while 循环,其内部又用多个局部变量 var_x,来接收 f(s,var) 的结果。而能看出这些 var_x 就是我们 for 循环中用于接收迭代过程结果的变量。这就构成了一个闭包,f 就是这里的一个闭包函数。
而下面的语句,将 var_1 接收到的 var 的值又再次赋值给 var。var 是一个外部变量,那么在下次循环中,f(s,var)
的 var 传入的就是新值,也即是控制变量的更新。
当 var 为 nil 时,循环就会跳出。
思考后的结论
注意事项
参考了这些,我认为对于无状态迭代器的编写需要遵守几个规则:
- for 迭代中,in 后面必须最终能接收到:迭代器函数,状态常量,控制变量 ;且顺序不能改变。
- 我们自行创建的迭代器方法要保证能正常迭代,必须包含两个(或至少两个)参数;同时参数分别表示:”状态常量,控制变量”,顺序也不允许改变。
- 我们自建的迭代器方法的返回值的第一个值,会作为下一次迭代的控制变量参数传递进来。而其他参数只会用做返回赋值给 for 的接收变量。
认识
参照这几个规则,基本上能保证我们编写的无状态迭代器不会出现什么超出预期的问题。
而根据分析,也能得出关于迭代器的内部参数特点:
- for 迭代 in 的后面可以是个包装器,方便我们阅读,其返回值就是迭代器函数,状态常量,控制变量 这三个元素。
- 内部实际上将迭代函数以闭包形式调用。
- 可见状态常量只有一开始初始化迭代器三元素的过程中被使用,之后的时候都是不变的,且直接作为对于参数被传递给迭代器函数。
- 迭代器函数的返回值有可能多也可能少,总之在填补的时候,没有的就会填补 nil ,多余的就被忽视。
- 迭代器函数的第一个返回值,必须是表示控制变量的值。
- 要使得迭代函数的迭代过程,只需要使得返回的控制变量的值为 nil 即可。
- 最后根据官方描述-3.3.5,for 中可以使用 break 中断结束循环迭代。