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) end

24.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) end

24.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
end

24.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()