Functions vs. Callable objects in Lua

While working on Lunatic Python, I’ve understood that Lua does specific type checking with lua_isfunction() in some places where a callable type is expected. As a side effect, these places only accept a real Lua function when the callable object might be used. An example of such behavior is, at the time this is being written, the table.foreach() function:

> obj = {}
> mt = {__call=function() print("Called!") end}
> setmetatable(obj, mt)
> obj()
Called!
> table.foreach({a=1}, obj)
stdin:1: bad argument #2 to `foreach' (function expected, got table)
stack traceback:
        [C]: in function `foreach'
        stdin:1: in main chunk
        [C]: ?

The trick used in Lunatic Python to overwhelm this situation was to enclose the custom Python object type inside a real Lua C function closure. This trick might indeed be used anywhere this situation is found. Here are a few functions that allow this trick to be used from inside Lua:

static int lwrapcall(lua_State *L)
{
        lua_pushvalue(L, lua_upvalueindex(1));
        lua_insert(L, 1);
        lua_call(L, lua_gettop(L)-1, LUA_MULTRET);
        return lua_gettop(L);
}

static int lwrapfunc(lua_State *L)
{
        luaL_checkany(L, 1);
        lua_pushcclosure(L, lwrapcall, 1);
        return 1;
}

static int luaopen_wrapfunc(lua_State *L)
{
        lua_pushliteral(L, "wrapfunc");
        lua_pushcfunction(L, lwrapfunc);
        lua_rawset(L, LUA_GLOBALSINDEX);
}

And here is another implementation, by Alex Bilyk, in pure Lua:

function wrapfunc(callable)
    return function(...)
        return callable(unpack(arg))
    end
end

Using them one would be able to obtain the effect above as follows:

> table.foreach({a=1}, wrapfunc(obj))
Called!

Hopefully, in the future the standard Lua library will stop checking for a specific type in such cases, or implement some kind of lua_iscallable() checking.

This entry was posted in Lua, Project, Python. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *