Generators

Writing coroutine.resume and catching the true every time gets tiring. When you only want the values a coroutine yields, use coroutine.wrap. It makes a coroutine too, but gives you a function instead: each call of that function resumes the coroutine and returns just the values from yield, without the true:

-- Emma's seed bag
local nextSeed = coroutine.wrap(function()
    coroutine.yield("carrot")
    coroutine.yield("potato")
    coroutine.yield("pumpkin")
end)

print(nextSeed())
print(nextSeed())
print("Last one: " .. nextSeed())

This prints:

carrot
potato
Last one: pumpkin

The function for the coroutine is anonymous here, written right inside the parentheses, as you know from the lesson on functions. A function that gives you the next value every time you call it is called a generator.

Generators in for. You've used one before: for word in text:gmatch("%a+") do from the lesson on strings. gmatch returns a function, and for … in calls it before every round of the loop. What it returns goes into the loop variables; when it returns nil or nothing, the loop ends. A generator from coroutine.wrap fits right in: when its function reaches its end, the call returns nothing, and the loop stops.

Usually, you write a function that takes some settings and returns a new generator. This one goes through the plots of a field, row by row:

-- Every plot of the field
local function plots(rows, columns)
    return coroutine.wrap(function()
        for row = 1, rows do
            for column = 1, columns do
                coroutine.yield(row, column)
            end
        end
    end)
end

for row, column in plots(2, 2) do
    print("Plot " .. row .. "-" .. column)
end

This prints:

Plot 1-1
Plot 1-2
Plot 2-1
Plot 2-2

The nested loops are hidden in the generator, and the loop that uses it is as simple as a loop with ipairs. Between calls, the coroutine remembers where it is in both loops. Each call of plots(2, 2) makes a new generator, which starts again at plot 1-1.

Watch out:

  • A generator is used up once its function has ended. Calling it again stops the program with cannot resume dead coroutine. For another round, make a new generator.
  • An ordinary function with yield inside, not made by coroutine.wrap, doesn't work in for: the loop runs it as an ordinary function, and its yield fails with attempt to yield from outside a coroutine.
  • A coroutine from coroutine.create isn't a function, so it doesn't work in for either: attempt to call a thread value. For for, use wrap.

Instructions

The function findReady yields the crop of every plot that has grown long enough. Make a generator of it, local nextReady = coroutine.wrap(findReady), call it twice and print what it returns. The program prints:

carrot
wheat

Now harvest the whole field. Loop over a new generator, for crop in coroutine.wrap(findReady) do, and for each ready plot, print Harvest: and its crop. (Why a new one? nextReady has already given out two crops and would go on with the third.) The program prints:

carrot
wheat
Harvest: carrot
Harvest: wheat
Harvest: strawberry

Start programming for free

By signing up, you agree to the Terms of Service and Privacy Policy.

Or sign up with:

3/6