更簡單的 For 迭代器

lua-users home
wiki

目前的迭代器「for」迴圈模型或許可以簡化。以下是一個說明目前模型的試驗,以及介紹一個較為簡潔的替代理論。複雜度似乎是基於

目前的語法為

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

資料是由函式傳回的實際資料,之後會用在區塊中。下方會對 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」,在教學中稱為「狀態」。在參照手冊中,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_ 可以做為向上值使用它(對吧?)。「範圍」只能是迭代器中的局部變數,因此不需要明確定義為函式的引數。(如果有任何錯誤,請更正,包括術語)

我們可以想像更複雜的案例,例如指定產生器時間間隔。其他資料就變成反覆運算器

-- 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


最近異動 · 喜好設定
編輯 · 歷程
上次編輯時間為 2009 年 11 月 13 日下午 3:41 GMT (diff)