lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


This first section is about how Lua calls iterators in a
functional-for loop, skip to "CODE" if you want the meat.

This is what ipairs() in a functional-for loop does:

local iter, self, key = ipairs({ 'a', 'b', 'c' }) -- iter is next(),
self is the table, key is 0!
local value = nil

key, value = iter(self, key) -- key, value = 1, 'a'
key, value = iter(self, key) -- key, value = 2, 'b'
key, value = iter(self, key) -- key, value = 3, 'c'
key, value = iter(self, key) -- key, value = nil (the iterator returns
nil, the loop exits)

The form for string.gmatch() is the same but with no self or starting
arguments, the iterator itself contains the state of the ...
iteration.

local iter = string.gmatch('cat', '.') -- iterator is similar to a
function returned by coroutine.wrap(), the functional-for calls it
repeatedly until it returns nil

m = iter() -- m = 'c'
m = iter() -- m = 'a'
m = iter() -- m = 't'

People generally write this when using either:

for i, v in ipairs({ 'a', 'b', 'c' }) do
    print(i, v)
end

for m in string.gmatch('cat', '.') do
    print(m)
end

Something I think most people don't know is that you can have an
iterator return more than 2 values.  Something like...

for w, x, y, z in permute({ 'a', 'b', 'c', 'd' }) do
    print(w, x, y, z)
end

Anyway, my point is you can't know how many values the iterator
returns.  You do know that the generator returns the iterator function
(required), possibly a self object, and the initial first arguments
after that.

I wanted to make a function taking the return values of another
generator and calls that iterator, returning its values in reverse.
An iterator reverser... thing:

for c in reverse(string.gmatch('cat', '.')) do print(c) end

...and have it print:
t
a
c

There obvious method to do this is to recursively call the iterator,
but return its next iterations' values before the current one.  The
thing that left me wondering was how to store the arguments.  You can
pack them into a table or store them in a coroutine (interrupted
function).  I worked out both solutions, so here is the code for Lua
5.2 (the Lua 5.1 version is here: http://codepad.org/eQP2e87S ).  You
should be able to call reverser_thread/reverser_pack() on the return
values of any generator and have it work as expected (((but in
reverse))).  I may have confused the meaning of a continuation,
unsure...  I didn't want to benchmark this because it was mostly about
finding some other method than packing args into a table to store a
vararg.  I did it for fun?  Table construction can be slow, but maybe
construction coroutines is heavier.

CODE:

-- used to retrieve args from a continuation
local dummy = function (...) return ... end

local continuation_pack =
    function (...)
        local args = table.pack(...)

        return function (f) return f(table.unpack(args)) end
    end

local continuation_thread =
    function (...)
        local c =
            coroutine.create
            (
                function (...)
                    -- args have been added
                    local f = coroutine.yield()

                    while true do
                        f = coroutine.yield(f(...))
                    end
                end
            )

        -- "init" the vararg
        coroutine.resume(c, ...)

        return function (f) return select(2, coroutine.resume(c, f)) end
    end

-- recursive; forward-declare
local reverser_impl_thread = nil
reverser_impl_thread =
    function (iter, self, ...)
        local args = continuation_thread(iter(self, ...))

        -- iteration is over?
        if args(dummy) == nil then return end

        reverser_impl_thread(iter, self, args(dummy))
        coroutine.yield(args(dummy))
    end

local reverser_impl_pack = nil
reverser_impl_pack =
    function (iter, self, ...)
        local args = table.pack(iter(self, ...))

        -- iteration is over?
        if args[1] == nil then return end

        reverser_impl_pack(iter, self, table.unpack(args))
        coroutine.yield(table.unpack(args))
    end

local reverser_pack =
    function (...)
        local args = table.pack(...)

        return coroutine.wrap(function () return
reverser_impl_pack(table.unpack(args)) end)
    end

local reverser_thread =
    function (...)
        local args = continuation_thread(...)

        return coroutine.wrap(function () return
reverser_impl_thread(args(dummy)) end)
    end

print('reverser (thread):')
for m    in reverser_thread(string.gmatch('cat', '.')) do print(m)    end
for i, v in reverser_thread(ipairs({ 'a', 'b', 'c' })) do print(i, v) end

print(('-'):rep(30))

print('reverser (pack): ')
for m    in reverser_pack(string.gmatch('cat', '.')) do print(m)    end
for i, v in reverser_pack(ipairs({ 'a', 'b', 'c' })) do print(i, v) end