Simpler For Iterator

lua-users home
wiki

也许可以简化当前的迭代器“for”循环模型。本文档旨在尝试解释当前模型并介绍一种更简单的替代方案——或许吧。似乎复杂性源于

当前的语法如下:

for A, data in iterator_func, X, Y do block end

Data 是函数返回的实际数据,之后在块中使用。A、X 和 Y 的含义将在下面进一步解释。这里是基于教程示例实现的集合迭代器和生成器迭代器的可能实现(力求明确,并从 1 开始以作改变)

-- collection iterator --
numbers = {1,3,5,7,9,11,13}
function coll_squares(coll)
    local function next_square(coll, index)
        if index > #coll then
            return nil
        end
        n = coll[index]
        return index+1, n*n
    end
    return next_square, coll, 1
end
for i, square in coll_squares(numbers) do print (square) end     --> OK

-- generator iterator --
function gen_squares(limit)
    local function next_square(limit, number)
        if number > limit then
            return nil
        end
        return number+1, number*number
    end
    return next_square, limit, 1
end
for n, square in gen_squares(7) do print (square) end     --> OK

那么,A、X 和 Y 是什么?对于集合

对于生成器

很难找到一个共同点来有意义地解释和命名 A、X 和 Y。X 在参考手册中称为“s”,在教程中称为“state”。在参考手册中,A 称为 var1,而 Y 称为 var。这里有一个尝试来理解它们:

[如果有人找到更好的名字...] 除了用于产生下一个数据之外,标记和范围还一起用于知道何时停止迭代。猜测迭代器和迭代器函数应该返回什么,以及函数隐式地从 Lua 接收什么,以及所有这些值的正确顺序,并非易事。

上面的代码可以重写如下:

-- collection iterator --
function coll_squares(coll)
    local index = 1
    local coll = coll       -- just to make things clear
    local function next_square()
        if index > #coll then
            return nil
        end
        n = coll[index]
        index = index+1
        return n*n
    end
    return next_square
end
for square in coll_squares(numbers) do print (square) end     -- OK

-- generator iterator --
function gen_squares(limit)
    local number = 1
    local limit = limit     -- ditto
    local function next_square()
        if number > limit then
            return nil
        end
        n = number
        number = number+1
        return n*n
    end
    return next_square
end
for square in gen_squares(7) do print (square) end     -- OK

存在一些细微的差异,除了最后一点,其他都是简化。

最后一点使得标记(索引或数字)成为迭代器中的一个局部变量,可以通过 _closure_ 作为上值(upvalue)访问(对吗?)。“范围”只能是迭代器中的一个局部变量,因此无需将其显式作为参数传递给函数。(如有错误,请纠正,包括术语)

我们可以想象更复杂的情况,例如指定生成器间隔。附加数据成为迭代器参数。

-- generator iterator --
function gen_squares(start, stop, step)
    local number = start
    local function next_square()
        if number > stop then
            return nil
        end
        n = number
        number = number+step
        return n*n
    end
    return next_square
end
for square in gen_squares(3,9,2) do print (square) end     --> OK

同理,如果我们复杂化一个集合迭代器(这里是人为地):

-- collection iterator --
require "math"
numbers = {1,3,5,7,9,11,13,15,17}
function coll_squares(coll, modulo)
    local index = 1
    local function number_filter()
        -- return next number in coll multiple of modulo, else nil
        while (index < #coll) do
            number = coll[index]
            if math.fmod(number, modulo) == 0 then
                return number
            end
            index = index+1
        end
        return nil
    end
    local function next_square()
        -- yield squares of multiples of modulo in coll
        n = number_filter()
        if not n then
            return nil
        end
        index = index+1
        return n*n
    end
    return next_square
end
for square in coll_squares(numbers, 3) do print (square) end     --> OK

在所有情况下,A、X 和 Y 似乎都是不必要的。这种实现迭代器的方式充分利用了 Lua 的基本特性:函数作为值、嵌套函数、闭包/上值。因此,问题是:通过去掉 A、X 和 Y,我们能否简化“for”语法、迭代器和迭代器函数之间的接口?如果可以,新的语法可能是:

for data in iterator_func do block end
而当前的是:
for A, data in iterator_func, X, Y do block end

因此,迭代器的多样性将不会被语法本身以一种相当复杂的方式全局捕获,而是留给用户来实现。(请纠正,如果有错误,包括术语)学习和解释语法以及为特定任务编写迭代器的正确方法肯定会更容易。

参考手册指出:

<< f、s 和 var 是不可见的变量。这里的名称仅用于解释目的。 >>
在目前的提议中,它们不存在。所需数据像现在一样作为参数传递给迭代器:集合、边界或其他任何东西。

(第一页表述由 DeniSpir)


RecentChanges · preferences
编辑 · 历史
最后编辑于 2009 年 11 月 13 日 上午 9:41 GMT (差异)