lua-users home
lua-l archive

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


Generators using coroutines is not the best way to implement them; it is simpler to use a first function that returns the generator function inside the closure of the first function that contains the generation parameters; exactly what pairs()/ipairs() are doing.
No coroutine needed (yield/resume operations are costly due to context switches/longjumps), just using closures instead (to get/update saved state of the generator: the generator returned is a simple function; no complex handling of errors/exceptions)

Compare:
-- generate all the numbers from 2 to n
function gen (n)
  return coroutine.wrap(function ()
    for i=2,n do coroutine.yield(i) end
  end)
end 

With:
function gen(n)
  local i, n = 2, n return function()
     if i <= n then local j = i; i = i + 1
       return j  -- else return nil
     end
   end
end


Le dim. 10 mai 2020 à 14:17, nerditation <nerditation@outlook.com> a écrit :
Hello lua-l,

I was learning the coroutines TS (which should be coming to C++20) the other day,
and realized it can be used to implement a yieldable/resumable `lua_CFunction`
without having to manually transform the code into CPS style.

as an exercise I translated the sieve[1] demo code from Lua into C++, and here's
some snippets of what I've come up with:

-------------------- code of C++ ---------------------
/**
-- generate all the numbers from 2 to n
function gen (n)
  return coroutine.wrap(function ()
    for i=2,n do coroutine.yield(i) end
  end)
end
*/
static int gen(lua_State *L) {
    auto n = luaL_checkinteger(L, 1);
    lua_getglobal(L, "coroutine");
    lua_getfield(L, -1, "wrap");
    mylua_pushresumable(L, [n](lua_State *L) -> mylua_ResumableHandle {
        for (int i = 2; i < n; ++i) {
            lua_pushinteger(L, i);
            // same as: co_await mylua_yield(L, 1);
            co_yield 1;
        }
        co_return 0;
    });
    lua_call(L, 1, 1);
    return 1;
}

/**
-- filter the numbers generated by `g', removing multiples of `p'
function filter (p, g)
  return coroutine.wrap(function ()
    for n in g do
      if n%p ~= 0 then coroutine.yield(n) end
    end
  end)
end
*/
static int filter(lua_State *L) {
    auto p = luaL_checkinteger(L, 1);
    luaL_checktype(L, 2, LUA_TFUNCTION);
    // the C++ lambda can't capture Lua value directly
    auto g = luaL_ref(L, LUA_REGISTRYINDEX);
    lua_getglobal(L, "coroutine");
    lua_getfield(L, -1, "wrap");
    mylua_pushresumable(L, [p, g](lua_State *L) -> mylua_ResumableHandle {
        // repeatedly call the generator till it return nil
        for (;;) {
            lua_rawgeti(L, LUA_REGISTRYINDEX, g);
            co_await mylua_call(L, 0, 1);
            if (lua_isnil(L, -1)) {
                break;
            }
            auto n = lua_tointeger(L, -1);
            if ((n % p) != 0) {
                co_yield 1;
            } else {
                lua_pop(L, 1);
            }
        }
        luaL_unref(L, LUA_REGISTRYINDEX, g);
        co_return 0;
    });
    lua_call(L, 1, 1);
    return 1;
}

int main() {
    auto L = luaL_newstate();
    luaL_openlibs(L);

    // register metatables for types
    mylua_init(L);

    lua_pushcfunction(L, &gen);
    lua_setglobal(L, "gen");

    lua_pushcfunction(L, &filter);
    lua_setglobal(L, "filter");

    luaL_dostring(L, R"(
N=N or 500              -- from command line
x = gen(N)              -- generate primes up to N
while 1 do
  local n = x()         -- pick a number until done
  if n == nil then break end
  print(n)              -- must be a prime number
  x = filter(n, x)      -- now remove its multiples
end
)");
    return 0;
}

---------------------- end of C++ -----------------------

the code (full listing at [2]) is tested using Visual Studio 2019 and GCC 10.

actually I have implemented it in two ways. the snippets shown here
is implemented using the C++ `std::function<>` so that I can use
lambdas with captured state (i.e. closures generated by C++ compilers).
the other implementation accepts a plain function pointer so I have to
utilize the upvalues associated with the `lua_CFunction`.

disclaimer: this is just a quick experiment to help myself understanding the
C++ coroutines concepts. personally I have not used `lua_yieldk()`,
`lua_callk()` or `lua_pcallk()` very much in the past. I just feel it
might interest some people, so I commented the code a little bit and
want to share the results with you. just be warned the code surly contains
many errors.

cheers.


[1] https://www.lua.org/cgi-bin/demo?sieve
[2] https://gist.github.com/nerditation/441a2d4409a778ae77683fc0645b69de
_______________________________________________
lua-l mailing list -- lua-l@lists.lua.org
To unsubscribe send an email to lua-l-leave@lists.lua.org