Nanfeng

Notes on software development, code, and curious ideas

Iterators and Generic For Loops in Lua

An iterator returns the next value in a sequence each time it is called. Lua’s generic for loop supports both closures that retain state and stateless functions that receive all state explicitly.

A closure iterator

1
2
3
4
5
6
7
8
9
10
11
local function values(items)
local index = 0
return function()
index = index + 1
return items[index]
end
end

for value in values({ "a", "b", "c" }) do
print(value)
end

Iteration ends when the first returned value is nil. A closure is convenient, but each iterator call creates private captured state.

The generic for protocol

An iterator expression may return three values: an iterator function, an invariant state, and an initial control value. Conceptually,

1
2
3
for key, value in pairs(tableValue) do
-- body
end

repeatedly calls the iterator with the state and previous control value. pairs normally returns next, the table, and nil.

A stateless array iterator can use the same protocol:

1
2
3
4
5
6
7
8
9
10
11
12
13
local function nextArray(items, index)
index = index + 1
local value = items[index]
if value ~= nil then return index, value end
end

local function arrayItems(items)
return nextArray, items, 0
end

for index, value in arrayItems({ 10, 20, 30 }) do
print(index, value)
end

Ordered traversal

Table key order is not guaranteed by pairs. To produce stable output, collect and sort keys first:

1
2
3
4
5
6
7
8
9
10
11
12
local function sortedPairs(items)
local keys = {}
for key in pairs(items) do keys[#keys + 1] = key end
table.sort(keys)

local index = 0
return function()
index = index + 1
local key = keys[index]
if key ~= nil then return key, items[key] end
end
end

Use ipairs or a numeric loop for dense 1-based arrays, pairs for unordered mappings, and a custom iterator when filtering, ordering, or lazy generation is part of the abstraction.

+