第一版是針對 Lua 5.0 編寫的。雖然在很大程度上仍然適用於後續版本,但有一些差異。
第四版針對 Lua 5.3,可在 Amazon 和其他書店購買。
購買這本書,您也可以幫助支援 Lua 專案


9.3 – 協程作為迭代器

我們可以將迴圈迭代器視為生產者-消費者模式的一個相當具體的範例。迭代器會產生要由迴圈主體消耗的項目。因此,使用協程來撰寫迭代器似乎很恰當。事實上,協程提供了強大的工具來執行這項任務。同樣地,其關鍵功能是它們能夠顛倒呼叫者和被呼叫者之間的關係。有了這個功能,我們就可以撰寫迭代器,而不用擔心如何在對迭代器進行連續呼叫之間保持狀態。

為了說明這種使用方式,讓我們撰寫一個迭代器來遍歷給定陣列的所有排列。直接撰寫此類迭代器並不容易,但撰寫一個產生所有這些排列的遞迴函數並不困難。這個想法很簡單:輪流將每個陣列元素放在最後一個位置,然後遞迴產生所有剩餘元素的排列。程式碼如下

    function permgen (a, n)
      if n == 0 then
        printResult(a)
      else
        for i=1,n do
    
          -- put i-th element as the last one
          a[n], a[i] = a[i], a[n]
    
          -- generate all permutations of the other elements
          permgen(a, n - 1)
    
          -- restore i-th element
          a[n], a[i] = a[i], a[n]
    
        end
      end
    end
要看到它的運作方式,我們應該定義一個適當的 printResult 函數,並使用適當的引數呼叫 permgen
    function printResult (a)
      for i,v in ipairs(a) do
        io.write(v, " ")
      end
      io.write("\n")
    end
    
    permgen ({1,2,3,4}, 4)

在準備好產生器之後,將其轉換為迭代器是一項自動化的任務。首先,我們將 printResult 變更為 yield

    function permgen (a, n)
      if n == 0 then
        coroutine.yield(a)
      else
      ...
然後,我們定義一個工廠,安排在協程內執行產生器,然後建立迭代器函數。迭代器僅繼續協程以產生下一個排列
    function perm (a)
      local n = table.getn(a)
      local co = coroutine.create(function () permgen(a, n) end)
      return function ()   -- iterator
        local code, res = coroutine.resume(co)
        return res
      end
    end
有了這個機制,使用 for 陳述式遍歷陣列的所有排列就變得非常簡單
    for p in perm{"a", "b", "c"} do
      printResult(p)
    end
      --> b c a
      --> c b a
      --> c a b
      --> a c b
      --> b a c
      --> a b c

perm 函數使用 Lua 中的常見模式,它將對 resume 的呼叫與其對應的協程封裝在函數內。這個模式非常常見,以至於 Lua 為它提供了一個特殊函數:coroutine.wrap。與 create 一樣,wrap 會建立一個新的協程。與 create 不同,wrap 不会傳回協程本身;相反,它會傳回一個函數,在呼叫時會繼續協程。與原始的 resume 不同,該函數不會將錯誤碼作為第一個結果傳回;相反,它會在發生錯誤時引發錯誤。使用 wrap,我們可以將 perm 撰寫如下

    function perm (a)
      local n = table.getn(a)
      return coroutine.wrap(function () permgen(a, n) end)
    end

通常,coroutine.wrapcoroutine.create 容易使用。它提供我們從 coroutine 所需的內容:一個用於繼續運作的函式。然而,它也比較不靈活。無法檢查使用 wrap 建立的 coroutine 的狀態。此外,我們也無法檢查錯誤。