Genny is a lua libraries for working with generators.

Lua defines iterators that can be used with for loops. Unfortunately, since they are defined as 3 separate values, it is very hard to manipulate these iterators. Genny defines so-called "generators", which nothing but lua iterators that don't take any arguments. Since this means a generator is a single (callable) value, it's much easier to pass them around, manipulate them, store them, etc.

Of course, Genny is more than this concept, and comes with a bunch of functions to create, manipulate and use generators. Note that where other libraries tend to create lots of intermediary tables, no functions create tables unless otherwise specified.


The following functions all produce generators. While the code examples will all use lua's generic for loops to iterate over generators, it is possible to iterate over them manually. Calling the generator (e.g. gen() for a generator gen) produces the next values, when the first return value is nil, the generator ends. Note that calling a generator after it has returned nil is undefined behaviour.


gen = genny.generator(it, state, init)

This function takes any lua iterator (including generators) and turns them into a generator. It's mostly useful when working with other libraries, or iterators that haven't already been wrapped by Genny.


g = genny.generator(ipairs{1, 2, 3})
for i, v in g do
    print(i, v) -- prints 1 and 1, then 2 and 2, then 3 and 3


gen = genny.ipairs(t)

Shorthand for genny.generator(ipairs(t)), iterates over a sequence and returns the index and its value. Stops on the first nil value.


gen = genny.ripairs(t)

Much like genny.ipairs, but iterates in reverse. Starts at index #t and ends at index 1. Mostly useful for when the table is to be modified in the loop, as the indices won't change when elements shift down.


gen = genny.pairs(t)

Shorthand for genny.generator(pairs(t)), iterates over a table and returns the key and value.


gen = genny.range([from,] to, [step])

A generator that iterates over all integers in the range from up to and including to. Generator version of for i = from, to. In case from is omitted, a value 1 is assumed. In case step is specified, its value is added each iteration.


for i in genny.range(3) do
    print(i) -- prints 1, 2, 3

for i in genny.range(2, 4) do
    print(i) -- prints 2, 3, 4

for i in genny.range(1, 5, 2) do
    print(i) -- prints 1, 3, 5

for i in genny.range(3, 1, -1) do
    print(i) -- prints 3, 2, 1


gen = genny.gmatch(str, pattern)

Shorthand for genny.generator(string.gmatch(str, pattern)), iterates over all matches of pattern in str.


gen = genny.split(str, delim, [plain, [empty]])

Iterates over all substrings in str, delimited by pattern delim. In case plain is true, delim is treated as a literal string, instead of a pattern. If plain is absent, its value is assumed to be false. In case empty is true or absent, empty substrings are also returned, if it is false, empty substrings are skipped.


for column in genny.split("a,b,c,d", ",") do
    print(column) -- prints a, then b, then c, then d


gen = genny.once(value)

Returns value the first time it is called, returns nil from then on. Usually only useful in combination with generator manipulation.


Combinators act upon multiple generators to form a new generator.


gen = genny.join(first, ...)

Iterates over all generators in turn. First all of first is iterated over, then all of second, then all of third, etc.


local t1 = {1, 2, 3}
local t2 = {"a", "b", "c"}
for i, v in genny.join(genny.ipairs(t1), genny.ipairs(t2)) do
    print(v) -- prints 1, 2, 3, a, b, c


gen = genny.roundrobin(first, ...)

Iterates over all generators simultaneously. Returns the first value of first, then the first value of second, then the first value of third, etc, then the second value of first, the second value of second, the second value of third, etc.


local t1 = {1, 2, 3}
local t2 = {"a", "b", "c"}
for i, v in genny.roundrobin(genny.ipairs(t1), genny.ipairs(t2)) do
    print(v) -- prints 1, a, 2, b, 3, c


Operators take one generator, and return a new, modified generator.


gen = genny.enumerate(gen)

Adds a counter to each iteration, much like ipairs.


for c in genny.gmatch("abc", ".") do
    print(c) -- prints a, b, c
for i, c in genny.enumerate(genny.gmatch("abc", ".")) do
    print(i, c) -- prints 1 a, 2 b, 3 c

gen =, func)

Applies func to each iteration of the input generator, then returns its return values. Useful when an operation needs to be applied to each element. Note that map can change the number of values, their contents, and even stop iteration if it returns nil as first value.


for c in genny.gmatch("abc", ".") do
    print(c) -- prints a, b, c
for c in"abc", "."), string.upper) do
    print(c) -- prints A, B, C


gen = genny.filter(gen, func)

Applies func to each iteration of the input generator, and skips an iteration if func returns false or nil.


function even(x)
    return x % 2 == 0
for x in genny.filter(genny.range(5), even) do
    print(x) -- prints 2, 4


gen = genny.discard(gen, [elems])

Discards the first elems keys, mostly useful with iterators like ipairs. If elems is omitted, it defaults to 1 element. This means genny.discard(genny.enumerate(gen)) produces a generator that is equivalent to gen.


for v in genny.discard(genny.ipairs{4, 5, 6}) do
    print(v) -- prints 4, 5, 6


gen = genny.tablify(gen)

Produces a new generator that no longer returns multiple values, but instead a single table containing those values.


for iv in genny.tablify(genny.ipairs{4, 5, 6}) do
    print(iv[1], iv[2]) -- prints 1 4, 2 5, 3 6


gen = genny.take(gen, max)

Returns a new generator that produces at most max iterations. So genny.take(genny.ipairs{1, 2, 3}, 2) only returns the key/value pairs for the first and second elements. If the input generator produces less than max elements, the resulting generator does not add additional elements.


gen = genny.when(gen, func)

Much like genny.filter, but when func returns false or nil iteration stops.


function f(i, v)
    return v == 5
for i, v in genny.when(genny.ipairs{4, 5, 6}, f) do
    print(v) -- prints 4


Collectors consume a generator, and produce a value.


tbl = genny.sequence(gen)

Returns a sequence, i.e. a table with only positive integer keys, built from the first return values of the generator. Note that if a generator returns multiple values, all but the first are ignored, so it's often useful to combine this with genny.discard.


t1 = genny.sequence(genny.range(5)) -- t1 is now {1, 2, 3, 4, 5}
t2 = genny.sequence(genny.ipairs{4, 5, 6}) -- t2 is now {1, 2, 3}
t3 = genny.sequence(genny.gmatch("abc", ".")) -- t3 is now {"a", "b", "c"}


tbl = genny.dictionary(gen)

Much like genny.sequence, this returns a table containing the returned values. Unlike genny.sequence, it expects two return values (and ignores the rest), using the first as key, and the second as value.


t = genny.dictionary(genny.gmatch("a=b;c=d;", "(.-)=(.-);")) -- t is now {a = "b", c = "d"}


state = genny.fold(gen, init, func)

Uses func to combine all return values in the generator into one value. func should be a function that takes the current state (initially init) and the return values returned by the generator and produces a new state. This function always calls func in iteration order, and when gen produces no value, it returns init.


function plus(state, value)
    return state + value

sum = genny.fold(genny.range(5), 0, plus) -- returns init+1+2+3+4+5=0+15=15


Anything that does not fall in the above categories.


chain = genny.chain(gen)

Chain can be used to flatten the calls, to prevent deeply nested function calls. A chain is itself a valid generator, but also has a next method, which allows adding on an operator.


s = "a=b;c=d;"

function filter(k, v)
    return k == "c"

function mapper(k, v)
    return 4*k, v

chain = genny.chain(genny.gmatch(s, "(.-)=(.-);")) -- (a, b), (c, d)
    :next(genny.filter, filter) -- (c, d)
    :next(genny.discard) -- d
    :next(, string.upper) -- D
    :next(genny.enumerate) -- (1, D)
    :next(, mapper) -- (4, D)

for k, v in chain do
    print(k, v) -- prints 4 D