Coroutine Patterns
24.1 Generators
local function gen_range(n)
return coroutine.wrap(function()
for i = 1, n do coroutine.yield(i) end
end)
end
for i in gen_range(3) do print(i) end
-- Permutations generator
local function permgen(a, n)
n = n or #a
if n <= 1 then coroutine.yield(a)
else
for i = 1, n do
a[n], a[i] = a[i], a[n]
permgen(a, n - 1)
a[n], a[i] = a[i], a[n]
end
end
end
local function permutations(a)
return coroutine.wrap(function() permgen(a) end)
end
for p in permutations({"a", "b", "c"}) do print(table.concat(p)) end
-- Tree traversal (yield from recursion)
local function inorder(node)
if node then
inorder(node.left)
coroutine.yield(node.value)
inorder(node.right)
end
end
local tree = {value = 2, left = {value = 1}, right = {value = 3}}
for v in coroutine.wrap(function() inorder(tree) end) do print(v) end24.2 Producer/Consumer Pipelines
local function producer()
return coroutine.wrap(function()
for line in io.lines("data.txt") do coroutine.yield(line) end
end)
end
local function filter(source, pred)
return coroutine.wrap(function()
for item in source do if pred(item) then coroutine.yield(item) end end
end)
end
local function map(source, f)
return coroutine.wrap(function()
for item in source do coroutine.yield(f(item)) end
end)
end
for line in map(filter(producer(), function(l) return #l > 0 end), string.upper) do print(line) end24.3 A Simple Cooperative Scheduler
local tasks = {}
local function spawn(f, ...) tasks[#tasks + 1] = coroutine.create(f) end
local function sleep_ticks(n) for _ = 1, n do coroutine.yield() end end
spawn(function() for i = 1, 3 do print("task A", i); sleep_ticks(1) end end)
spawn(function() for i = 1, 2 do print("task B", i); sleep_ticks(2) end end)
while #tasks > 0 do
for i = #tasks, 1, -1 do
local co = tasks[i]
local ok, err = coroutine.resume(co)
if not ok then print("task error: " .. err) end
if coroutine.status(co) == "dead" then table.remove(tasks, i) end
end
end24.4 Async/Await Style (Callbacks to Sequential Code)
This pattern powers game scripting (cutscenes, AI behaviors that "wait"), Neovim plugins (plenary.async, vim.uv callbacks), OpenResty's cosockets (non-blocking I/O that looks synchronous), and copas (coroutine-based networking with LuaSocket).
-- Turn a callback-based API into sequential-looking code
local function async(fn)
return function(...)
local co = coroutine.create(fn)
local function step(...)
local ok, waiter = coroutine.resume(co, ...)
if not ok then error(waiter) end
if coroutine.status(co) ~= "dead" then waiter(step) end -- waiter receives the continuation
end
step(...)
end
end
local function await(start_operation) -- start_operation(callback)
return coroutine.yield(start_operation)
end
-- Fake callback-based timer API
local function set_timeout(cb, value) cb(value) end
local main = async(function()
local a = await(function(cb) set_timeout(cb, 10) end)
local b = await(function(cb) set_timeout(cb, 20) end)
print("sum:", a + b) --> sum: 30
end)
main()