Search code examples
luaiterator

How to pass an arbitrary iterator as a function argument?


Question

If I write a generic walk routine, why can I pass in my own iterators but not pairs?

Example

Here's a simple walk that does something for each item in src.

function walk(src)
  for n,i in src do 
    print(n,  i[1]+i[#i]) end end

If I call this with walk(items(x)) then walk works fine.

function items(lst, n)
  n=0
  return function()
    if lst and n < #lst then
      n=n+1
      return n,lst[n] end end end

x={
  {1,2},
  {3,4},
  {5,6},
  {5,6}}

walk(items(x)) --> prints out (1,3),(2,7),(3,11),etc

But I try the same with pairs(x), I get a crash.

walk(pairs(x))
lua: x.lua: bad argument #1 to 'for iterator' (table expected, got nil)
stack traceback:
    [C]: in function 'next'
    x.lua:7: in function 'walk'
    x.lua:31: in main chunk
    [C]: in ?

So what is different about pairs and my own iterator items?


Solution

  • The pairs function returns 3 values:

    1. The next function
    2. The table
    3. The initial key nil

    The for-in statement uses the 3 values in an equivalent process as follows:

    local f, t, k = pairs(x)
    local v
    
    repeat
      k, v = f(t, k)
      if k then
        -- for body
      end
    until not k
    

    Since the 3rd value is always nil, if you want the walk function to be effective on pairs, you need at least 2 arguments:

    function walk(itor, t)
      for n,i in itor, t do 
        print(n, i[1]+i[#i]) end end
    

    You need 3 if you want it to be effective on any iterator functions.