第一版为 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 更易于使用。它为我们提供了协程所需的一切:一个用来恢复它的函数。但是,它也较不灵活。没有办法检查使用 wrap 创建的协程的状态。此外,我们无法检查错误。